前言
再写完《走进Spring中Bean的世界》,写这篇文章时,原本想法是把AOP的思想、原理以及源码统统囊入其中,最后发现会和上篇文章一样,读起来难以专注。所以还是本着跳出看全景,转进去看本质的原则,将AOP的文章分为三部分,分别为思想篇、源码篇、应用篇。
你可以了解到
- Java程序在JVM中的运行流程
- 面向切面编程思想
- 代理模式的应用
- Spring AOP的工作原理
Java程序在JVM中的运行流程
- 先来了解下虚拟机栈(JVM stack)
虚拟栈是一个后入先出(LIFO)栈。每一个线程创建时,JVM会为这个线程创建一个私有的虚拟机栈,当线程调用某个对象的方法时,JVM会相应地创建一个栈帧(Stack Frame)放到虚拟机中,用来表示某个方法的调用。线程对方法的调用就对应一个栈帧的入栈和出栈的过程。
- 来看段代码了解虚拟机栈
上面代码我们在method()1时,打个断点。public class TestMethod { public void method1() { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); } public static void main(String[] args) { TestMethod target = new TestMethod(); target.method1(); } } 当前虚拟栈的信息可以如下图所示:

- 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.
参考文章
- AOP官方文档:docs.spring.io/spring/docs…
- 《Spring设计思想》AOP设计基本原理:blog.csdn.net/luanlouis/a…