走进Spring中AOP的世界(一)思想篇

662 阅读7分钟

前言

再写完《走进Spring中Bean的世界》,写这篇文章时,原本想法是把AOP的思想、原理以及源码统统囊入其中,最后发现会和上篇文章一样,读起来难以专注。所以还是本着跳出看全景,转进去看本质的原则,将AOP的文章分为三部分,分别为思想篇、源码篇、应用篇。

你可以了解到

  • Java程序在JVM中的运行流程
  • 面向切面编程思想
  • 代理模式的应用
  • Spring AOP的工作原理

Java程序在JVM中的运行流程

  • 先来了解下虚拟机栈(JVM stack)

    虚拟栈是一个后入先出(LIFO)栈。每一个线程创建时,JVM会为这个线程创建一个私有的虚拟机栈,当线程调用某个对象的方法时,JVM会相应地创建一个栈帧(Stack Frame)放到虚拟机中,用来表示某个方法的调用。线程对方法的调用就对应一个栈帧的入栈和出栈的过程

  • 来看段代码了解虚拟机栈
        public class TestMethod {
            public void method1() {
                StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            }
            public static void main(String[] args) {
                TestMethod target = new TestMethod();
                target.method1();
            }
        }
    
    上面代码我们在method()1时,打个断点。
    当前虚拟栈的信息可以如下图所示:
  • Java程序执行机制
    图中左面是方法执行顺序,对应右面是抽象的调用流程,其实程序执行机制,把方法当做连接点,串起来就是整个执行过程。

面向切面编程思想

上面把程序执行机制以连接点的概念进行了抽象,下面来了解下面向切面以此为基准的编程思想

  • 走进面向切面编程
    AOP将每一个方法调用抽象成连接点(Join Point),连接点串起来的程序执行流就是整个程序的执行过程,“按需”选择连接点进行切入,也就是切入点(Pointcut)。那么切入点如何确定呢?
  • 确定切入点
    切入点(Pointcut)确定,其实是根据切入点表达式来匹配该方法(连接点)是否满足。

代理模式的应用

上面了解到AOP是对方法调用进行编程,那么AOP如何捕获方法的调用的?其实AOP实现的基础是代理模式的应用。

  • 引入代理模式Java程序执行机制
    想访问目标对象的方法时,其实执行的是代理对象的“拦截方法”。下面来了解下Spring中代理模式的两种实现方式。
  • 代理模式
    • 几大角色
      • 抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法
      • 真实角色(TargetSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
      • 代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

    代理模式中,由抽象角色(Subject)、真实角色(TargetSubject)、代理角色(Proxy)。其中:抽象角色(Subject)负责定义真实角色(TargetSubject)和代理角色(Proxy)应该实现的接口;真实角色(TargetSubject)来完成真正的request功能;代理角色(Proxy)负责将自身的request请求调用真实角色(TargetSubject)对应的request功能来实现业务功能,自己不真正做业务,并在调用request前后,插入代码实现具体功能。

    • JDK动态代理
      • 实现:JDK动态代理会根据真实角色(TargetSubject)的所有接口列表,确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXX,然后根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码,将对应的字节码转换为对应的class 对象,创建InvocationHandler 实例handler,用来处理Proxy所有方法调用,Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象。
      • 代码实现:
        public interface Subject {
            public void request();
        }
        public class TargetSubject implements Subject {
            @Override
            public void request() {
                System.out.println("TargetSubject#request...");
            }
        }
        public class InvocationHandlerImpl implements InvocationHandler {
            private TargetSubject targetSubject;
            public InvocationHandlerImpl(TargetSubject targetSubject) {
                this.targetSubject = targetSubject;
            }
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("TargetSubject#request before...");
                method.invoke(targetSubject, null);
                System.out.println("TargetSubject#request after...");
                return null;
            }
        }
        public class Test001 {
            public static void main(String[] args) {
                TargetSubject targetSubject = new TargetSubject();
                // 1.获取对应的ClassLoader
                ClassLoader classLoader = targetSubject.getClass().getClassLoader();
                // 2.获取targetSubject 所实现的所有接口
                Class[] interfaces = targetSubject.getClass().getInterfaces();
                // 3.设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用
                InvocationHandler handler = new InvocationHandlerImpl(targetSubject);
        	/**
                 * 4.根据上面提供的信息,创建代理对象 在这个过程中
                 *  a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码。
                 *  b.然后根据相应的字节码转换成对应的class。
                 *  然后调用newInstance()创建实例。
        	 */
                Subject proxy = (Subject)Proxy.newProxyInstance(classLoader, interfaces, handler);
                proxy.request();
            }
        }
        

      JDK动态代理提供的生成动态代理类有个特点,真实角色(TargetSubject)必须有实现的定义接口(Subject),并且只能代理该接口定义的方法,所以当某个类没有实现接口,那么这个类就不能使用动态代理了。那该如何去解决这种情况呢?请看cglib代理。

    • cglib代理
      • 实现:cglib(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它通过查找类上的所有非final的public类型的方法定义,将这些方法的定义转换成字节码,将组成的字节码转换成相应的代理的class对象,实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)。
      • 代码实现
        public class MethodInterceptorImpl implements MethodInterceptor {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("TargetSubject#request before...");
                methodProxy.invokeSuper(o, args);
                System.out.println("TargetSubject#request after...");
                return null;
            }
        }
        public class Test002 {
            public static void main(String[] args) {
                TargetSubject targetSubject = new TargetSubject();
                MethodInterceptorImpl methodInterceptor = new MethodInterceptorImpl();
                //cglib 中加强器,用来创建动态代理
                Enhancer enhancer = new Enhancer();
                //设置要创建动态代理的类
                enhancer.setSuperclass(targetSubject.getClass());
                // 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截
                enhancer.setCallback(methodInterceptor);
                TargetSubject proxy =(TargetSubject)enhancer.create();
                proxy.request();
            }
        }
        

通过上面两种代理模式的实现方式来看,有抽象角色存在时选择JDK也能实现,cglib属于全能型,有无抽象角色均可。Spring中对于“Bean”生成代理对象时,如果该“Bean”实现某个抽象角色(或者接口定义)则选择JDK动态代理生成代理对象,某则选用cglib方式生成代理对象。

Spring AOP的工作原理

  • 工作原理全景图
    在Spring中,AOP工作原理如上图所示,定义个切面(Aspect),在切面中编写切入点(Pointcut)是什么,匹配上的类,在生成Bean的时候,实际上不会生成原本的目标对象(Target Object),而且经过Spring生成的代理对象(AOP Proxy),这样在执行目标方法(Join Point)时,其实执行的代理对象的拦截方法,然后按建议(Advice)在目标方法前后插入定制的代码。
  • 全景图中重要角色分析:
    • Aspect: A modularization of a concern that cuts across multiple classes。
    • Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
    • Advice: Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.)
    • Pointcut: A predicate that matches join points.Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name)
    • Target object: An object being advised by one or more aspects.
    • AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.

参考文章