关于代理模式的介绍,以及动态代理(jdk、cglib)的介绍
代理模式
简要介绍
代理,就是代理人
当A想找B做一件事,但是可能B在十万八千里之外,联系不上,但是C又能联系上B,而且A也能找到C,所以A找C办这件事,但真正做事的人是B,C只是个代理人
一句话:代理人可以控制客户端对其他对象的访问
代理种类:
- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
哪里用到这个模式了?
- 使用过的一些中间件例如;
RPC
框架,在拿到jar包对接口的描述后,中间件会在服务启动的时候生成对应的代理类,当调用接口的时候,实际是通过代理类发出的socket信息进行通过。 - 另外像我们常用的
MyBatis
,基本是定义接口但是不需要写实现类,就可以对xml
或者自定义注解里的sql
语句进行
类图
在上图中,Client就是A,Proxy就是C,RealSubject就是B,也就是真正做事的人
例子
定义一个接口叫HelloService
,然后定义一个sayHello
方法,我们通过代理来调用这个方法,来实现在目标对象执行sayHello
方法的前后记录日志
HelloService
1 2 3 4 5 6 7
| public interface HelloService {
void say(); }
|
实现类(具体做事的人):HelloServiceImpl
1 2 3 4 5 6
| public class HelloServiceImpl implements HelloService{ @Override public void say() { System.out.println("hello!"); } }
|
代理:HelloServiceProxy
可以看到我们就是在这里直接调用真正做事的人执行方法,并且前后还可以加额外的东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class HelloServiceProxy implements HelloService{
private final HelloService target;
public HelloServiceProxy(HelloService target) { this.target = target; }
@Override public void say() { System.out.println("记录日志"); target.say(); System.out.println("清理数据"); } }
|
客户端:Main
1 2 3 4 5 6 7 8 9
| public class Main { public static void main(String[] args) { HelloService target = new HelloServiceImpl(); HelloServiceProxy helloServiceProxy = new HelloServiceProxy(target); helloServiceProxy.say(); } }
|
执行结果:
缺点
经过上面的介绍,我们可以发现,一个代理类只能为一个接口服务,平时开发是有N多个接口的,肯定会产生很多的代理类的,所以我们就会想,有没有可能一个代理类,可以完成所有代理的功能,所以就有了动态代理
动态代理
JDK
JDK
为我们提供了动态代理的支持,我们的代理类需要实现java.lang.reflect.InvocationHandler
接口并且调用java.lang.reflect.Proxy
类的newProxyInstance
方法创建一个代理实例,然后调用具体的方法,其实就是通过反射生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler
来处理
在上述代码基础上加入MyInvocationHandler
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class MyInvocationHandler implements InvocationHandler {
private final Object target;
public MyInvocationHandler(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(target.getClass().getName() + "." + method.getName()); System.out.println("记录日志"); Object result = method.invoke(target, args); System.out.println("清理数据"); return result; }
public Object getProxy() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
|
关于Proxy.newProxyInstance
方法参数的说明:
- 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
- 第一个参数指定产生代理对象的类加载器(就是要为谁加代理,就传谁的class),需要将其指定为和目标对象同一个类加载器
- 第二个参数要实现和目标对象一样的接口(可以看出jdk的动态代理,其实就是把我们手动创建和代理对象相同接口的代理类自动化了),所以只需要拿到目标对象的实现接口
- 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
下面使用一下这个动态代理,看看好使不:
1 2 3 4 5 6 7 8 9
| public class Main { public static void main(String[] args) { HelloService target = new HelloServiceImpl(); MyInvocationHandler invocationHandler = new MyInvocationHandler(target); HelloService proxy = (HelloService) invocationHandler.getProxy(); proxy.say(); } }
|
结果:
1 2 3 4
| com.hc.basics.dynamicproxy.jdk.HelloServiceImpl.say 记录日志 hello! 清理数据
|
cglib
jdk动态代理的局限性:只能对实现了接口的类进行代理,对于没有实现接口的类无法代理
cglib对于没有实现接口的类也可以进行代理
这个是咋动态生成代理的呢?他利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
cglib
需要引入依赖:
1 2 3 4 5 6
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
|
在上述基础,再加一个CglibProxyFactory
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class CglibProxyFactory implements MethodInterceptor {
private final Enhancer enhancer = new Enhancer();
private final Object target;
public CglibProxyFactory(Object target) { this.target = target; }
public Object getProxy() { enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); }
@Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(target.getClass().getName() + "." + method.getName()); System.out.println("记录日志"); Object result = method.invoke(target, args); System.out.println("清理数据"); return result; } }
|
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Main { public static void main(String[] args) { HelloService helloService = new HelloServiceImpl(); CglibProxyFactory helloServiceProxyFactory = new CglibProxyFactory(helloService); HelloService helloServiceProxy = (HelloService) helloServiceProxyFactory.getProxy(); helloServiceProxy.say(); Student student = new Student(); CglibProxyFactory studentProxyFactory = new CglibProxyFactory(student); Student studentProxy = (Student) studentProxyFactory.getProxy(); studentProxy.study(); } }
|
1 2 3 4 5 6 7 8
| com.hc.basics.dynamicproxy.jdk.HelloServiceImpl.say 记录日志 hello! 清理数据 com.hc.basics.dynamicproxy.cglib.Student.study 记录日志 student study! 清理数据
|