Spring AOP介绍

AOP全名 Aspect-oriented programming 面向切面编程

AOP的两种代理方式:

  • 以AspectJ为代表的静态代理。
  • 以Spring AOP为代表的动态代理。

基本术语

切面(Aspect)

切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

可以简单地认为, 使用 @Aspect 注解的类就是切面

@Component
@Aspect
public class LogAspect {
}

目标对象(Target)

目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。

连接点(JoinPoint)

程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:

  • 方法(表示程序执行点,即在哪个目标方法)
  • 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)

简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法

@Before("pointcut()")
public void log(JoinPoint joinPoint) { //这个JoinPoint参数就是连接点
}

切入点(PointCut)

切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 

一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知。

@Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
public void pointcut() {
}

上边切入点的匹配规则是com.remcarpediem.test.aop.service包下的所有类的所有函数。

通知(Advice)

通知是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。 

// @Before说明这是一个前置通知,log函数中是要前置执行的代码,JoinPoint是连接点,
@Before("pointcut()")
public void log(JoinPoint joinPoint) { 
}
  • Before:前置通知。在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可。
  • After:后置通知。在目标方法完成之后做增强,无论目标方法是否成功完成,@After可以指定一个切入点表达式。
  • AfterReturning:返回通知。在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值。
  • AfterThrowing:异常通知。主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名。
  • Around:环绕通知。环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务、日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint。

织入(Weaving)

织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。

快速入门

必不可少的两个依赖:

spring.version为5.0.2.RELEASE

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.version}</version>
</dependency>

服务接口

public interface UserService {
    void addUser(String name);

    void updateUser();

    void deleteUser();

    void query();
}

目标对象

@Service
public class UserServiceImpl implements UserService {

    @Override
    public void addUser(String name) {
        System.out.println("增加用户。。");
    }

    @Override
    public void updateUser() {
        System.out.println("修改用户。。");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户。。");
    }

    @Override
    public void query() {
        System.out.println("查询用户。。");
    }
}

以前置通知为例:

/**
 * 切面代码
 */
@Component
@Aspect
public class FirstAspect {

    @Pointcut("execution(* com.spring.service.impl.UserServiceImpl.addUser(..))")
    public void pointCut(){

    }

    @Before("pointCut()")
    public void before(JoinPoint jp) { // 可以选择额外的传入一个JoinPoint连接点对象,必须用方法的第一个参数接收。
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); // 通过JoinPoint对象获取更多信息
        String name = signature.getName();
        System.out.println("1 -- before...[" + clz + "]...[" + name + "]...");
    }
}

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, WebConfig.class})
@WebAppConfiguration
public class AopTest {

    @Autowired
    private UserService userService;

    @Test
    public void test(){
        userService.addUser("before advice");
    }

}

切点表达式

切入点指示符用来指示切入点表达式目的,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持的AspectJ切入点指示符如下:

  • execution:用于匹配方法执行的连接点;
  • within:用于匹配指定类型内的方法执行;
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
  • @within:用于匹配所以持有指定注解类型内的方法;
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
  • @annotation:用于匹配当前执行方法持有指定注解的方法;

execution

使用“execution(方法表达式)”匹配方法执行。

模式 描述
public * *(..) 任何公共方法的执行
* cn.javass..IPointcutService.*() cn.javass包及所有子包下IPointcutService接口中的任何无参方法 
* cn.javass..*.*(..)  cn.javass包及所有子包下任何类的任何方法 
* cn.javass..IPointcutService.*(*) cn.javass包及所有子包下IPointcutService接口的任何只有一个参数方法 
* (!cn.javass..IPointcutService+).*(..) 非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法 
* cn.javass..IPointcutService+.*()  cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法 
* cn.javass..IPointcut*.test*(java.util.Date)

cn.javass包及所有子包下IPointcut前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的。

如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的;

* cn.javass..IPointcut*.test*(..)  throws IllegalArgumentException, ArrayIndexOutOfBoundsException

cn.javass包及所有子包下IPointcut前缀类型的的任何方法,且抛出IllegalArgumentException和ArrayIndexOutOfBoundsException异常

* (cn.javass..IPointcutService+

&& java.io.Serializable+).*(..)

任何实现了cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的类型的任何方法 
@java.lang.Deprecated * *(..)  任何持有@java.lang.Deprecated注解的方法 
@java.lang.Deprecated @cn.javass..Secure  * *(..)  任何持有@java.lang.Deprecated和@cn.javass..Secure注解的方法 
@(java.lang.Deprecated || cn.javass..Secure) * *(..) 任何持有@java.lang.Deprecated或@ cn.javass..Secure注解的方法
(@cn.javass..Secure  *)  *(..)  任何返回值类型持有@cn.javass..Secure的方法 
*  (@cn.javass..Secure *).*(..)  任何定义方法的类型持有@cn.javass..Secure的方法 
* *(@cn.javass..Secure (*) , @cn.javass..Secure (*)) 

任何签名带有两个参数的方法,且这个两个参数都被@ Secure标记了,

如public void test(@Secure String str1, @Secure String str1);

 

* *((@ cn.javass..Secure *))或

* *(@ cn.javass..Secure *)

任何带有一个参数的方法,且该参数类型持有@ cn.javass..Secure;

如public void test(Model model);且Model类上持有@Secure注解

 

* *(

@cn.javass..Secure (@cn.javass..Secure *) ,

@ cn.javass..Secure (@cn.javass..Secure *))

任何带有两个参数的方法,且这两个参数都被@ cn.javass..Secure标记了;且这两个参数的类型上都持有@ cn.javass..Secure; 

* *(

java.util.Map<cn.javass..Model, cn.javass..Model>

, ..)

 

任何带有一个java.util.Map参数的方法,且该参数类型是以< cn.javass..Model, cn.javass..Model >为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型;

如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *(

java.util.HashMap<cn.javass..Model,cn.javass..Model>

, ..)”进行匹配;

而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配

* *(java.util.Collection<@cn.javass..Secure *>) 

任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;

如public void test(Collection<Model> collection);Model类型上持有@cn.javass..Secure

* *(java.util.Set<? extends HashMap>)

任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型继承与HashMap;

Spring AOP目前测试不能正常工作。

* *(java.util.List<? super HashMap>) 

任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型是HashMap的基类型;如public voi test(Map map);

Spring AOP目前测试不能正常工作

* *(*<@cn.javass..Secure *>)

任何带有一个参数的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;

Spring AOP目前测试不能正常工作

within

使用“within(类型表达式)”匹配指定类型内的方法执行。

模式 描述
within(cn.javass..*) cn.javass包及子包下的任何方法执行
within(cn.javass..IPointcutService+) cn.javass包或所有子包下IPointcutService类型及子类型的任何方法
within(@cn.javass..Secure *)

持有cn.javass..Secure注解的任何类型的任何方法

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

this

使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符;

模式 描述
this(cn.javass.spring.chapter6.service.IPointcutService) 当前AOP对象实现了 IPointcutService接口的任何方法
this(cn.javass.spring.chapter6.service.IIntroductionService)

当前AOP对象实现了 IIntroductionService接口的任何方法,也可能是引入接口

target

使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符;

模式 描述
target(cn.javass.spring.chapter6.service.IPointcutService) 当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法
target(cn.javass.spring.chapter6.service.IIntroductionService)

当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法。

不可能是引入接口

args

使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;

模式 描述
args (java.io.Serializable,..) 任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的

@within

使用“@within(注解类型)”匹配所以持有指定注解类型内的方法;注解类型也必须是全限定类型名;

模式 描述
@within cn.javass.spring.chapter6.Secure)

任何目标对象对应的类型持有Secure注解的类方法;

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

@target

使用“@target(注解类型)”匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;注解类型也必须是全限定类型名; 

模式 描述
@target (cn.javass.spring.chapter6.Secure)

任何目标对象持有Secure注解的类方法;

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

@args

使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;注解类型也必须是全限定类型名;

模式 描述
@args (cn.javass.spring.chapter6.Secure) 任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符;

@annotation

使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名;

模式 描述
@annotation(cn.javass.spring.chapter6.Secure ) 当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配

bean

使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring ASP扩展的,在AspectJ中无相应概念;

模式 描述
bean(*Service) 匹配所有以Service命名(id或name)结尾的Bean

Advice通知

服务接口

public interface UserService {
    void addUser(String name);

    void updateUser();

    void deleteUser();

    void query();

    String getUser(String name);
}

目标对象

/**
 * UserServiceImpl:目标对象
 */
@Service
public class UserServiceImpl implements UserService {

    @Override
    public void addUser(String name) {
        System.out.println("增加用户。。");
    }

    @Override
    public void updateUser() {
        System.out.println("修改用户。。");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户。。");
    }

    @Override
    public void query() {
        System.out.println("查询用户。。");
    }

    @Override
    public String getUser(String name) {
        System.out.println("查询用户。。" + name);
        return name;
    }
}

五种通知类型

前置通知(@Before)

含义:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。 

前置通知方法,可以没有参数,也可以额外接收一个JoinPoint,Spring会自动将该对象传入,代表当前的连接点,通过该对象可以获取目标对象 和 目标方法相关的信息。

注意,如果接收JoinPoint,必须保证其为方法的第一个参数,否则报错。

/**
 * 切面代码
 */
@Component
@Aspect
public class BeforeAspect {

    @Pointcut("execution(* com.spring.service.impl.UserServiceImpl.addUser(..))")
    public void pointCut(){

    }

    @Before("pointCut()")
    public void before(JoinPoint jp) { // 可以选择额外的传入一个JoinPoint连接点对象,必须用方法的第一个参数接收。
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); // 通过JoinPoint对象获取更多信息
        String name = signature.getName();
        System.out.println("1 -- before...[" + clz + "]...[" + name + "]...");
    }
}

正常返回通知(@AfterReturning)

含义:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。 

在后置通知中也可以选择性的接收一个JoinPoint来获取连接点的额外信息,但是这个参数必须处在参数列表的第一个。

@Component
@Aspect
public class AfterReturnAspect {

    @Pointcut("execution(* com.spring.service.impl.UserServiceImpl.addUser(..))")
    public void pointCut(){

    }

    @AfterReturning("pointCut()")
    public void afterReturn(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature();
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...");
    }
}

在后置通知中,还可以通过配置获取返回值,但是一定要保证JoinPoint处在参数列表的第一位,否则抛异常。

@Component
@Aspect
public class AfterReturnAspect {

    @Pointcut("execution(* com.spring.service.impl.UserServiceImpl.getUser(..))")
    public void pointCut(){

    }

    @AfterReturning(pointcut = "pointCut()", returning = "msg")
    public void afterReturn(JoinPoint jp, Object msg){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature();
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]...");
    }
}

异常返回通知(@AfterThrowing)

含义:在连接点抛出异常后执行。 

可以配置传入JoinPoint获取目标对象和目标方法相关信息,但必须处在参数列表第一位。

另外,还可以配置参数,让异常通知可以接收到目标方法抛出的异常对象。

@Component
@Aspect
public class AfterThrowAspect {


    @Pointcut("execution(* com.spring.service.impl.UserServiceImpl.getUser(..))")
    public void pointCut(){

    }

    /**
     * 可以随便在目标方法中设置异常,如:int = 1/0;
     * @param jp
     * @param e
     */
    @AfterThrowing(pointcut = "pointCut()", throwing = "e")
    public void afterThrow(JoinPoint jp, Throwable e){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1afterThrow..["+clz+"]..["+name+"].."+e.getMessage());
    }
}

返回通知(@after)

含义:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。 

和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返-例如抛出异常,则后置通知不会执行。

而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。

另外,后置通知可以通过配置得到返回值,而最终通知无法得到。

最终通知也可以额外接收一个JoinPoint参数,来获取目标对象和目标方法相关信息,但一定要保证必须是第一个参数。

@Component
@Aspect
public class AfterAspect {

    @Pointcut("execution(* com.spring.service.impl.UserServiceImpl.getUser(..))")
    public void pointCut(){

    }

    @After("pointCut()")
    public void after(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- after..["+clz+"]..["+name+"]...");
    }
}

环绕通知(@Around)

含义:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。 

环绕通知中必须显式的调用目标方法,目标方法才会执行,这个显式调用是通过ProceedingJoinPoint来实现的,可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,注意这个参数必须处在环绕通知的第一个形参位置。

要注意,只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint。

环绕通知需要返回返回值,否则真正调用者将拿不到返回值,只能得到一个null。

环绕通知有控制目标方法是否执行、有控制是否返回值、有改变返回值的能力。

环绕通知虽然有这样的能力,但一定要慎用,不是技术上不可行,而是要小心不要破坏了软件分层的“高内聚 低耦合”的目标。

@Component
@Aspect
public class AroundAspect {

    @Pointcut("execution(* com.spring.service.impl.UserServiceImpl.getUser(..))")
    public void pointCut(){

    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("1 -- around before...");
        Object obj = jp.proceed(); //--显式的调用目标方法
        System.out.println("1 -- around after...");
        return obj;
    }
}

对JoinPoint的一些说明

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。

常用api:

  • Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
  • Object[] getArgs(); 获取传入目标方法的参数对象
  • Object getTarget(); 获取被代理的对象
  • Object getThis(); 获取代理对象

对ProceedingJoinPoint 的一些说明

ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中。

  • Object proceed() throws Throwable //执行目标方法
  • Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法

五种通知的执行顺序

在目标方法没有抛出异常的情况下:

  1.  前置通知
  2. 环绕通知的调用目标方法之前的代码
  3. 目标方法
  4. 环绕通知的调用目标方法之后的代码
  5. 后置通知
  6. 最终通知

在目标方法抛出异常的情况下

  1. 前置通知
  2. 环绕通知的调用目标方法之前的代码
  3. 目标方法
  4. 抛出异常
  5. 异常通知
  6. 最终通知

五种通知的常见使用场景

Spring AOP的实现原理

基于注解的方式实现AOP需要在配置类中添加注解@EnableAspectJAutoProxy

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class) //引入AspectJAutoProxyRegister.class对象
public @interface EnableAspectJAutoProxy {
 
    //true——使用CGLIB基于类创建代理;false——使用java接口创建代理
    boolean proxyTargetClass() default false;
 
    //是否通过aop框架暴露该代理对象,aopContext能够访问.
    boolean exposeProxy() default false;
}

如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

如果目标对象实现了接口,可以强制使用CGLIB实现AOP

如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换。

Spring AOP代理对象的生成

Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:
/**
    * <ol>
    * <li>获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised(opaque=false)
    * <li>检查上面得到的接口中有没有定义 equals或者hashcode的接口
    * <li>调用Proxy.newProxyInstance创建代理对象
    * </ol>
    */
   public Object getProxy(ClassLoader classLoader) {
       if (logger.isDebugEnabled()) {
           logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource());
       }
       Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);
       findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

代理对象生成了,那切面是如何织入的?

切面是如何织入

注意:方法之间的调用直接使用的是原始对象,而非代理对象,因而内部调用不会产生代理。

我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下Spring AOP是如何织入切面的。

public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable {
       MethodInvocation invocation = null;
       Object oldProxy = null;
       boolean setProxyContext = false;
 
       TargetSource targetSource = this.advised.targetSource;
       Class targetClass = null;
       Object target = null;
 
       try {
           //eqauls()方法,具目标对象未实现此方法
           if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){
                return (equals(args[0])? Boolean.TRUE : Boolean.FALSE);
           }
 
           //hashCode()方法,具目标对象未实现此方法
           if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){
                return newInteger(hashCode());
           }
 
           //Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知
           if (!this.advised.opaque &&method.getDeclaringClass().isInterface()
                    &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                // Service invocations onProxyConfig with the proxy config...
                return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args);
           }
 
           Object retVal = null;
 
           if (this.advised.exposeProxy) {
                // Make invocation available ifnecessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
           }
 
           //获得目标对象的类
           target = targetSource.getTarget();
           if (target != null) {
                targetClass = target.getClass();
           }
 
           //获取可以应用到此方法上的Interceptor列表
           List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);
 
           //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
           if (chain.isEmpty()) {
                retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
           } else {
                //创建MethodInvocation
                invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
           }
 
           // Massage return value if necessary.
           if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy)
                    &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                // Special case: it returned"this" and the return type of the method
                // is type-compatible. Notethat we can't help if the target sets
                // a reference to itself inanother returned object.
                retVal = proxy;
           }
           return retVal;
       } finally {
           if (target != null && !targetSource.isStatic()) {
                // Must have come fromTargetSource.
               targetSource.releaseTarget(target);
           }
           if (setProxyContext) {
                // Restore old proxy.
                AopContext.setCurrentProxy(oldProxy);
           }
       }
}

主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。

首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:

public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
   MethodCacheKeycacheKey = new MethodCacheKey(method);
   List<Object>cached = this.methodCache.get(cacheKey);
   if(cached == null) {
            cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                               this,method, targetClass);
            this.methodCache.put(cacheKey,cached);
   }
   returncached;
}
可以看到实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。
/**
* 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor,
* 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断
* 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回.
*/
publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {
   // This is somewhat tricky... we have to process introductions first,
   // but we need to preserve order in the ultimate list.
   List interceptorList = new ArrayList(config.getAdvisors().length);

   //查看是否包含IntroductionAdvisor
   boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);

   //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor
   AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();

   Advisor[] advisors = config.getAdvisors();
    for (int i = 0; i <advisors.length; i++) {
       Advisor advisor = advisors[i];
       if (advisor instanceof PointcutAdvisor) {
            // Add it conditionally.
            PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;
            if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                //TODO: 这个地方这两个方法的位置可以互换下
                //将Advisor转化成Interceptor
                MethodInterceptor[]interceptors = registry.getInterceptors(advisor);

                //检查当前advisor的pointcut是否可以匹配当前方法
                MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();

                if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {
                    if(mm.isRuntime()) {
                        // Creating a newobject instance in the getInterceptors() method
                        // isn't a problemas we normally cache created chains.
                        for (intj = 0; j < interceptors.length; j++) {
                           interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));
                        }
                    } else {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
       } else if (advisor instanceof IntroductionAdvisor){
            IntroductionAdvisor ia =(IntroductionAdvisor) advisor;
            if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
                Interceptor[] interceptors= registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
       } else {
            Interceptor[] interceptors =registry.getInterceptors(advisor);
            interceptorList.addAll(Arrays.asList(interceptors));
       }
   }
   return interceptorList;
}

这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor。

接下来我们再看下得到的拦截器链是怎么起作用的。

if (chain.isEmpty()) {
    retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
} else {
    //创建MethodInvocation
    invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
    retVal = invocation.proceed();
}

从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码:

public Object proceed() throws Throwable {
   //  We start with an index of -1and increment early.
   if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {
       //如果Interceptor执行完了,则执行joinPoint
       return invokeJoinpoint();
   }

   Object interceptorOrInterceptionAdvice =
       this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
   
   //如果要动态匹配joinPoint
   if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){
       // Evaluate dynamic method matcher here: static part will already have
       // been evaluated and found to match.
       InterceptorAndDynamicMethodMatcher dm =
            (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
       //动态匹配:运行时参数是否满足匹配条件
       if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {
            //执行当前Intercetpor
            returndm.interceptor.invoke(this);
       }
       else {
            //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
            return proceed();
       }
   }
   else {
       // It's an interceptor, so we just invoke it: The pointcutwill have
       // been evaluated statically before this object was constructed.
       //执行当前Intercetpor
       return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
   }
}

多个切面的执行顺序及原理

当程序存在多个切面的时候,我们可以配置它们的顺序。

配置切面的执行顺序一般有以下几种方法:

  • 切面类实现org.springframework.core.Ordered接口。重写getOrder()方法,返回一个整数
  • 切面类添加@Order注解,指定一个整数,如@Order(1)

order越小越是最先执行,但是最先执行的最后结束。

由此得出:spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。

如果我们要在同一个方法事务提交后执行自己的AOP,那么把事务的AOP order设置为2,自己的AOP order设置为1,然后在doAfterReturn里边处理自己的业务逻辑。

 

posted @   残城碎梦  阅读(171)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示