Java基础-反射&&动态代理
参考文献
反射
获取Class对象的四种方式
-
知道具体类的情况下可以使用:
1
Class klass = Person.class;
-
通过
Class.forName()
传入类的全路径获取:1
Class klass = Class.forName("cn.holelin.Person");
-
通过对象实例
instance.getClass()
获取:1
2Person o = new Person();
Class klass = o.getClass(); -
通过类加载器
xxxClassLoader.loadClass()
传入类路径获取:1
ClassLoader.getSystemClassLoader().loadClass("cn.holelin.Person");
- 通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
使用Class
对象
- 一旦得到了 Class 对象,便可以正式地使用反射功能了:
- 使用
newInstance()
来生成一个该类的实例.它要求该类中拥有一个无参数的构造器. - 使用
isInstance(Object)
来判断一个对象是否该类的实例,语法上等同于instanceof
关键字. - 使用
Array.newInstance(Class,int)
来构造该类型的数组. - 使用
getFields()/getConstructors()/getMethods()
来访问该类的成员.除了这三个之外,Class
类还提供了许多其他方法.需要注意的是,方法名中带Declared
的不会返回父类的成员,但是会返回私有成员;而不带Declared
的则相反.
- 使用
- 当获得了类成员之后,可以进一步做如下操作:
- 使用
Constructor/Field/Method.setAccessible(true)
来绕开 Java 语言的访问限制. - 使用
Constructor.newInstance(Object[])
来生成该类的实例. - 使用
Field.get/set(Object)
来访问字段的值. - 使用
Method.invoke(Object, Object[])
来调用方法.
- 使用
反射的作用
- 在运行时获取类的信息
代理模式
- 代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
代理模式的分类
- 代理根据创建代理类的时间点的不同,可以分为静态代理和动态代理
静态代理
- 由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来.在程序运行之前,代理类的.class文件就已经生成.
动态代理
-
基于接口的动态代理:基于接口的动态代理是指代理类和目标类都要实现同一个接口,代理类通过实现
InvocationHandler
接口,实现代理类对目标类的代理逻辑。在运行时,使用Proxy
类的静态方法newProxyInstance()
创建代理类的实例。这种方式只能为接口创建代理对象。 -
基于类的动态代理:基于类的动态代理是指代理类和目标类都是普通的 Java 类,代理类通过继承目标类,覆盖其中的方法,实现代理类对目标类的代理逻辑。在运行时,使用
ByteBuddy、ASM
等字节码工具生成代理类的字节码,并通过类加载器加载代理类。这种方式可以为类创建代理对象。 -
Java中,动态代理常见的又有两种实现方式:
JDK
动态代理和CGLIB
代理JDK
动态代理其实就是运用了反射机制;JDK
动态代理会帮我们实现接口的方法,通过invokeHandler
所需要的方法进行增强;- JDK中的
Proxy
只能为接口生成代理类,如果想给某个类创建代理类,那么Proxy是无能为力的,
CGLIB
代理则是用ASM
框架,通过修改其字节码生成子类来处理
JDK
动态代理实现步骤
-
在Java的
java.lang.reflect
包下提供了一个Proxy
类和一个InvocationHandler
接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象.1
2java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
-
通过实现
InvocationHandler
接口来自定义自己的InvocationHandler
;1
2//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu); -
通过
Proxy.getProxyClass
获得动态代理类1
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
-
通过反射机制获得代理类的构造方法,方法签名为
getConstructor(InvocationHandler.class)
1
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
-
通过构造函数获得代理对象并将自定义的
InvocationHandler
实例对象传为参数传入1
Person stuProxy = (Person) cons.newInstance(stuHandler);
-
通过代理对象调用目标方法
-
其中上述步骤可以简化一下,如下所示
1
2
3
4
5//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(
Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
CGLIB
动态代理实现
拦截所有方法
1 | import org.junit.Test; |
直接放行,不做任何操作(NoOp.INSTANCE
)
1 |
|
不同的方法使用不同的拦截器(CallbackFilter
)
1 |
|
不同的方法使用不同的拦截器(CallbackHelper
)
1 |
|
JDK 动态代理的优缺点
使用场景
- 如果程序需要频繁、反复地创建代理对象,则JDK动态代理在性能上更占优.
优点
- 简单易用:JDK 动态代理不需要任何第三方库支持,只需要 JDK 提供的相关类即可.
- 安全可靠:JDK 动态代理只能代理实现了接口的类,不会对未实现接口的类进行代理,避免了对未知类型的方法调用.
- 高效性能:JDK 动态代理是基于 Java 反射机制实现的,在生成类上比较高效.
缺点
- 只能代理实现了接口的类,无法代理未实现接口的类.
- 无法代理
final
类和方法:由于cglib是通过继承来实现代理的,因此无法代理final类和方法. - 只能代理
public
方法,不能代理private
和protected
方法. - 需要实现接口并重写其中的方法,如果接口中方法较多,则需要编写大量的重复代码,增加了代码的复杂性和维护难度.
CGLIB 优缺点
适用场景
- 不需要频繁创建代理对象的应用,如Spring中默认的单例bean,只需要在容器启动时生成一次代理对象.
优点
- 支持代理未实现接口的类,能够代理所有的方法.
- 无需实现接口并重写方法,可以直接对目标类进行代理.
- 在创建代理对象时不需要传递接口或类类型,相对于 JDK 动态代理更加灵活.
- CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效
缺点
- 无法代理
final
类和方法:由于cglib是通过继承来实现代理的,因此无法代理final类和方法. - 性能较差:相比于JDK动态代理,cglib的性能较差.这主要是由于cglib是通过创建目标类的子类来实现代理的,而创建子类的过程需要较多的时间和内存空间.
- 对于构造函数的处理:cglib无法代理没有无参构造函数的类.如果目标类没有无参构造函数,那么cglib会抛出异常.
- 对于私有方法和私有属性的处理:cglib无法访问和修改私有方法和私有属性,因为它是通过继承来实现代理的.
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HoleLin's Blog!