上篇文章我们写了静态代理设计模式之代理模式【1】静态代理
今天说一下java的动态代理。
其实动态代理是弥补了静态代理的短板,静态代理需要给每一个被代理对象写一个代理类,这就需要编写大量冗余代码,而动态代理,只需要编写一个代理类,就可以代理多个不同的对象,大大减少重复代码,前提是他们实现了同一个接口。
请记住,代理的主要目的是控制对目标对象的访问,而不是增强目标对象的功能。访问控制包括同步,身份验证,远程访问(RPC),懒惰实例化(Hibernate,Mybatis),AOP(事务)。
首先我们设定一个场景,比如我要去相亲约会,约会对象是小花,但是我无法直接联系到小花,我需要通过媒婆介绍才能和小花约会。
这时候,小花就是被代理对象,媒婆就是代理对象,我是程序的发起人。
约会接口:两个功能,看电影和吃午饭
package com.example.demo1.Dynamic;
//定义接口 约会
public interface YueHui {
//看电影
void movie();
//吃午饭
void lunch();
}
小花:实现约会接口
/**
* 对象小花
* 需实现约会接口
* create by c-pown on 2020-06-29
*/
public class XiaoHua implements YueHui {
@Override
public void movie() {
System.out.println("和小花看电影");
}
@Override
public void lunch() {
System.out.println("和小花看吃午饭");
}
}
代理对象(媒婆)核心:里面实现了InvocationHandler接口并重写了invok方法,通过反射调用了对象方法,并可以再上下文进行增强操作。
getObj()
方法可以返回一个接口实现对象。
/**
* 代理对象 媒婆
* 需要实现InvocationHandler接口
* create by c-pown on 2020-06-29
*/
public class ProxyPerson implements InvocationHandler {
//需要被代理的对象(泛指)
private Object object;
//创建构造器
public ProxyPerson(Object object) {
this.object = object;
}
// 参数说明:
// proxy:动态代理对象(即哪个动态代理对象调用了method()
// method:目标对象被调用的方法
// args:指定被调用方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//此处可以对代理方法进行增强 比如:添加事务,前后包装等
System.out.println("先征求家长同意");
method.invoke(object,args);
System.out.println("向家长汇报结果");
return null;
}
//返回接口实现
//此处 this::invok可以省略为 this,代表的是执行invok方法
public Object getObj(){
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this::invok);
}
}
测试主类:我们需要将被代理对象初始化进代理对象里面,然后直接返回接口对象,调用方法。
/**
* 测试动态代理
* create by c-pown on 2020-06-29
*/
public class TestDynamicProx {
public static void main(String[] args) {
//需要被代理的对象
XiaoHua xiaoHua = new XiaoHua();
//将需要被代理对象,传递给代理对象
ProxyPerson proxyPerson = new ProxyPerson(xiaoHua);
//返回接口实现
YueHui yueHui = (YueHui)proxyPerson.getObj();
yueHui.lunch();
yueHui.movie();
}
}
执行结果:
先征求家长同意
和小花看吃午饭
向家长汇报结果
先征求家长同意
和小花看电影
向家长汇报结果
实现了代理并完成了增强。
如果我和小花双方都不满意咋办,媒婆又给我介绍了一个新的女生,小静。
这时候我们就不需要重新为小静写一个代理类了,只需要小静实现约会接口即可;
/**
* 新的对象小静
* 需实现约会接口
* create by c-pown on 2020-06-29
*/
public class XiaoJing implements YueHui {
@Override
public void movie() {
System.out.println("和小静看电影");
}
@Override
public void lunch() {
System.out.println("和小静看吃午饭");
}
}
同一个代理对象可以多次代理不同对象。
package com.example.demo1.Dynamic;
/**
* 测试动态代理
* create by c-pown on 2020-06-29
*/
public class TestDynamicProx {
public static void main(String[] args) {
//需要被代理的对象
XiaoHua xiaoHua = new XiaoHua();
//将需要被代理对象,传递给代理对象
ProxyPerson proxyPerson = new ProxyPerson(xiaoHua);
//返回接口实现
YueHui yueHui = (YueHui)proxyPerson.getObj();
yueHui.lunch();
yueHui.movie();
System.out.println("-------------分割线-------------");
//需要被代理的对象
XiaoJing xiaoJing = new XiaoJing();
//将需要被代理对象,传递给代理对象
ProxyPerson proxyPerson1 = new ProxyPerson(xiaoJing);
//返回接口实现
YueHui yueHui1 = (YueHui)proxyPerson1.getObj();
yueHui1.lunch();
yueHui1.movie();
}
}
结果:
先征求家长同意
和小花看吃午饭
向家长汇报结果
先征求家长同意
和小花看电影
向家长汇报结果
-------------分割线-------------
先征求家长同意
和小静看吃午饭
向家长汇报结果
先征求家长同意
和小静看电影
向家长汇报结果
好啦,后边不管有多少女生,小美,小芳,一个媒婆就够啦。
JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。
使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
对象小美:此时这里是么有实现任何接口的。
/**
* 对象小美
* create by c-pown on 2020-06-29
*/
public class XiaoMei {
public String name;
public XiaoMei() {
}
public XiaoMei(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void movie(){
System.out.println("和小美看电影");
}
}
代理类:通过实现MethodInterceptor接口,并通过Enhancer创建了一个新类,并继承了目标类,通过intercept方法重写了父类的方法,也可以实现动态代理的效果。
package com.example.demo1.Dynamic;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* cglib 动态代理类
* create by c-pown on 2020-06-29
*/
public class CglibInterceptor implements MethodInterceptor {
//被代理对象
private Object object;
public CglibInterceptor(Object object) {
this.object = object;
}
public Object getObj(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.object.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("先征求家长同意");
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("向家长汇报结果");
return o1;
}
}
XiaoMei xiaoMei = new XiaoMei("小美");
System.out.println(xiaoMei.getName());
CglibInterceptor cglibInterceptor = new CglibInterceptor(xiaoMei);
XiaoMei obj = (XiaoMei)cglibInterceptor.getObj();
obj.movie();
结果:
先征求家长同意
和小美看电影
向家长汇报结果
也可以实现代理。
注意:CGLIB代理是在方法层面进行代理,是无法获取代理对象的属性的。例如:
/**
* 对象小美
* create by c-pown on 2020-06-29
*/
public class XiaoMei {
public String name;
public XiaoMei() {
}
public XiaoMei(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void movie(){
System.out.println("和"+name+"看电影");
}
}
我们将小美里面的movie方法添加name属性获取,为我们初始化的小美。
在调用一次:
小美
先征求家长同意
和null看电影
向家长汇报结果
发现name的值是null,说明CGLIB动态代理,不能够代理目标对象的属性。