Skip to content
On this page

Spring 第七讲 Aspect Advisor

pom.xml 准备

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/>
    </parent>

    <groupId>org.laoshiren</groupId>
    <artifactId>spring-bean-init</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>

        <aspectj.version>1.9.7</aspectj.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>


        <!-- 织入的依赖 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
        </dependency>

        <!-- 运行时需要的依赖 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
        </dependency>

    </dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

spring 中的代理

java
@Aspect
@Component("acMyAspect")
public class MyAspect {
    

    @Before("execution(* foo())")
    public void before(){
        System.out.println("before");
    }

    @Before("execution(* foo())")
    public void before2(){
        System.out.println("before2");
    }

    @After("execution(* foo())")
    public void after(){
        System.out.println("after");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

spring中有2中切面,一种是aspect,一种advisor , advisor是更细粒度的切面,只包含了一个通知(advice)和切点(pointcut) 而aspect 会将拆解成多个通知和切点。

Spring 中对切点、通知、切面的抽象如下

  • 切点:接口 Pointcut,典型实现 AspectJExpressionPointcut
  • 通知:典型接口为 MethodInterceptor 代表环绕通知
  • 切面:Advisor,包含一个 Advice 通知,PointcutAdvisor 包含一个 Advice 通知和一个 Pointcut
java
public class AspectMain {

    public static void main(String[] args) {
        IService iService = new MyService();
        // 准备切点
        // Pointcut
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* com.laoshiren.*..*.foo())");
        // 准备通知
        MethodInterceptor advice = (invocation)->{
            System.out.println("before");
            Object proceed = invocation.proceed();
            System.out.println("after");
            return proceed;
        };
        // 准备切面
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
        // 创建代理
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(iService);
        proxyFactory.addAdvisor(advisor);
        IService proxy = (IService)proxyFactory.getProxy();

        proxy.foo();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

选择规则

查看我们代理使用的是cglib 在什么时候才会使用JDK呢

java
public class ProxyConfig implements Serializable {
    
    private boolean proxyTargetClass = false;

}
1
2
3
4
5
  1. proxyTargetClass 是false 的时候,目标实现了接口,使用JDK
  2. proxyTargetClass 是false 的时候,目标没有实现接口,使用cglib
  3. proxyTargetClass 是true 的时候,总是使用cglib

上述代码为什么还是使用cglib 呢,是因为我们没有设置代理的接口 proxyFactory.setInterfaces(iService.getClass().getInterfaces())

切点匹配

判断哪些方法适合这个切点匹配

java
public class AspectMatchMain {

    public static void main(String[] args) throws NoSuchMethodException {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* com.laoshiren.*..*.foo())");
        Method methodFoo = MyService.class.getMethod("foo");
        boolean matches = pointcut.matches(methodFoo, MyService.class);
        System.out.println(matches);
    }
}
1
2
3
4
5
6
7
8
9
10

调用aspectj的匹配方法,比较关键额是它实现了MethodMatcher 接口用来执行匹配

java
public class AspectMatchMain {

    public static void main(String[] args) throws NoSuchMethodException {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* com.laoshiren.*..*.foo())");
        Method methodFoo = MyService.class.getMethod("foo");
        boolean matches = pointcut.matches(methodFoo, MyService.class);
        System.out.println(matches);

        AspectJExpressionPointcut pointcut2 = new AspectJExpressionPointcut();
        pointcut2.setExpression("@annotation(com.laoshiren.app.aspect.match.Ass)");
        boolean matches2 = pointcut2.matches(methodFoo, MyService.class);
        System.out.println(matches2);

        AnnotationMatchingPointcut annotationPointcut = new AnnotationMatchingPointcut(Ass.class);
        boolean matches1 = annotationPointcut
                .getMethodMatcher()
                .matches(methodFoo, MyService.class);
        System.out.println(matches1);


        StaticMethodMatcherPointcut staticPointcut = new StaticMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                // 检查方法上是否加了对应注解
                boolean present = MergedAnnotations
                        .from(method)
                        .isPresent(Ass.class);
                // 查看类上是否加了对应注解
                boolean present1 = MergedAnnotations
                        .from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
                        .isPresent(Ass.class);
                return present || present1;
            }
        };
        System.out.println(staticPointcut.matches(methodFoo, MyService.class));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

@Advisor 与 @Aspect

java
class Target1 {
    public void foo(){
        System.out.println("target1 foo");
    }
}


class Target2 {
    public void bar(){
        System.out.println("target1 bar");
    }
}

// 高级切面类
@Aspect
class Aspect1{

    @Before("execution(* foo())")
    public void before(){
        System.out.println("aspect1 before...");
    }


    @After("execution(* foo())")
    public void after(){
        System.out.println("aspect1 after...");
    }

}

@Configuration
class Config {
    // 低级切面类
    @Bean
    public Advisor advisor3(MethodInterceptor advice3) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        return new DefaultPointcutAdvisor(pointcut, advice3);
    }

    @Bean
    public MethodInterceptor advice3(){
        return invocation -> {
            System.out.println("advice before...");
            Object proceed = invocation.proceed();
            System.out.println("advice after...");
            return proceed;
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

AnnotationAwareAspectJAutoProxyCreator 的作用

  • 将高级 @Aspect 切面统一为低级 Advisor 切面
  • 在合适的时机创建代理
java
public class AdvisorMain {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(Aspect1.class);
        context.registerBean(Config.class);
        // 解析@Bean
        context.registerBean(ConfigurationClassPostProcessor.class);
        // Bean 处理器 将Aspect 高级切面转换低级切面 ,根据切面创建代理对象 (ProxyFactory)
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
        context.refresh();
        // 获取这个processor
        AnnotationAwareAspectJAutoProxyCreator proxyCreator
                = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        // 根据目标获取有资格的切面 看看那些advisor 可以应用于目标类(低级切面)
        // 高级切面会转换成低级切面
        List<Advisor> targetAdvisor = proxyCreator.findEligibleAdvisors(Target1.class, "target");
        for (Advisor advisor : targetAdvisor) {
            System.out.println(advisor.getClass().getName());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

输出如下

shell
  ## Spring 给所有代理都加的切面
  org.springframework.aop.interceptor.ExposeInvocationInterceptor$1
  ## Config类里的定义的切面
  org.springframework.aop.support.DefaultPointcutAdvisor
  ## 高级切面转换后的切面 @Before
  org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl
  ## 高级切面转换后的切面 @After
  org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl
1
2
3
4
5
6
7
8

findEligibleAdvisors 找到资格的 Advisors

  • 资格的 Advisor 一部分是低级的, 可以由自己编写, Config类 advisor3
  • 资格的 Advisor 另一部分是高级的, 由解析 @Aspect 后获得

调用之后返回集合不为空,则表示需要创建代理

java
public class AdvisorMain {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(Aspect1.class);
        context.registerBean(Config.class);
        // 解析@Bean
        context.registerBean(ConfigurationClassPostProcessor.class);
        // Bean 处理器 将Aspect 高级切面转换低级切面 ,根据切面创建代理对象 (ProxyFactory)
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
        context.refresh();
        // 获取这个processor
        AnnotationAwareAspectJAutoProxyCreator proxyCreator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        // 根据目标获取有资格的切面 看看那些advisor 可以应用于目标类(低级切面)
        // 高级切面会转换成低级切面
        List<Advisor> targetAdvisor = proxyCreator.findEligibleAdvisors(Target1.class, "target");
        // 代理对象 wrapIfNecessary 会内部调用FindEligibleAdvisors 判断是否需要进行转换低级切面再创建代理
        Object o = proxyCreator.wrapIfNecessary(new Target1(), "target1", "target1");
        // 原始对象
        Object o1 = proxyCreator.wrapIfNecessary(new Target2(), "target2", "target2");
        Target1 target1 = (Target1)o;
        target1.foo();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
shell
advice before...
aspect1 before...
target1 foo
aspect1 after...
advice after...
1
2
3
4
5

高级切面向低级切面的转换

首先我们拿到Aspect类之后,应该遍历里面的方法,将带有@Before@After等注解转换成低级的注解

java
@Aspect
class Aspect1 {
    @Before("execution(* foo())")
    public void before() {
        System.out.println("aspect1 before...");
    }
    @After("execution(* foo())")
    public void after() {
        System.out.println("aspect1 after...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
  1. 获取当前类的定义方法
  2. 查看方法是否存在对应的注解
  3. 创建对应的切点
  4. 创建通知
  5. 创建切面
java
public class AspectTransAdvisorMain {

    public static void main(String[] args) {
        List<Advisor> list = new ArrayList<>();        
        // Aspect的实例工场
        AspectInstanceFactory factory = new SimpleAspectInstanceFactory(Aspect1.class);
        // 获取Aspect切面类定义的所有方法
        Method[] declaredMethods = Aspect1.class.getDeclaredMethods();
        // 遍历数组准备转换成低级切面
        for (Method declaredMethod : declaredMethods) {
            // 查看方法上是否标注指定注解
            boolean beforeAnnotationPresent = declaredMethod.isAnnotationPresent(Before.class);
            if (beforeAnnotationPresent) {
                // 获取注解
                Before methodBeforeAnnotation = declaredMethod.getAnnotation(Before.class);
                String expression = methodBeforeAnnotation.value();
                // 创建切点对象
                AspectJExpressionPointcut expressionPointcut = new AspectJExpressionPointcut();
                expressionPointcut.setExpression(expression);
                // 前置通知底层通知类 AspectJMethodBeforeAdvice
                AspectJMethodBeforeAdvice beforeAdvice = new AspectJMethodBeforeAdvice(
                        // 需要前置通知的方法
                        declaredMethod,
                        // 切点
                        expressionPointcut,
                        // 切面的实例工程
                        factory
                );
                // 切面
                Advisor advisor = new DefaultPointcutAdvisor(expressionPointcut, beforeAdvice);
                list.add(advisor);
            }
        }
        System.out.println(list.size());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

创建通知的代码包含了如下信息

  1. 通知的代码从哪来 (即需要通知的代码的方法)
  2. 切点
  3. 通知对象如何创建 对应不同的Aspect类
通知注解通知
前置@BeforeAspectJMethodBeforeAdvice
环绕通知@AroundAspectJAroundAdivce
返回后通知@AfterReturningAspectAfterReturingAdvice
throw通知@AfterThrowingAspectAfterThrowingAdvice
after通知@AfterAspectJAfterAdvice

Released under the MIT License.