一、为什么要引入动态代理
已经学习过静态代理:静态代理
假设现在项目经理有一个需求:在项目现有所有类的方法前后打印日志
如何在不修改已有代码的前提下,完成这个需求?
我首先想到的是静态代理。具体做法是:
(1)为现有的每一个类都编写一个对应的代理类,并且让它实现和目标类相同的接口(假设都有)
(2)代理角色中需要引入真实角色,也就是:代理对象=增强代码+目标对象(原对象)
静态代理的缺陷:程序员要手动为每一个目标类编写对应的代理类。如果当前系统已经有成百上千个类,工作量太大了
所以,现在我们的努力方向是:如何少写或者不写代理类,却能完成代理功能?
二、动态代理
每一个代理类只能为一个接口服务,如果要想通过一个代理完成全部的代理功能,此时就必须使用动态代理功能。
动态代理实现的是方法的增强,这里之所以可以完成全部的代理功能,是因为这些需要代理的类需要增强的方法是相同的,比如小明买房需要个中介(中介相当于代理),小红,小花买房这个行为都可以交由一个同一个中介。
创建对象的过程:
换个思路:
所谓的Class对象,是Class类的实例,而Class类是描述所有类的,比如Person类,Student类
因为创建一个对象的过程:我们编写的class类被编译成字节码文件,字节码文件被类加载器进内存生成class对象,最后由这个class对象创建实例
所以,要创建一个实例,最关键的就是得到对应的Class对象
Class对象包含了一个类的所有信息,比如构造器、方法、字段等。
所以可以利用 反射 不写代理类,而直接得到代理Class对象,然后根据它创建代理实例
这种情况需要代理类和目标类理应实现同一组接口
之所以实现相同接口,是为了尽可能保证代理对象的内部结构和目标对象一致,这样我们对代理对象的操作最终都可以转移到目标对象身上,代理对象只需专注于增强代码的编写
所以,可以这样说:接口拥有代理对象和目标对象共同的类信息。所以,我们可以从接口那得到理应由代理类提供的信息。
这也是动态代理的约束:目标对象一定是要有接口的,没有接口就不能实现动态代理(当然也可以借助 cglib代理解决)
但是别忘了,接口是无法创建对象的,怎么办?
Java 中的实现动态代理机制解决了这个问题,需要 java.lang.reflect.InvocationHandler
接口和 java.lang.reflect.Proxy
类的支持。
Proxy有个静态方法:getProxyClass(ClassLoader, interfaces)
,只要你给它传入类加载器和一组接口,它就给你返回代理Class对象。
用通俗的话说,getProxyClass()
这个方法,会从你传入的接口Class中,“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创建对象的。
也就是一旦明确接口,就可以通过接口的Class对象,创建一个代理Class,然后通过代理 Class 即可创建代理对象
Proxy.getProxyClass()
这个方法的本质就是:以Class造Class
静态代理:
动态代理:
具体的:
其实无论是静态代理还是动态代理本质都是最终生成代理对象,区别在于静态代理对象需要人手动生成,而动态代理对象是运行时,JDK通过反射动态生成的代理类最终构造的对象,JDK生成的类在加载到内存之后就删除了,所以看不到类文件
有了 Class 对象:
根据代理Class的构造器创建对象时,需要传入InvocationHandler。每次调用代理对象的方法,最终都会调用InvocationHandler的invoke()方法:
根据代理Class的构造器创建对象时,需要传入InvocationHandler。通过构造器传入一个引用,那么必然有个成员变量去接收。没错,代理对象的内部确实有个成员变量invocationHandler,而且代理对象的每个方法内部都会调用handler.invoke()!
InvocationHandler对象成了代理对象和目标对象的桥梁,不像静态代理这么直接。
大家仔细看上图右侧的动态代理,我在invocationHandler的invoke()方法中并没有写目标对象。因为一开始invocationHandler的invoke()里确实没有目标对象,需要我们手动new。
但这种写法不够优雅,属于硬编码。我这次代理A对象,下次想代理B对象还要进来改invoke()方法,太差劲了。改进一下,让调用者把目标对象作为参数传进来:
作者:bravo1988
链接:https://www.zhihu.com/question/20794107/answer/658139129
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
public class ProxyTest {
public static void main(String[] args) throws Throwable {
CalculatorImpl target = new CalculatorImpl();
//传入目标对象
//目的:1.根据它实现的接口生成代理对象 2.代理对象调用目标对象方法
Calculator calculatorProxy = (Calculator) getProxy(target);
calculatorProxy.add(1, 2);
calculatorProxy.subtract(2, 1);
}
private static Object getProxy(final Object target) throws Exception {
//参数1:随便找个类加载器给它, 参数2:目标对象实现的接口,让代理对象实现相同接口
Class proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
Object proxy = constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法开始执行...");
Object result = method.invoke(target, args);
System.out.println(result);
System.out.println(method.getName() + "方法执行结束...");
return result;
}
});
return proxy;
}
}
这样就非常灵活,非常优雅了。无论现在系统有多少类,只要你把实例传进来,getProxy()都能给你返回对应的代理对象。
就这样,我们完美地跳过了代理类,直接创建了代理对象!
不过实际编程中,一般不用getProxyClass(),而是使用Proxy类的另一个静态方法:Proxy.newProxyInstance(),直接返回代理实例,连中间得到代理Class对象的过程都帮你隐藏
总结:
1. Proxy 类
Proxy 类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,Proxy 类提供了如下的操作方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
- 1
- 2
通过 newProxyInstance()
方法可以动态地生成实现类:
取得类加载器:
class Person{}
public class Root{
public static void main(String[] args) {
Person stu = new Person();//实例化子类对象
System.out.println("类加载器:" +
stu.getClass().getClassLoader().getClass().getName());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
对于Proxy 类需要传入,类加载器,接口,InvocationHandler 接口的子类实例
2. InvocationHandler 接口
根据代理Class的构造器创建对象时,需要传入InvocationHandler
。每次调用代理对象的方法,最终都会调用InvocationHandler
的invoke()
方法:
InvocationHandler 接口的定义:
public interface InvocationHandler{
public Object invoke(Onject proxy,Method method,Object[] args)
throws Throwable
}
- 1
- 2
- 3
- 4
该接口定义了一个 invoke()
方法,含有三个参数:
invoke 的具体学习可参考:动态代理 InvocationHandler 和 Invoke
三、示例操作
如果要完成动态代理,首先要定义一个 InvocationHandler 接口的子类,以完成代理的具体操作。
- 定义 MyInvocationHandler 的类
class MyInvocationHandler implements InvocationHandler{
private Object obj;//真实主题
public Object bind(Object obj){//绑定真题操作主题
this.obj = obj;
//取得代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),this);//this 指的是MyInvocationHandler
}
//动态调用方法
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable{
System.out.println("打印日志 !!!")
Object temp = method.invoke(this.obj,args);//调用方法,传入真实的主题和参数
return temp;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
这里 MyInvocationHandler 类的 bind()
方法中接受被代理的真实主题实现,之后覆写 InvocationHandler
接口中的 invoke()
方法
- 定义接口
interface Subject{//定义 Subject 接口
public String say(String name,int age);//定义抽象方法 say
}
- 1
- 2
- 3
- 定义真实主题实现类
//定义真实主题实现类
class RealSubject implements Subject{//真实实现类
public String say(String name,int age){//覆写 say() 方法
return "姓名:" + name + ",年龄:" + age;//返回信息
}
}
- 1
- 2
- 3
- 4
- 5
- 6
这里定义了接口和真实主题类,这样在操作时直接将真实主题类的对象传入到 MyInvocationHandler 类的 bind()
方法中即可。
- 测试动态代理
public class Test{
public static void main(String[] args) {
MyInvocationHandler handler = new MyInvocationHandler();//实例化代理操作类
Subject sub = (Subject)handler.bind(new RealSubject());//绑定对象
String info = sub.say("Java",30);//通过动态代理调用方法
System.out.println(info);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
四、代理 Class 和代理对象
单从名字看,代理Class和Calculator的接口确实相去甚远,但是我们却能将代理对象赋值给接口类型:
千万别觉得名字奇怪,就怀疑它不能用接口接收,只要实现该接口就是该类型。
代理对象的本质就是:和目标对象实现相同接口的实例。代理Class可以叫任何名字,whatever,只要它实现某个接口,就能成为该接口类型。
我写了一个MyProxy类,那么它的Class名字必然叫MyProxy。但这和能否赋值给接口没有任何关系。由于它实现了Serializable和Collection,所以myProxy(代理实例)同时是这两个接口的类型。
五、cglib 代理
参考:Java3y
1. 介绍
由于静态代理需要实现目标对象的相同接口,那么可能会导致代理类会非常非常多,不好维护,
因此出现了动态代理
动态代理也有个约束:目标对象一定是要有接口的,没有接口就不能实现动态代理,因此出现了cglib代理
cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能
CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)
2. 编写 cglib 代理
(1)需要引入cglib – jar文件, 但是spring的核心包中已经包括了cglib功能,所以直接引入spring-core-3.2.5.jar即可。
(2)引入功能包后,就可以在内存中动态构建子类
(3)代理的类不能为final,否则报错【在内存中构建子类来做扩展,当然不能为final,有final就不能继承了】
(4)目标对象的方法如果为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。
//需要实现MethodInterceptor接口
public class ProxyFactory implements MethodInterceptor{
// 维护目标对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
// 给目标对象创建代理对象
public Object getProxyInstance(){
//1. 工具类
Enhancer en = new Enhancer();
/https://files.jxasp.com/image/2. 设置父类
en.setSuperclass(target.getClass());
//3. 设置回调函数
en.setCallback(this);
//4. 创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("开始事务.....");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务.....");
return returnValue;
}
}
- 33
- 34
- 35
测试:
public class App {
public static void main(String[] args) {
UserDao userDao = new UserDao();
UserDao factory = (UserDao) new ProxyFactory(userDao).getProxyInstance();
factory.save();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
使用 cglib 主要就是为了弥补动态代理的不足【动态代理的目标对象一定要实现接口】
六、面试题
【1】CGLIB动态代理的使用步骤是什么?