[关闭]
@adamhand 2018-11-10T23:13:14.000000Z 字数 4490 阅读 721

Java--代理


一、概念

给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问。

代理类的应用场景很多,最容易想到的情况就是权限过滤,我有一个类做某项业务,但是由于安全原因只有某些用户才可以调用这个类,此时我们就可以做一个该类的代理类,要求所有请求必须通过该代理类,由该代理类做权限判断,如果安全则调用实际类的业务开始处理。

还有就是Spring的AOP,利用代理实现切面编程,将不同业务逻辑中相同功能的代码提取出来,减少了耦合。

Java里实现代理有两种方式:静态代理动态代理。动态代理又可以使用JDKProxy或者CGLib的方式实现。

二、静态代理

静态代理的特点就是,代理和被代理对象在代理之前就是已经确定好的,它们都实现共同的接口或者继承相同的抽象类。

静态代理通常用于对原有业务逻辑的扩充。比如要在原有类的基础上加上日志功能,但又不能修改袁石磊,就可以使用代理模式,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加所需要业务逻辑的目的。有点类似于装饰模式

一个典型的代理模式通常有三个角色,这里称之为代理三要素共同接口真实对象代理对象

共同接口

一个Hello接口。

  1. public interface Hello {
  2. void say(String name);
  3. }

真实对象

实现了Hello接口。

  1. public class HelloImp implements Hello {
  2. @Override
  3. public void say(String name) {
  4. System.out.println("Hello" + name);
  5. }
  6. }

代理对象

写一个 HelloProxy 类,让它去调用 HelloImpl 的 say() 方法,在调用的前后分别进行逻辑处理

  1. public class HelloProxy implements Hello{
  2. private HelloImp helloImp;
  3. public HelloProxy(){
  4. helloImp = new HelloImp();
  5. }
  6. @Override
  7. public void say(String name) {
  8. before();
  9. helloImp.say(name);
  10. after();
  11. }
  12. private void before(){
  13. System.out.println("Before");
  14. }
  15. private void after(){
  16. System.out.println("After");
  17. }
  18. }

测试类和结果如下:

  1. public static void main(String[] args) {
  2. HelloProxy helloProxy = new HelloProxy();
  3. helloProxy.say("Jack");
  4. }
  5. Before
  6. HelloJack
  7. After

三、动态代理

Java实现动态代理的方式有两种:使用JDKProxy或使用CGLib类库。使用JDKProxy代理的类必须实现一个接口,CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。

JDKProxy使用反射机制实现,CGLib使用ASM实现。

JDKProxy

使用JDKProxy实现的动态代理主要包含以下三个元素:

  • 业务接口(Interface)
    业务的抽象表示
  • 业务具体实现类(concreteManager)
    实现业务接口,执行具体的业务操作
  • 业务代理类(proxyHandler,实现了InvocationHandler接口的类)
    代理方法的直接调用者,通过InvocationHandler中的invoke方法直接发起代理
  • 客户端调用对象(client)
    发起业务

实现步骤如下:

  • 创建被代理的接口和类;
  • 创建一个类实现InvocationHandler接口,实现其中的invoke()方法;
  • 调用Proxy的静态方法,创建一个代理类:newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHander h);
  • 通过代理调用方法;

还是基于上面的Hello接口和HelloImp类。下面创建一个实现InvocationHandler接口的方法DynamicProxy,在 DynamicProxy 类中,定义了一个 Object 类型的 target 变量,它就是被代理的目标对象,通过构造函数来初始化。

DynamicProxy 实现了 InvocationHandler 接口,那么必须实现该接口的 invoke 方法,参数见注释。在该方法中,直接通过反射去 invoke method,在调用前后分别处理 before 与 after,最后将 result 返回。

接着,在DynamicProxy 里添加了一个 getProxy() 方法,在里面调用 JDK 给我们提供的 Proxy 类的工厂方法 newProxyInstance() 去动态地创建一个 Hello 接口的代理类,最后调用这个代理类的 say() 方法。这样可以简化主函数中的代码。

@SuppressWarnings("unchecked") 注解表示忽略编译时的警告(因为 Proxy.newProxyInstance() 方法返回的是一个 Object,这里我强制转换为 T 了,这是向下转型,IDE 中就会有警告,编译时也会出现提示。

  1. public class DynamicProxy implements InvocationHandler {
  2. //被代理的对象
  3. private Object target;
  4. public DynamicProxy(Object target){
  5. this.target = target;
  6. }
  7. /**
  8. * @param proxy:被代理的对象
  9. * @param method:被代理对象的方法
  10. * @param args:方法的参数
  11. * @return
  12. * @throws InvocationTargetException
  13. * @throws IllegalAccessException
  14. */
  15. @Override
  16. public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
  17. before();
  18. Object result = method.invoke(target, args);
  19. after();
  20. return result;
  21. }
  22. /**
  23. * Proxy.newProxyInstance的三个参数:
  24. * 1. 被代理类的类加载器
  25. * 2. 该实现类的所有接口
  26. * 3. 动态代理对象
  27. * @param <T>
  28. * @return
  29. */
  30. @SuppressWarnings("unchecked")
  31. public <T> T getProxy(){
  32. return (T) Proxy.newProxyInstance(
  33. target.getClass().getClassLoader(),
  34. target.getClass().getInterfaces(),
  35. this
  36. );
  37. }
  38. private void before(){
  39. System.out.println("Before");
  40. }
  41. private void after(){
  42. System.out.println("After");
  43. }
  44. }

测试方法和结果如下:

  1. public static void main(String[] args) {
  2. DynamicProxy dynamicProxy = new DynamicProxy(new HelloImp());
  3. Hello hello = dynamicProxy.getProxy();
  4. hello.say("Jack");
  5. }
  6. Before
  7. HelloJack
  8. After

CGLib

Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:

cglib有两种可选方式,继承引用。第一种是基于继承实现的动态代理,所以可以直接通过super调用target方法,但是这种方式在spring中是不支持的,因为这样的话,这个target对象就不能被spring所管理,所以cglib还是才用类似jdk的方式,通过持有target对象来达到拦截方法的效果。

使用CGLib实现代理的步骤如下:

  • 创建被代理的接口和类;
  • 实现 CGLib 提供的 MethodInterceptor 实现类,并填充 intercept() 方法;
  • 通过Enhancer类产生一个代理类;
  • 通过代理调用方法;
  1. public class CGLibProxy implements MethodInterceptor {
  2. private static CGLibProxy instance = new CGLibProxy();
  3. private CGLibProxy(){}
  4. public <T> T getProxy(Class<T> cls){
  5. return (T) Enhancer.create(cls, this);
  6. }
  7. /**
  8. * @param o:目标类的实例
  9. * @param method:目标方法的反射对象
  10. * @param objects:目标方法的参数
  11. * @param methodProxy:代理类的实例
  12. * @return
  13. * @throws Throwable
  14. */
  15. @Override
  16. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  17. before();
  18. Object restult = methodProxy.invokeSuper(o, objects);
  19. after();
  20. return restult;
  21. }
  22. public static CGLibProxy getInstance(){
  23. return instance;
  24. }
  25. private void before(){
  26. System.out.println("Before");
  27. }
  28. private void after(){
  29. System.out.printf("After");
  30. }
  31. }

在上述代码中,使用了单例模式,简化了主函数中的代码;

主函数和结果如下:

  1. public static void main(String[] args) {
  2. HelloImp helloImp = CGLibProxy.getInstance().getProxy(HelloImp.class);
  3. helloImp.say("Jack");
  4. }
  5. Before
  6. HelloJack
  7. After

参考

Proxy那点事
Java动态代理实现及原理分析
CGLIB介绍与原理
静态代理和动态代理的理解
java动态代理原理及解析
详解java动态代理机制以及使用场景(一)
Java动态代理的两种实现方法
Java设计模式之动态代理
CGLib之Enhancer
代理8 cglib demo以及Enhancer源码解析
基于 JDK 的动态代理-佟刚老师《Spring4视频教程》学习笔记(16)
java的动态代理机制详解
JAVA动态代理

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注