Spring AOP 实现机制

1,523 阅读4分钟

1. AOP概念

1.1 JoinPoint连接点:程序执行中的特定点,如方法执行,调用构造函数或字段复制等,面向切面编程,JoinPoint就是要被切入的对象。 

1.2 Advice通知:在一个连接点中,切面采取的行动,针对切入点,要做的事情。

1.3 Pointcut切点:一个匹配连接点的正则表达式。每个任何连接点匹配一个切入点时,就执行与该切入点相关联的指定通知。关注哪些被切入点可以切入。

1.4 Aspect切面(Advisor):一个分布在应用程序中多个位置的标准代码/功能,通常与实际的业务逻辑(例事务管理)不同。每个切面都侧重于一个特定的横切功能。

1.5 Weaving织入:链接切面和目标对象来创建一个通知对象的过程。

1.6 Advice通知 + Pointcut切点形成了切面Aspect/Advisor

1.7 JoinPoint与Pointcut区别:在Spring AOP中,所有方法执行都是JoinPoint。Pointcut是描述信息,修饰的是JoinPoint,通过Pointcut就可以确定哪些JoinPoint可以织入Advice,JointPoint与Pointcut本质上两个不同的纬度。Advice是在JointPoint执行,Pointcut确定了哪些JoinPoint执行Advice。

2. AOP实现

2.1 织入节点

编译期间:切面在目标类编译时被织入,需要引入独立的编译器。

编译后:增强已经编译出来的类,如我们要增强依赖的 jar 包中的某个类的某个方法。

类加载期:在 JVM 进行类加载的时候进行织入。

运行期:切面在应用运行的某个时期被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象。

2.2 实现

AspectJ:AspectJ 是一个采用Java 实现的AOP框架,它能够对代码进行编译(一般在编译期进行),让代码具有AspectJ 的 AOP 功能,AspectJ 是目前实现 AOP 框架中最成熟,功能最丰富的语言。ApectJ 主要采用的是编译期静态织入的方式。

AspectWerkz:基于Java的简单、动态、轻量级、强大的AOP框架。可以在运行时或编译时轻松的改造任何(旧)应用程序或除了rt.jar以外的外部类库。

JBoss AOP:JBoss 4.0带了一个AOP框架。这个框架和JBoss应用服务器紧密地结合,但是也能够在应用中单独运行它。

Spring AOP:Spring AOP 是通过动态代理技术实现的,而动态代理是基于反射设计的。Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理

2.3 Spring AOP与AspectJ 对比 

Spring AOP 与AspectJ都是完整的AOP方案,不存在依赖。Spring AOP作为一个整合框架,兼容了AspectJ切面定义,底层使用动态代理。

3. AspectJ实现

3.1 定义切面

针对test()方法进行拦截,方法执行前打印"before interceptor"

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectjInterceptor {    
    @Pointcut("execution( * com.hobbit.aspectj.Hello.test())")    
    public void execute(){    }    

    @Before("execute()")    
    public void beforeLog(){        
        System.out.println("before interceptor");    
    }
}

3.2 定义PointCut

public class Hello {
    public void test(){
        System.out.println("hello aspectj");    
    }
}

3.3 设置编译器

Java Compiler -> User compiler 选择Ajc,同时设置aspectjtools.jar

3.4 运行结果

3.5 反编译文件

4. Spring AOP实现

Spring aop既可以使用AspectJ注解实现拦截,也可以不依赖AspectJ,使用Spring advice + pointcut,实现拦截注入。

4.1 使用AspectJ注解

4.1.1 定义元注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
    String value();
}

4.1.2 定义切面

import java.util.Objects;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogInterceptor {
    @Pointcut("@annotation(com.hobbit.interceptor.LogAnnotation)") 
    public void annotationPointCut(){    }    

    @Before("annotationPointCut()")    
    public void beforeLog(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        LogAnnotation logAnnotation = methodSignature.getMethod().getAnnotation(LogAnnotation.class);
        if(Objects.nonNull(logAnnotation)){
            System.out.println("before interceptor : " + logAnnotation.value());
        }
    }
}

4.1.3 织入

import com.hobbit.entity.UserEntity;
import com.hobbit.interceptor.LogAnnotation;
import com.hobbit.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @LogAnnotation("queryUser")
    @Transactional
    public UserEntity queryUser(int id){
        return userMapper.getById(id);
    }
}

4.2 Spring AOP实现

Spring AOP实现了完整的方案,通过定义advice + pointcut 实现对象动态织入。

4.2.1 定义Advice

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class LogBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable{
        System.out.println("before advice for class:" + target.getClass() +" and method:" + method.getName());
    }
}

4.2.2 配置PointCut及切面

<bean id="logBeforeAdvice" class="com.hobbit.interceptor.LogBeforeAdvice"></bean>

<bean id="logPointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
   <property name="pattern" value="com.hobbit.service.ShowService.show"/>
</bean>

<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
   <property name="advice" ref="logBeforeAdvice"></property>
   <property name="pointcut" ref="logPointCut"></property>
</bean>

4.2.3 通过ProxyFactoryBean创建代理

<bean id="showProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="target" ref="showService"></property>
   <property name="interceptorNames" value="logAdvisor"></property>
   <property name="proxyInterfaces" value="com.hobbit.service.ShowInterface"></property>
</bean>

4.2.5 Spring AOP 类图

4.2.4 Spring AOP时序图

tips: 代理对象什么节点创建的?

5. Spring AOP生效

5.1 Spring 创建代理的节点

Spring 通过org.springframework.beans.factory.config.BeanPostProcessor #postProcessAfterInitialization实现AspectJ注解代理对象的创建,具体在org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization实现代理对象的创建,方法如下

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,      @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);   
    }   
    ProxyFactory proxyFactory = new ProxyFactory();   
    proxyFactory.copyFrom(this);   
    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
           proxyFactory.setProxyTargetClass(true);      
        }
        else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
     }
     Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
     proxyFactory.addAdvisors(advisors);
     proxyFactory.setTargetSource(targetSource);
     customizeProxyFactory(proxyFactory);
     proxyFactory.setFrozen(this.freezeProxy);
     if (advisorsPreFiltered()) {
         proxyFactory.setPreFiltered(true);
     }
     return proxyFactory.getProxy(getProxyClassLoader());
}

核心点是根据beanName找到对应的切面列表,完成代理对象的创建

容器启动时,注册BeanPostProcessor对象,org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator

代理对象创建前

创建后

5.2 Spring AOP拦截

Spring AOP通过把Advice转化成MethodIntercetor,通过递归调用的方式实现了Advice的织入,完成advice的调用后,调用目标方法完成切入。核心点:让Advice和Joinpoint会面。

目标对象:ReflectiveMethodInvocation,在JdkDynamicAopProxy invoke方法中创建,是JoinPiont的一个实现。

拦截对象:MethodInterceptor.invoke(MethodInvocation invocation)包括了要拦截的对象。实现不同形式的拦截

5.3 总结

Spring的AOP实现并不依赖于AspectJ任何类,它自己实现了一套AOP的。比如它Spring自己提供的BeforeAdvice和AfterAdvice都是对AOP联盟规范的标准实现。以及Spring自己抽象出来的对Advice的包装:org.springframework.aop.Advisor贯穿Spring AOP的始终。