文章目录
1 反射
1.1 反射的作用
Java的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以调用任意对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
- 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
- 通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
- 使用反射机制能在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象方法。
- 反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。
1.2 反射的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
2 反射代码实现
2.1 相关API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
2.2 获取class对象
想要创建一个对象,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤对该类进行初始化。在这个过程中,该类产生了三种阶段的类型:源码阶段(.class文件),Class对象阶段,运行时对象阶段。因为这三种阶段的存在,也就产生了三种获取Class对象的方式。
// 源码阶段
Class cla1 = Class.forName("Person");
// Class对象阶段
Class cla2 = Person.class;
// Class对象阶段
Person person = new Person();
Class cla3 = person.getClass();
// 判断是否相等
System.out.println(cla1 == cla2); //True
System.out.println(cla1 == cla3); //True
2.3 获取接口和父类
- getInterfaces() :获取当前运行时类的接口的Class。
- getSuperclass():获取当前运行时类的父类的Class。
- getGenericSuperclass():获取当前运行时类的带泛型的父类的Class。
2.4 获取构造器
- getConstructors():获取当前运行时类中声明为public权限的构造器。
- getDeclaredConstructors():获取当前运行时类中所有的构造器。
在Constructor对象中:
- getModifiers():取得修饰符。
- getName():取得方法名称。
- getParameterTypes():取得参数的类型。
2.5 获取方法
- getMethods():获取当前运行时类及其父类中声明为public权限的方法。
- getDeclaredMethods():获取当前运行时类中的所有方法(不包含父类中声明的方法)。
在Method对象中:
- getAnnotations():获取方法声明的注解。
- getModifiers():取得权限修饰符(返回整数)。
- getReturnType():获取方法的返回值类型。
- getName():获取方法名。
- getParameterTypes():获取方法的参数。
- getExceptionTypes():取得异常信息。
2.6 获取属性
- getFields(): 获取当前运行时类及其父类中声明为public权限的属性。
- getDeclaredFields() :获取当前运行时类中的所有属性(不包含父类中声明的属性)。
- 在Field对象中:
- getModifiers(): 以整数形式返回此属性的权限修饰符。(再调用toString()方法可以输出对应的英文)。
- getType() :得到Field的属性类型。返回类型为class。
- getName(): 返回属性的名称。
2.7 获取注解
- getAnnotations():获取当前运行时类声明的注解。
2.8 获取泛型
- getGenericSuperclass():获取父类泛型类型。
- getTypeParameter():泛型类型。
- getActualTypeArguments():获取实际的泛型类型参数数组。
2.9 获取包信息
- getPackage(): 获取当前运行时类所在的包。
3 动态代理
3.1 动态代理是什么
静态代理中,对目标对象的方法增强都是手动完成的。比如继承原来类,重写方法时,增强代码的功能。
动态代理的作用就是在不改变源码的基础上,实现对方法级别上的增强。在被代理的类的每个方法时,都会被拦截,此时可以在代理类中添加代码实现对方法的增强,可以看做是对类的横向扩展。Java的动态代理有两种形式:基于接口的动态代理、基于子类的动态代理。与之对应的是基于JDK实现的动态代理和基于CGLIB实现的动态代理。
3.2 基于JDK动态代理实现日志记录(基于接口)
定义一个计算器,该计算器可以实现加法和减法操作。代理功能增强的是,记录每一次操作到日记文件上。
public class Main {
public static void main(String[] args) throws Exception {
Counter counter = new Counter();
ICounter counterProxy = (ICounter) Proxy.newProxyInstance(counter.getClass().getClassLoader(),
counter.getClass().getInterfaces(),
(proxy, method, args1) -> {
Object returnValue = null;
if (method.getName().equals("add")) {
// 增强代码的逻辑
returnValue = method.invoke(counter, args1[0], args1[1]); // 原方法的执行
// 增强代码的逻辑
}
return returnValue;
});
int add = counterProxy.add(5, 10);
System.out.println("add1 = " + add);
}
}
3.3 基于CGLIB动态代理实现日志记录(基于子类)
public class CountTest {
public static void main(String[] args) {
Counter counter = new Counter();
Counter cglibCounter = (Counter) Enhancer.create(counter.getClass(), new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = null;
if (method.getName().equals("add")) {
// 增强代码的逻辑
returnValue = method.invoke(counter, args[0], args[1]);
// 增强代码的逻辑
}
return returnValue;
}
});
cglibCounter.add(500, 50);
}
}
3.4 JDK动态代理和CGLIB动态代理对比
- JDK动态代理只能代理实现了接口的类或者直接代理接口,而CGLIB可以代理未实现任何接口的类。
- CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final 类型的类和方法。
就二者的效率来说,大部分情况都是JDK动态代理更优秀,随着JDK版本的升级,这个优势更加明显。
3.5 静态代理和动态代理的对比
- 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
- JVM层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的class文件。而动态代理是在运行时动态生成类字节码,并加载到JVM中的。