- AOP概念:
- Spring Aop示例
- Spring AOP 的源码分析:
- Pointcut 接口 深入
- NameMatchMethodPointcut
- JdkRegexpMethodPointcut
- AnnotationMatchingPointcut
- ComposablePointcut
- ControlFlowPointcut
- Advice接口 深入
- Around Advice - MethodInterceptor
- Aspect(Advisor) 接口 深入
- 织入
- ProxyFactory 深入:
- AdvisedSupport
- 重回 ProxyFactory
- ProxyFactoryBean
- 自动化织入过程
- 总结:
连接点(joincut) 就是 可以被切入的所有方法
切入点(pointcut)就是你要在哪个方法上切入 的信息,这个信息就是切入点。
通知(advice)就是你要在那个方法前后要执行的具体方法
切面则是定义切入点和通知的组合
把切面应用到目标函数的过程称为织入(weaving)。
像下面这个类:
public class UserService {
public void addUser(){}
public void modifyUser(){}
public void deleteUser(){}
}
连接点(joinpoint) 就是指哪些方法可以被拦截(上面就是addUser(),modifyUser(),deleteUser() )
切入点(pointcut) 就是指定 具体在哪个方法上进行切入 的信息
通知 advice 在某个切入点上需要执行的代码,如日志记录和权限验证
切面(aspect) 由切入点和通知 组合而成
织入(weaving) 把切面的代码织入到目标函数的过程
织入又分静态织入和动态织入,
静态织入: 先将切面(aspect)类编译成class字节码之后,在Java目标类编译时织入,即先编译aspect类再编译目标类
**动态织入:**在运行时动态地将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成。
Spring Aop采用的是 动态织入,使用jdk和CGLIB来做动态代理。
我们先来做一个实例,然后再分析原理:
创建一个 SpringBoot 项目,然后导入依赖:
org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-web
然后编写Controller类:
@RestController
public class MyController {
@RequestMapping("/hello")
public String hello(){
System.out.println("hello");
return "hello";
}
}
编写切面类:
@Component
@Aspect
public class WebAspect {
@Pointcut("execution(public * com.learning.controller..*.*(..)))")
public void controllerAspect(){} //可以理解成 切入点的名称
@Before("controllerAspect()")
public void beforeMethod(){
System.out.println("我在执行请求之前做操作");
}
@After("controllerAspect()")
public void afterMethod(){
System.out.println("我在执行请求之后做操作");
}
}
@Aspect是告诉Spring容器,这个是一个切面类。
既然这个类是一个 切面(aspect),那么必须包含有 切入点(pointcut)和通知(advice).
其中
@Pointcut就是说明这是一个切入点,我们这里配置了 表达式,execution是指当执行匹配的那个方法时,
用法是:
execution(方法修饰符(可选) 返回类型 类路径 方法名 参数 异常模式(可选))
还有其他的匹配表达式:
execution: 匹配连接点 within: 某个类里面 this: 指定AOP代理类的类型 target:指定目标对象的类型 args: 指定参数的类型 bean:指定特定的bean名称,可以使用通配符(Spring自带的) @target: 带有指定注解的类型 @args: 指定运行时传的参数带有指定的注解 @within: 匹配使用指定注解的类 @annotation:指定方法所应用的注解
注意,方法修饰符必须要是 public的,因为动态代理只能拦截那些能访问到的方法,所以尽量不要用其他修饰符
@Pointcut修饰的方法名就是切入点的名字,用来标识这个切入点。
@Before() ,@After(), @Around() @AfterReturning @AfterThrowing,就是定义通知(advice)
@Around(): 它的方法的参数一定要ProceedingJoinPoint,这个对象是JoinPoint的子类。我们可以把这个看作是切入点的那个方法的替身,这个proceedingJoinPoint有个 proceed() 方法,相当于就是那切入点的那个方法执行
@AfterReturning : 是在目标方法正常完成后把增强处理织入
@AfterThrowing: 异常抛出后织入的增强
Spring AOP 的源码分析:Spring AOP 对我们上面的一些概念都做了抽象接口。
切入点(pointcut) 的接口就是 Pointcut:
我们前面说了 切入点 是指定具体在哪个方法上进行切入,所以接口里面就定义了 ClassFilter和 MethodMatcher这两个接口,指定具体在哪个类的哪个方法
分别看一下这两个接口:
ClassFilter接口 :
很简单,如果class类型匹配的话,matches()就会返回true,否则返回false,false就不会对该 连接点(Joincut)所在的类进行切入
MethodMatcher接口:
public interface MethodMatcher {
boolean matches(Method method, Class> targetClass);
boolean isRuntime();
boolean matches(Method method, Class> targetClass, Object... args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
这里面有两个 matches() 方法,一个带了连接点方法的参数,一个没有,具体执行哪一个则是看 isRuntime() 方法来确定。
- 如果isRuntime() 返回false,表示不会考虑具体的 Joincut的方法参数,这种类型的MethodMatch称之为StaticMethodMatcher,因为不用每次都检查参数,那么对于同样类型的方法匹配结构,就可以在框架内部缓存来提高性能。 所以 isRuntime() 返回false,则 matches(Method method, Class> targetClass)这个方法的匹配结果会成为其所属的PointCut接口的主要依据
- 如果isRuntime() 返回true,表示每次都对方法调用的参数进行匹配检查,叫做DynamicMethodMatcher ,检查时仍会先调用两个参数的matches() 方法,只有返回true时才会调用三个参数的matches()方法去匹配。性能差,最好少使用。
看一下该接口的实现类:
这是最简单的Pointcut 实现,见名知义,肯定就是根据指定的匹配字符串 和 连接点(JoinCut) 的方法名进行匹配 。
缺点就是无法对重载的方法进行区分,因为它只检查方法名称
看到该实现类里面的 Regex ,我们就知道该类肯定是基于正则表达式来实现匹配的。
使用参考下面:
public class test1 {
public static void main(String[] args) {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern("*.dosth.*");
}
public void dosth(){
System.out.println("do sth");
}
}
我们注意到,匹配的字符串是 *.dosth.* ,这是因为 ,使用该实现类的匹配模式必须以整个方法签名的形式指定,而不能像上面那个实现类一样仅指定 方法名称
AnnotationMatchingPointcut见名知义,肯定是根据是否存在某注解来匹配 JoinPoint
示例:
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(RequestMapping.class, RestController.class);
像这样的定义,就会将 所有 @RestController标注的类的所有@RequestMapping标注的方法作为 切入点(PointCut)
ComposablePointcut该实现类就是可以实现几个 Pointcut 之间的交集或者并集
示例:
public static void main(String[] args) {
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(RequestMapping.class, RestController.class);
NameMatchMethodPointcut pointcut1 = new NameMatchMethodPointcut();
pointcut1.addMethodName("dosth");
ComposablePointcut composablePointcut = new ComposablePointcut();
composablePointcut = composablePointcut.union(pointcut);
}
ControlFlowPointcut
比如一个方法 method1() 被作为切入点(pointcut),其他的实现类都在这个方法被执行的时候就切入,但是这个实现类可以判断是哪一个方法调用的它,如果是被指定的方法调用的话才会切入。
Advice接口 深入通知 advice 是在某个切入点上需要执行的具体逻辑 ,在Spring AOP中对应的接口是 Advice
根据 Advice 实例能否在所有目标对象类的所有实例中共享这一标准 ,可以划分为 per-class和 per-instance 类型的 Advice,per-class的 Advice只是提供方法拦截的功能,不会为目标对象类保存任何状态或者添加新的特性,
Spring AOP中 BeforeAdvice和AfterAdvice 都是 per-class,Interceptor 则是 per-instance
- BeforeAdvice 所实现的横切逻辑将在对应的 Joincut 之前执行,在 BeforeAdvice 执行完成之后,程序将会从Joincut 处继续执行。我们可以使用 BeforeAdvice 来进行整个系统的某些资源初始化,或者其他的一些准备工作。
- ThrowsAdvice这个就是 在抛出异常之后执行的切面逻辑,
我们可以提供实现该接口来实现对系统中特定的异常情况进行监控。虽然该接口没有定义任何方法,但是我们在实现类中定义方法,仍然需要按照一定的规则: void afterThrowing(Method m ,Object[] args,Object target,Throwable t)(前三个参数是可以省略的) - AfterReturningAdvice这个则是在方法执行成功之后进行的处理逻辑,其中定义了 afterReturning()方法,有个缺点就是,该接口只能访问方法的返回值,但却不能修改返回值
- 我们在前面的AOP理论中介绍了 Around Advice,那么在Spring AOP中对应的接口就是MethodInterceptor。
在这个接口中,定义了一个方法:
@Nullable Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
其中MethodInvocation 接口定义了这样的方法:
@Nonnull Method getMethod();
这就是获得当前的Joincut的方法,我们可以决定具体何时再调用该方法。
所以我们可以在 Joincut的逻辑执行之前或者之后插入相应的逻辑,甚至捕获 Joincut方法可能抛出的异常
前面已经说过了,切面(aspect) 由切入点和通知 组合而成
当 PointCut 和 Advice 都准备好之后,就需要将它们装入到 切面(Aspect) 中去
Spring中的 Aspect 是 Advisor。正常来说,一个Aspect可以有多个Pointcut 和 多个 Advice ,但是Spring的 Advisor 只有一个 Pointcut和一个Advice,所以Spring中的 Advisor 是特殊的切面(aspect)
Spring AOP 的 Advisor接口层次如上。
实际上,看 Advisor接口:
public interface Advisor {
Advice EMPTY_ADVICE = new Advice() {};
Advice getAdvice();
boolean isPerInstance();
}
它本身只有 (通知)Advice
到了 PointcutAdvisor接口才算一个有 Pointcut 和 Advice 的完整的 Advisor:
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
下面来看一下具体的几个实现:
- DefaultPointcutAdvisor 这是最通用的PointcutAdvisor实现,任何类型的Pointcut和任何类型的Advice都可以通过DefaultPointcutAdvisor来使用,可以直接通过构造方法或者 setter和 getter来注入Pointcut和Advice
- NameMatchMethodPointcutAdvisor是细化后的 DefaultPointcutAdvisor ,它限定自身能使用的 Pointcut类型为NameMatchMethodPointcut,并且外部不可更改。不过 Advice任何类型均可用。
- RegexpMethodPointcutAdvisor这个类也是限定了自己能使用的 Pointcut类型,强制为AbstractRegexpMethodPointcut类型,默认使用的是 JdkRegexpMethodPointcut这个实现类
- DefaultBeanFactoryPointcutAdvisor这个是较少使用的一个实现类,它的唯一作用是可以通过 BeanName 来在容器里面找到具体的 Bean的 Pointcut 和 Advice ,其他和第一个没有什么区别。
前面的概念已经说了,织入(weaving) 把切面的代码织入到目标函数的过程。
在Spring AOP中,org.springframework.aop.framework.ProxyFactory 这个类是最基本的织入器。
使用ProxyFactory需要两个最基本的东西 。
- 第一个是要对其进行织入的目标对象
- 第二个是将要应用到目标对象的 Aspect (Advisor),
我们知道Spring的织入过程是 使用JDK动态代理(类实现了一个接口)和 CGLIB代理(类没有实现任何接口) 。
ProxyFactory 深入:这个类一看就是工厂类,所以只要看它提供产品类的方法即可:
public Object getProxy() {
return createAopProxy().getProxy();
}
然后再去找 createAopProxy()方法,
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
可以看到,它返回的是一个 AopProxy 接口。
接口层次如上,可知,这个 AopProxy接口应该就是拿来做具体的织入过程的。
AopProxy是通过getAopProxyFactory().createAopProxy(this);这个工厂方法来获取的
找到这个方法,发现到子类去实现了,所以我们再到子类去看:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
伪代码如下:
if((config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)))){
// 创建CglibAopProxy实例返回
}else{
//创建JdkDynamicAopProxy实例返回
}
也就是说,如果传入的AdvisedSupport实例config的isOptimize或者 isProxyTargetClass为true或者对象没有实现任何接口,那么就使用 CGLIB去代理,否则 使用 JDK去代理
那么 这个传入的AdvisedSupport实例又是什么呢?
AdvisedSupport来看一下它的类层次
里面的 ProxyConfig仅定义了五个属性,分别控制在生成代理对象的时候,应该采取怎么样的措施:
- proxyTargetClass 如果为true,则使用 CGLIB进行代理。默认为false
- optimize主要用于告知代理对象是否需要采取进一步的优化措施。 如果为true,则使用CGLIB代理,默认false
- opaque该属性用于控制生成的代理对象是否可以强制转换成Advised类
- exposeProxy该属性可以让Spring AOP在生成代理对象时,将其绑定到 ThreadLocal
- frozen为true,则一旦针对代理对象生成的各项信息配置完成,则不容许更改
要生成代理对象,仅仅靠 ProxyConfig提供的这几个属性完全不够,我们还需要生成代理对象的一些具体信息,如,要针对哪些目标类生成代理对象,要为代理对象加入哪些横切逻辑等这些信息可以通过 Advised接口查询到,简单地说,我们可以使用 Advised接口访问相应代理对象所持有的Advisor,进行添加、移除Advisor的操作
AdvisedSupport继承了 ProxyConfig,我们可以设置代理对象生成的一些控制属性,实现了Advised接口,我们就可以设置生成代理对象相关的目标类、Advice等必要信息。因此,具体的AopProxy实现在生成代理对象的时候,可以从 AdvisedSupport处获得所有必要的信息
重回 ProxyFactory
然后ProxyFactory 类继承了 AdvisedSupport,又能够通过 createAopProxy() 方法来获取AopProxy。所以我们既可以通过 ProxyFactory 设置生成代理对象的所需要的相关信息,也可以取得最终生成的代理对象,前者是 AdvisedSupport的职责,后者是AopProxy的职责
ProxyFactory只是最普通的一个 织入器。
ProxyFactoryBean这个类将Spring AOP和Spring IOC容器支持相结合,使我们可以在 容器中对 切入点(PointCut)和通知(Advice)管理更容易。
ProxyFactoryBean可以这样理解 Proxy+ FactoryBean,在IOC容器中,FactoryBean的作用是存储一个对象,如果容器中的某个对象持有某个FactoryBean的引用,那它取得的不是 FactoryBean实例本身,而是 getObject() 方法返回的对象。因此,如果容器中某个对象依赖了 ProxyFactoryBean的实例,那它就会使用到通过 getObject() 返回的代理对象。
因为 ProxyFactoryBean继承了 ProxyCreatorSupport 这个类,而这个类又已经把需要做的事情基本完成了(如设置目标对象,配置各种属性,生成对应的AopProxy对象),所以 ProxyFactoryBean做的主要事情就是拿出AopProxy ,调用它的getProxy()拿到代理对象
//简化代码
public Object getObject() throws BeansException {
if (isSingleton()) {
return getSingletonInstance();
}
}
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
protected Object getProxy(AopProxy aopProxy) {
return aopProxy.getProxy(this.proxyClassLoader);
}
可以发现,确实如我们所想
自动化织入过程Spring AOP 的自动代理是建立在IOC容器的 BeanPostProcessor概念之上。使用BeanPostProcessor,我们可以在遍历容器中所有bean的基础上,对遍历到的bean进行一些操作。
我们只需要提供一个BeanPostProcesser ,然后在这个BeanPostProcesser 内部实现这样的逻辑: 当对象实例化的时候,为其生成代理对象并返回,而不是原本的对象,从而达到代理对象自动生成的目的。
伪代码如下:
for (bean in Ioc容器){
if(bean符合拦截条件){
Object proxy = createProxyFor(bean);
return proxy; //返回代理的对象
}else{
Object instance = createInstance(bean);
return instance;
}
}
createProxyFor()方法创建代理对象,就直接通过 ProxyFactoryBean的getBean()都可以。
对于拦截条件,则可以是标注了某些注解。
DefaultAdvisorAutoProxyCreator这个类就是实现了完全自动的自动注入。
可以发现,它确实实现了 BeanPostProcessor 这个接口。
它会自动搜寻容器内的所有 Advisor,然后根据各个 Advisor所提供的拦截信息,为符合条件的容器中的目标对象生成相应的代理对象。
在AOP概念中的词基本在Spring AOP都能找到对应的接口,Pointcut --> Pointcut接口 , Advice --> Advice接口,Aspect --> Advisor接口,织入 --> ProxyFactory类
真正实现AOP,还是靠的 ProxyBeanFactory 这个类集成了 切入点和切面等信息,DefaultAdvisorAutoProxyCreator实现了 BeanPostProcessor 这个接口,会去容器内搜寻所有的
ProxyBeanFactory 实例,并为其生成代理对象,而不是原本的对象,这样就实现了代理,也实现了AOP



