Spring/Boot/Cloud系列知识(3)——代理模式(中)

版权声明:欢迎转载,但是看在我辛勤劳动的份上,请注明来源:http://blog.csdn.net/yinwenjie(未经允许严禁用于商业用途!) https://blog.csdn.net/yinwenjie/article/details/78008276

(接上文《Spring/Boot/Cloud系列知识(2)——代理模式》)

2.2、代理模式在Spring中的应用

那么java中基于java.lang.reflect.Proxy的动态代理模式和Spring生态有什么关系呢?Spring中的所有Bean实例存储在一个名叫IOC容器(Inversion of Control 控制反转容器)中。这个容器中存在着接口和实现类的对应关系(也可以直接存储类的实例,无需这个类有任何接口的实现),而其中Bean实例的存储方式都默认采用单例保存。

一般情况下我们可以通过BeanFactory(Spring IOC容器工厂)接口中getBean()方法,直接获取到这个接口在IOC容器中对应的实例。但当我们需要为这个Bean实例附加AOP切面操作时,这个实例就会被代理——视这个实例实现接口的情况和Spring的配置情况,又可以区别为使用Proxy动态代理还是使用Cglib代理。如下图所示:

  • 通常情况下当我们使用IOC容器中的Bean实例时,IOC容器将向使用者返回这个接口的具体实现,并不会代理这个Bean实例。如下图所示:

这里写图片描述

  • 当我们为这个Bean设定了AOP切面功能后,这个IOC容器中的Bean实例就会被代理。以下设定了一个AOP切面拦截(AOP切面的详细讲解在后续文章中进行,这里我们只看被代理的结果就好):
// 创建一个AOP切面拦截器
@Aspect
@Component
public class MyInterceptor {
  /** 
   * 拦截yinwenjie.test.proxy.service包下面的所有类中的所有方法
   * 后续文章还会详细讲解Spring EL和AOP拦截规则 
   */    
  @Pointcut("execution(* yinwenjie.test.proxy.service..*.*(..))")  
  public void executeService() {
    System.out.println("拦截器作用!");
  }
  // 前置事件
  @Before("executeService()")  
  public void doBeforeAdvice(JoinPoint joinPoint){
    System.out.println("前置事件!");
  }
}
  • 接着我们重新执行单元测试(重新运行代码),就会发现“MyService”接口的实现——MyServiceImpl被代理了。代理者是Spring基础设施中aop模块的JdkDynamicAopProxy类。

这里写图片描述

org.springframework.aop.framework.JdkDynamicAopProxy类非常重要,它实现了Java对动态代理模式的支持,既实现了java.lang.reflect.InvocationHandler接口(当然不止是实现了这个接口)。注意:需要设定您的Spring主配置文件(这里是Spring Boot)中spring.aop.proxy-target-class=false,否则就算您依赖注入的Bean存在接口,也会使用Cglib代理。

3. Cglib代理(动态)

要使用java对动态代理的原生支持,就需要被代理的类至少实现了一个接口。但如果某个需要被代理的类没有实现任何接口,又怎么办呢?这时一个比较好的选择就是使用Cglib代理,Cglib代理并不是java的原生代理模式,而是由第三方提供的一种代理方式。Cglib代理组件是Mockito Framework的一部分,其内部封装了一个java字节码生成框架ASM:

ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or dynamically generate classes, directly in binary form. Provided common transformations and analysis algorithms allow to easily assemble custom complex transformations and code analysis tools.

以上引用自OW2组织对ASM的描述(http://asm.ow2.org)。简单来说就是ASM字节码框架可在运行时动态生成Class字节码内容,或者扩展已有的某个Class。实际上Spring生态中默认使用Cglib动态代理作为主要的动态代理方式——不是java原生的java.lang.reflect.Proxy动态代理。无论被代理的Bean实例是否实现了任何接口,Cglib都能更好胜任代理工作任务。

3.1 Cglib组件基本使用

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
</dependency>

在本小节的示例讲解中,我们需要在工程的maven文件中导入Cglib组件的引用(1.10.19版本)。接下来我们可以使用一个示例,来看看Cglib组件的基本使用。Cglib组件中的基本角色包括:

  • 代理器:代理器实现了org.mockito.cglib.proxy.MethodInterceptor接口,当代理者被外部使用者调用时,就会触发其中的intercept方法。在本示例中,我们创建了两个代理器DefaultProxy和CglibProxy:

    • DefaultProxy代理器
    // 默认的代理器,什么都不执行
    public class DefaultProxy implements MethodInterceptor {
      @Override
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 这里不做任何处理(也可以直接使用proxy执行父级代码)
        return null;
      }
    }
    • CglibProxy代理器
    /**
     * 这是一个基于Cglib的代理器
     * 它实现了org.mockito.cglib.proxy.MethodInterceptor接口
     * @author yinwenjie
     */
    public class CglibProxy implements MethodInterceptor {
      // 日志
      private static final Logger LOG = LoggerFactory.getLogger(CglibProxy.class);
      /**
       * 该方法在代理者被调用时触发
       * @param obj 这是代理者,按照本示例就是使用Enhancer生成的子级类
       * @param method 被调用的方法
       * @param args 调用方法时可能传入的参数
       * @param proxy cglib内部的方法代理,可以用它来执行真正的业务过程
       */
      @Override
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        LOG.info("通过debug观察Enhancer生成的子级类,是否有似曾相识的感觉?");
        LOG.info("被调用的方法:" + method.getName());
        LOG.info("可以在正式调用前执行一些额外的过程");
        // 这里返回真正的业务调用过程
        Object result = null;
        try {
          result = proxy.invokeSuper(obj, args);
        } catch (Exception e) {
          LOG.error(e.getMessage() , e);
          LOG.info("在调用执行异常后,执行一些额外的过程");
          return null;
        }
        LOG.info("在调用执行成功后,执行一些额外的过程");
        return result;
      }
    }
  • 被代理者:被代理者就是负责真正业务过程处理的类。本示例中我们创建了一个TargetClass类,作为真正业务的处理者

/**
 * 这是要被代理的业务处理类<br>
 * 这个类既没有任何父类,也没有实现任何接口
 * @author yinwenjie
 */
public class TargetClass {
  // 日志
  private static final Logger LOG = LoggerFactory.getLogger(TargetClass.class);
  public void handleSomething(String param) {
    LOG.info("传入了一个参数:" + param + ",做了一些业务处理");
  }
}
  • 字节码增强操作器Enhancer

    Enhancer是Cglib中用于动态生成java字节码的最重要工具,其内部是对ASM框架的封装。以下示例是Enhancer的基本使用:

public class Run {
  static {
    BasicConfigurator.configure();
  }
  public static void main(String[] args) {
    /*
     * Cglib的核心思路,是依靠一个java字节码操作框架ASM
     * 在代码运行时,为被代理的Class生成扩展代码(子级代码)
     * 然后通过运行子级代码,实现代理
     * 
     * Enhancer是其中用来进行代码生成的主要类,请看如下运行示例
     * */

    Enhancer enhancer = new Enhancer();
    // 基于被代理的TargetClass,为它创建一个子类进行代理执行
    enhancer.setSuperclass(TargetClass.class);
    // 设置代理器,这里设置了两个代理器DefaultProxy和CglibProxy(这里也可以传null)
    enhancer.setCallbacks(new Callback[]{new DefaultProxy() , new CglibProxy()});
    // 设置过滤器(很多资料都写错了用法,请参见本人MethodFilter中的注释)
    enhancer.setCallbackFilter(new MethodFilter());

    // 在运行时动态创建代理类(TargetClass类的子类)
    TargetClass proxy = (TargetClass)enhancer.create();

    // 以下方法将被DefaultProxy代理
    proxy.toString();
    // 以下方法将被CglibProxy代理
    proxy.handleSomething("做一些对社会有益的事情!");
  }
  ……
}
  • 其它的角色——代理过滤器
    代理过滤器CallbackFilter的主要工作,是根据当前外部使用者所调用的代理者的方法,来决定使用哪一个代理器:
public class Run {
  ……
  static class MethodFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
      /*
       * 在main方法中,我们一共设置了两个代理器,DefaultProxy和CglibProxy。
       * 当被执行的方法是Object类中的,类似clone、toString、hashCode这样的方法,则使用DefaultProxy进行代理
       * 其他的方法,就是我们真正需要被代理的业务方法了,使用CglibProxy这个代理器进行代理
       * 
       * 这里返回的结果,就是我们在main方法中使用enhancer.setCallbacks设定的代理器顺序
       * 返回0,表示使用DefaultProxy代理器;返回1,表示使用CglibProxy代理器
       * 
       * 代码就不解释了,很容易看懂。
       * 大家还可以参见org.mockito.cglib.proxy.Proxy.getProxyClass的源代码,后文中我们还会提到这个Proxy类
       */
      if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
        return 0;
      }
      return 1;
    }
  }
}
  • 下图展示了debug过程中,观察到的obj对象的结构效果。是否有似曾相似的感觉?

这里写图片描述

以下是示例代码的执行结果:

09:27:36.939 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 通过debug观察Enhancer生成的子级类,是否有似曾相识的感觉?
09:27:36.964 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 被调用的方法:handleSomething
09:27:36.964 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 在正式调用前执行一些额外的过程
09:27:36.998 [main] INFO yinwenjie.test.proxy.cglib.target.TargetClass - 传入了一个参数:做一些对社会有益的事情!,做了一些业务处理
09:27:36.998 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 在调用执行成功后,执行一些额外的过程

3.2 Cglib替代Java原生动态代理

Cglib组件中有类似java原生动态代理的实现方式,可以完整替代java中原生动态代理的支持。这使得Spring默认使用Cglib动态代理作为主要的动态代理方式有了基础支撑。本小节我们来看看Cglib如何提供类似java原生动态代理的支持(我们使用的Cglib版本还是1.10.19)。

  • 以下是接口和接口的实现类,也是我们需要进行进行代理的业务处理类。这里的代码示例已经在之前的文章中出现过,这里就不再赘述了:
// 被代理的接口
public interface TargetOneInterface {
  // 这是一个方法
  public void doSomething();
  // 这是第二个方法
  public void handleSomething();
}

......

// 接口实现,也是真正的业务处理类
public class TargetOneImpl implements TargetOneInterface {
  // 日志
  private static final Logger LOG = LoggerFactory.getLogger(TargetOneImpl.class);
  @Override
  public void doSomething() {
    LOG.info("doSomething 方法被执行");
  }
  @Override
  public void handleSomething() {
    LOG.info("handleSomething 方法被执行");
  }
}
  • 以下我们基于Cglib提供的InvocationHandler接口,实现的代理器。注意,在Java的原生动态代理支持中,也有这个名称相同的接口——包名不一样:
......
import org.mockito.cglib.proxy.InvocationHandler;
......

// 代理者处理器
public class ProxyInvocationHandler implements InvocationHandler {
  /**
   * 模拟一个简单的IOC容器<br>
   * 当然实际情况没有这么简单,IOC内部有一个组织结构的
   */
  private static Map<Class<?>, Object> simulatorContainer = new HashMap<>();

  static {
    // 这是TargetOneInterface的实现
    simulatorContainer.put(TargetOneInterface.class, new TargetOneImpl());
  }

  /**
   * @param proxy 代理对象,注意是代理者,不是被代理者
   * @param method 被代理的方法
   * @param args 被执行的代理方法中传入的参数
   */
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 被代理的接口
    Class<?> targetInterface = method.getDeclaringClass();
    // 从容器中取出执行类
    Object target = simulatorContainer.get(targetInterface);
    if(target == null) {
      return null;
    }
    // 开始调用
    return method.invoke(target, args);
  }
}

在以上这个代理器示例中,我们模拟了一个简易的IOC容器,并根据被代理的接口从这个简易的容器中取出接口对应的真实业务处理类,最后执行。

  • 以下是调用代码和执行结果
public class Run {

  static {
    BasicConfigurator.configure();
  }

  public static void main(String[] args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    //这是Cglib代理处理器
    ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
    Object proxy = Proxy.newProxyInstance(classLoader,new Class<?>[]{TargetOneInterface.class}, invocationHandler);

    // 开始调用(测试第一个接口)
    TargetOneInterface targetOne = (TargetOneInterface)proxy;
    targetOne.doSomething();
    targetOne.handleSomething();
  }
}

对以上代码进行debug操作,会发现类似如下的调试信息:

这里写图片描述

从调试信息可以看出,当我们调用org.mockito.cglib.proxy.Proxy.newProxyInstance这个静态方法时,一旦执行成功,该方法就将返回一个由Cglib组件动态生成的类。上图中表示为:这里写图片描述。后文内容中我们会阅读org.mockito.cglib.proxy.Proxy.newProxyInstance中的源代码,来讲解其中的工作内容。以下是示例的运行结果:

[main] INFO yinwenjie.test.proxy.cglib.dproxy.target.TargetOneImpl - doSomething 方法被执行
[main] INFO yinwenjie.test.proxy.cglib.dproxy.target.TargetOneImpl - handleSomething 方法被执行

=========================
(接后文,Spring中对Cglib的封装、Cglib中newProxyInstance实现过程等内容)

猜你喜欢

转载自blog.csdn.net/yinwenjie/article/details/78008276