Java注解、反射及动态代理
注解
注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。
Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
注解声明
声明一个注解类型
Java中所有的注解,默认实现 Annotation 接口:
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:
public @interface Test{ }
元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :
@Target
注解标记另一个注解,以限制可以应用注解的 Java 元素类型。目标注解指定以下元素类型之一作为其值:
名称 | 作用域 |
---|---|
ElementType.ANNOTATION_TYPE | 可以应用于注解类型 |
ElementType.CONSTRUCTOR | 可以应用于构造函数 |
ElementType.FIELD | 可以应用于字段或属性 |
ElementType.LOCAL_VARIABLE | 可以应用于局部变量 |
ElementType.METHOD | 可以应用于方法级注解 |
ElementType.PACKAGE | 可以应用于包声明 |
ElementType.PARAMETER | 可以应用于方法的参数 |
ElementType.TYPE | 可以应用于类的任何元素 |
@Retention
注解指定标记注解的存储方式:
名称 | 保留级别 |
---|---|
RetentionPolicy.SOURCE | 标记的注解仅保留在源级别中,并被编译器忽略 |
RetentionPolicy.CLASS | 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略 |
RetentionPolicy.RUNTIME | 标记的注解由 JVM 保留,因此运行时环境可以使用它 |
@Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。下文会介绍他们不同的应用场景。
下面来看例子:
//@Target(ElementType.TYPE) 只能在类上标记该注解
@Target({ElementType.TYPE,ElementType.FIELD}) // 允许在类与类属性上标记该注解
@Retention(RetentionPolicy.SOURCE) //注解保留在源码中
public @interface Test {
}
注解类型元素
在上文元注解中,允许在使用注解时传递参数。我们也能让自定义注解的主体包含 annotation type element (注解类型元素) 声明,它们看起来很像方法,可以定义可选的默认值。
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Test {
String value(); //无默认值
int age() default 1; //有默认值
}
注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。
@Test("帅")
//如果只存在value元素需要传值的情况,则可以省略:元素名=
@Test(value="帅",age = 2)
int i;
注解应用场景
按照**@Retention**元注解定义的注解存储方式,注解可以被在三种场景下使用:
RetentionPolicy.SOURCE
RetentionPolicy.SOURCE,作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。
IDE语法检查
在Android开发中, support-annotations 与 androidx.annotation) 中均有提供 @IntDef 注解,此注解的定义如下:
@Retention(SOURCE) //源码级别注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}
Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。
比常量多5到10倍的内存占用。此注解的意义在于能够取代枚举,实现如方法入参限制。
如:我们定义方法 test ,此方法接收参数 teacher 需要在:Lance、Alvin中选择一个。如果使用枚举能够实现为:
public enum Teacher{
LANCE,
ALVIN
}
public void test(Teacher teacher) {
}
而现在为了进行内存优化,我们现在不再使用枚举,则方法定义为:
public static final int LANCE = 1;
public static final int ALVIN = 2;
public void test(int teacher) {
}
然而此时,调用 test 方法由于采用基本数据类型int,将无法进行类型限定。此时使用@IntDef增加自定义注解:
public static final int LANCE = 1;
public static final int ALVIN = 2;
@IntDef(value = {LANCE, ALVIN}) //限定为LANCE,ALVIN
@Target(ElementType.PARAMETER) //作用于参数的注解
@Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface Teacher {
}
public void test(@Teacher int teacher) {
}
此时,我们再去调用 test 方法,如果传递的参数不是 LANCE 或者 ALVIN 则会显示 Inspection 警告(编译不会报错)。
以上注解均为 SOURCE 级别,本身IDEA/AS 就是由Java开发的,工具实现了对Java语法的检查,借助注解能对被注解的特定语法进行额外检查。
APT注解处理器
APT全称为:“Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。
注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
APT相关移步至:
https://blog.youkuaiyun.com/qq_20521573/article/details/82321755
RetentionPolicy.CLASS
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。
所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。
在程序中有多处需要进行是否登录的判断,如果我们使用普通的编程方式,需要在代码中进行 if-else 的判断,也许存在十个判断点,则需要在每个判断点加入此项判断。此时,我们可以借助 AOP(面向切面) 编程思想,将程序中所有功能点划分为: 需要登录 与 无需登录两种类型,即两个切面。对于切面的区分即可采用注解。
//Java源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login {
}
@Login
public void jumpA(){
startActivity(new Intent(this,AActivity.class));
}
public void jumpB(){
startActivity(new Intent(this,BActivity.class));
}
在上诉代码中, jumpA 方法需要具备登录身份。而 Login 注解的定义被设置为 CLASS 。因此我们能够在该类所编译的字节码中获得到方法注解 Login 。在操作字节码时,就能够根据方法是否具备该注解来修改class中该方法的内容加入 if-else 的代码段:
//Class字节码
@Login public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}
RetentionPolicy.RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
反射(Reflect)
反射之中包含了一个「反」字,所以了解反射我们先从「正」开始。
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的內部信息,并能直接操作任意对象的内部属性及方法。
Java反射机制主要提供了以下功能:
- 在运行时构造任意一个类的对象
- 在运行时获取任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法(属性)
Java 是一门面向对象的语言。在面向对象的世界里,万事万物皆对象,既然万事万物皆对象,那么我们的类是不是对象呢?我们写的每一个类都可以看成一个对象,是 java.lang.Class 类的对象。每一个类对应的Class放在哪里呢?当我们写完一个类的Java文件,编译成class文件的时候,编译器都会将这个类的对应的class对象放在class文件的末尾。里面都保存了些什么?大家可以理解保存了类的元数据信息,一个类的元数据信息包括什么?有哪些属性,方法,构造器,实现了哪些接口等等,那么这些信息在Java里都有对应的类来表示。
Class类
Class是一个类,封装了当前对象所对应的类的信息
一个类中有属性,方法,构造器等,比如说有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类。
Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等
对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例
获取Class对象的三种方式
- 通过类名获取 类名.class
- 通过对象获取 对象名.getClass()
- 通过全类名获取 Class.forName(全类名)
Class类的常用方法
方法名 | 功能说明 |
---|---|
static Class<?> forName(@NonNull String className) | 返回指定类名name的Class对象 |
native T newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
String getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class<? super T> getSuperclass() | 返回当前Class对象的父类的Class对象 |
Class<?>[] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
判断是否为某个类的实例
一般地,我们用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法:
public native boolean isInstance(Object obj);
判断是否为某个类的类型
public boolean isAssignableFrom(Class<?> cls)
创建实例
通过反射来生成对象主要有两种方式:
-
使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class; Object str = c.newInstance();
-
先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象 Class<?> c = String.class; //获取String类带一个String参数的构造器 Constructor constructor = c.getConstructor(String.class); //根据构造器创建实例 Object obj = constructor.newInstance("23333"); System.out.println(obj);
获取构造器信息
得到构造器的方法
Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类) Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有) Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs)
获取类的成员变量(字段)信息
获得字段信息的方法
Field getField(String name) -- 获得命名的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段
调用方法
获得方法信息的方法
Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法
当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。 invoke 方法的原型为:
public Object invoke(Object obj, Object... args)
利用反射创建数组
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference 其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:
public static Object newInstance(Class<?> componentType, int length);
反射获取泛型真实类型
当我们对一个泛型类进行反射时,需要的到泛型中的真实数据类型,来完成如json反序列化的操作。此时需要通过 Type 体系来完成。 Type 接口包含了一个实现类(Class)和四个实现接口,他们分别是:
- TypeVariable 泛型类型变量。可以泛型上下限等信息;
- ParameterizedType 具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
- GenericArrayType 当需要描述的类型是泛型类的数组时,比如List[],Map[],此接口会作为Type的实现。
- WildcardType 通配符泛型,获得上下限信息;
TypeVariable
public class TestType <K extends Comparable & Serializable, V> {
K key;
V value;
public static void main(String[] args) throws Exception { // 获取字段的类型
Field fk = TestType.class.getDeclaredField("key");
Field fv = TestType.class.getDeclaredField("value");
TypeVariable keyType = (TypeVariable)fk.getGenericType();
TypeVariable valueType = (TypeVariable)fv.getGenericType();
// getName 方法
System.out.println(keyType.getName()); // K
System.out.println(valueType.getName()); // V
// getGenericDeclaration 方法
System.out.println(keyType.getGenericDeclaration()); // class com.test.TestType
System.out.println(valueType.getGenericDeclaration()); // class com.test.TestType
// getBounds 方法
System.out.println("K 的上界:"); // 有两个
for (Type type : keyType.getBounds()) { // interface java.lang.Comparable
System.out.println(type); // interface java.io.Serializable
}
System.out.println("V 的上界:"); // 没明确声明上界的, 默认上界是 Object
for (Type type : valueType.getBounds()) { // class java.lang.Object
System.out.println(type);
}
}
}
ParameterizedType
public class TestType {
Map<String, String> map;
public static void main(String[] args) throws Exception {
Field f = TestType.class.getDeclaredField("map");
System.out.println(f.getGenericType()); // java.util.Map<java.lang.String, java.lang.String>
ParameterizedType pType = (ParameterizedType) f.getGenericType();
System.out.println(pType.getRawType()); // interface java.util.Map
for (Type type : pType.getActualTypeArguments()) {
System.out.println(type); // 打印两遍: class java.lang.String
}
}
}
GenericArrayType
public class TestType<T> {
List<String>[] lists;
public static void main(String[] args) throws Exception {
Field f = TestType.class.getDeclaredField("lists");
GenericArrayType genericType = (GenericArrayType) f.getGenericType();
System.out.println(genericType.getGenericComponentType());
}
}
WildcardType
public class TestType {
private List<? extends Number> a; // 上限
private List<? super String> b; //下限
public static void main(String[] args) throws Exception {
Field fieldA = TestType.class.getDeclaredField("a");
Field fieldB = TestType.class.getDeclaredField("b");
// 先拿到范型类型
ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType();
ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType();
// 再从范型里拿到通配符类型
WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0];
WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0];
// 方法测试
System.out.println(wTypeA.getUpperBounds()[0]); // class java.lang.Number
System.out.println(wTypeB.getLowerBounds()[0]); // class java.lang.String
// 看看通配符类型到底是什么, 打印结果为: ? extends java.lang.Number
System.out.println(wTypeA);
}
}
Gson反序列化
static class Response<T> {
T data;
int code;
String message;
@Override public String toString() {
return "Response{" +
"data=" + data +
", code=" + code +
", message='" + message + '\'' +
'}';
}
public Response(T data, int code, String message) {
this.data = data;
this.code = code;
this.message = message;
}
}
static class Data {
String result;
public Data(String result) {
this.result = result;
}
@Override public String toString() {
return "Data{" + "result=" + result + '}';
}
}
public static void main(String[] args) {
Response<Data> dataResponse = new Response(new Data("数据"), 1, "成功");
Gson gson = new Gson();
String json = gson.toJson(dataResponse);
System.out.println(json);
//为什么TypeToken要定义为抽象类?
Response<Data> resp = gson.fromJson(json, new TypeToken<Response<Data>>() {
}.getType());
System.out.println(resp.data.result);
}
在进行GSON反序列化时,存在泛型时,可以借助 TypeToken 获取Type以完成泛型的反序列化。但是为什么TypeToken 要被定义为抽象类呢?
因为只有定义为抽象类或者接口,这样在使用时,需要创建对应的实现类,此时确定泛型类型,编译才能够将泛型signature信息记录到Class元数据中。
动态代理
代理模式和静态代理
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:张三想买某种用品,虽然他可以自己去找,但是这确实太浪费时间和精力了,或者不好意思去买。于是张三就通过中介Mark来买,Mark来帮张三,张三只是负责选择自己喜欢的的size,然后付钱就可以了。
目的:(1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性; (2)通过代理对象对原有的业务增强;
代理模式一般会有三个角色:
- 抽象对象:指代理角色和真实角色对外提供的公共方法,一般为一个接口
- 真实对象:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此。
- 代理对象:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制都放到代理角色中处理!而访问者不再访问真实角色,而是去访问代理角色。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。
静态代理,一对一则会出现时静态代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。
动态代理
是指在使用时再创建代理类和实例
优点
- 只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码
- 更强的灵活性
缺点
- 效率低,相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法
- 应用场景局限,因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类创建代理类。
在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,这个类和接口是实现我们动态代理所必须用到的。InvocationHandler接口是给动态代理类实现的,负责处理被代理对象的操作的,而Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象我们才能调用那些需要代理的方法。
//抽象角色
interface Api {
void test(String a);
}
//真实角色
class ApiImpl implements Api{
@Override public void test(String a) {
System.out.println("真实实现:" + a);
}
}
//创建真实角色实例
Api api = new ApiImpl();
//JDK动态代理:
Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{Api.class}, //JDK实现只能代理接口
new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行真实对象方法
return method.invoke(api, args);
}
});
实际上, Proxy.newProxyInstance 会创建一个Class,与静态代理不同,这个Class不是由具体的.java源文件编译而来,即没有真正的文件,只是在内存中按照Class格式生成了一个Class。
动态代理实现原理
public interface AToolsFactory {
void saleATools(String size);
}
public interface BToolsFactory {
void saleBTools(float length);
}
public class AaFactory implements AToolsFactory {
@Override
public void saleATools(String size) {
System.out.println("按需求定制了一个size为"+size+"的model");
}
}
public class BbFactory implements BToolsFactory {
@Override
public void saleBTools(float length) {
System.out.println("按需求定制了一个高度为"+length+"的model");
}
}
public class MarkCompany implements InvocationHandler {
/*持有的真实对象*/
private Object factory;
public Object getFactory() {
return factory;
}
public void setFactory(Object factory) {
this.factory = factory;
}
/*通过Proxy获得动态代理对象*/
public Object getProxyInstance(){
return Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),this);
}
/*通过动态代理对象方法进行增强*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
doSthAfter();
Object result = method.invoke(factory, args);
doSthBefore();
return result;
}
/*前置处理器*/
private void doSthAfter() {
System.out.println("精美包装,快递一条龙服务");
}
/*后置处理器*/
private void doSthBefore() {
System.out.println("根据需求,进行市场调研和产品分析");
}
}
AToolsFactory aafactory = new AaFactory();
MarkCompany markCompany = new MarkCompany();
markCompany.setFactory(aafactory);
AToolsFactory employee1= (AToolsFactory)markCompany.getProxyInstance();
employee1.saleManTools("E");
BToolsFactory bbToolsFactory = new BbFactory();
markCompany.setFactory(bbToolsFactory);
BToolsFactory employee2= (BToolsFactory)markCompany.getProxyInstance();
employee2.saleWomanTools(1.9f);
通过调试模式我们发现,动态代理里,代理类的类名是这样的:
AToolsFactory employee1= (AToolsFactory)markCompany.getProxyInstance();//{$Proxy0@551}
这个代理类为何是这个名字?它是如何执行被代理对象的相关方法呢?我们在java文件编译后的目录里其实找不到这个名为$Proxy0的class文件的。
观察Proxy.newProxyInstance方法,与创建对象有关的代码主要有:
获得代理类的class对象:
Class<?> cl = getProxyClass0(loader,nitfs);
获得代理类的构造器:
final Constructor<?> cons = cl.getConstructor(constructorParams);
创建代理类的实例
return cons.newInstance(new Object[]{h});
看来其中的关键点就是如何获得代理类的class对象,我们进入getProxyClass0方法,进而进入proxyClassCache.get方法,通过这个这个方法所在的类名,我们可以推测,JDK内部使用了某种机制缓存了我们的代理类的class对象,同时get方法接受的参数是被代理类的类加载器和类实现的的接口。
在这个get方法中,除去和缓存相关的操作,同时用到了被代理类的类加载器和类实现的的接口这两个参数的是
Object var9 = Objects.requireNonNull(this.subKeyFactory.apply(var1, var2));
我们再进入java.lang.reflect.Proxy查看这个方法的实现,
public Class<?> apply(ClassLoader var1, Class<?>[] var2) {
........
if (var16 == null) {
var16 = "com.sun.proxy.";
}
long var19 = nextUniqueNumber.getAndIncrement();
String var23 = var16 + "$Proxy" + var19;//代理类的类名
byte[] var22 = ProxyGenerator.generateProxyClass(var23, var2, var17);//代理类的字节码
try {
return Proxy.defineClass0(var1, var23, var22, 0, var22.length);
} catch (ClassFormatError var14) {
throw new IllegalArgumentException(var14.toString());
}
}
}
而最终生成代理类的class对象是defineClass0方法,但是这个方法是个native方法,所以我们不去也无法深究它,但是通过这个方法的参数我们可以明显看到它接收了上面所生成的byte数组。
public class ProxyUtils {
public static void generateClassFile(Class clazz,String proxyName){
/*ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);*/
byte[] proxyClassFile =ProxyGenerator.generateProxyClass(
proxyName, new Class[]{clazz});
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
out = new FileOutputStream(paths+proxyName+".class");
out.write(proxyClassFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ProxyUtils.generateClassFile(aafactory.getClass(),
employee1.getClass().getSimpleName());
而我们通过ProxyUtils,这个自己写的工具类,将这个byte数组写入文件,我们并反编译,我们将会看到
public final class $Proxy0
extends Proxy //派生自Proxy类
implements AaFactory{//实现了业务接口
private static Method m1;//各种方法
private static Method m2;
private static Method m3;
private static Method m4;
...
}
同时我们还会看到其中实现了业务接口的方法
public final void saleATools(String paramString) throws Exception {
try{
this.h.invoke(this,m3,new Object[]{paramString});
}catch(Exception e){
}
}
而h则来自派生类Proxy中
public class Proxy implements Serializable {
private static final long serialVersionUID = -2222568056686623797L;
private static final Class<?>[] constructorParams = new Class[]{InvocationHandler.class};
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache(new Proxy.KeyFactory(), new Proxy.ProxyClassFactory());
protected InvocationHandler h;
...
}
这个h的实例来自哪里?不就是我们在创建代理类的实例时传入的吗?
public class MarkCompany implements InvocationHandler {
/*持有的真实对象*/
private Object factory;
public Object getFactory() {
return factory;
}
public void setFactory(Object factory) {
this.factory = factory;
}
/*通过Proxy获得动态代理对象*/
public Object getProxyInstance(){
return Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),this);
}
...
}
使用了动态代理的Retrofit
Retrofit简单的说就是一个网络请求的适配器,它将一个基本的Java接口通过动态代理的方式翻译成一个HTTP请求,并通过OkHttp去发送请求。此外它还具有强大的可扩展性,支持各种格式转换以及RxJava。我们基于Retrofit2解析。
先定义一个名为X的java接口,当然里面有各种注解。
@FormUrlEncoded注解表示from表单,另外还有@Multipart等注解。@POST表示post请求,此外还可以使用@GET请求
public interface X{
@FormUrlEncoded
@POST("user/personal_list_info")
Call<Response<PersonalInfo> getPersonalListInfo(@Field("cur_page") int page);
}
然后如何使用的呢?
首先将域名传入构造一个Retrofit,然后通过retrofit中的create方法传入一个Java接口并得到一个x(当然x这个对象是经过处理了的)调用getPersonalListInfo(12)然后返回一个Call,最后这个Call调用了enqueue方法去异步请求http,这就是一个基本的Retrofit的网络请求。Retrofit2中Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client。
void requestRetrofit(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("www.xxxx.com/").build();
X x = retrofit.create(X.class);
Call<Response<PersonalInfo>> call = x.getPerSonalListInfo(12);
call.enqueue(new Callback<Response<PersonalInfo>>(){
@Override
public void onResponse(Call<Response<PersonalInfo>>call,
Response<Response<PersonalInfo>> response){
//数据请求成功
}
public void onFailure(Call<Response<PersonalInfo>> call, Throwable t){
// 数据请求失败
}
});
}
我们只定义了一个接口X,并没有实现这个接口,那么它是如何工作的呢?我们看看create方法的实现。
create()方法是个泛型方法,调用它时会返回一个范型T的对象,我们这里类型是X接口,在内部实现上,很明显了使用了动态代理返回了一个X的代理类。当调用X内部方法的时候,会调用invoke方法。invoke方法内则通过内部一系列的封装最后返回一个Call对象。
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}