10. Spring AOP源码解析

本文深入探讨了Spring AOP的基础知识,包括AOP的核心概念、切点配置规则,以及如何通过XML和注解实现AOP。文章详细讲解了Spring AOP中的BeanPostProcessor、TargetSource、Advice、Advisor、Advised等核心类,阐述了它们在AOP中的作用。此外,还剖析了Spring AOP的初始化流程,从寻找入口、选择代理策略到生成AOP代理类,直至执行代理方法并触发AOP通知。通过对Spring AOP源码的分析,帮助读者更好地理解和运用AOP。
摘要由CSDN通过智能技术生成

10 Spring AOP源码解析

目录

10 Spring AOP源码解析

Pt1 Spring AOP基础知识

Pt1.1 AOP核心概念

Pt1.2 AOP切点配置规则

(1) execution

(2) within

(3) args

Pt2 Spring AOP使用示例

Pt2.1 AOP日志(XML)

pom.xml

spring.xml配置AOP信息

加载spring.xml

定义切面

定义Controller

运行Controller

Pt2.2 JDBC(Annotation)

pom.xml

AOP配置

Controller

测试请求

Pt3 Spring AOP核心类

Pt3.1 BeanPostProcessor

Pt3.2 TargetSource

Pt3.3 Advice

Pt3.4 Advisor

Pt3.5 Advised

(1) TargetClassAware

(2) Advised

(3) ProxyConfig

(4) AdvisedSupport

(5) ProxyCreatorSupport

(6) ProxyFactory

Pt3.6 Pointcut

Pt3.7 ClassFilter

Pt3.8 MethodMatcher

Pt3.9 Aware

Pt4 Spring AOP初始化剖析

Pt4.1 寻找入口

Pt4.2 选择代理策略

Pt4.3 生成AOP代理类

Pt4.4 执行代理方法

Pt4.5 触发AOP通知

Pt4.6 流程图

参考资料


Pt1 Spring AOP基础知识

AOP是Aspect Oriented Programming的缩写,意思是面向切面编程,通过预编译和运行时的动态代理,实现在不修改源代码的情况下给程序动态添加功能。同时AOP也为服务方和调用方实现解耦提供了很有效的方式。

在日常编码中,通常分为业务代码和非业务代码,比如事务、日志等,这些代码和业务代码混在一起。举个最常见的例子,就是JDBC的事务处理,有大量的模板代码是的事务处理代码,这些大量重复的、复制粘贴的、和业务处理无关的代码,为维护带来了风险和困难。AOP通过代理机制,可以将这些非业务代码剥离,使得他们可以独立于业务代码存在,从而降低维护的难度和风险。

所以,Spring AOP是一种编程范式,主要目的是将非功能性需求从功能型需求中分离出来,达到解耦的目标。

 


Pt1.1 AOP核心概念

  • Aspect(切面)

    Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

     

  • Jointpoint(连接点)

    表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。

     

  • Pointcut(切点)

    表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。

     

  • Advice(增强)

    Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

    • @Before:该注解标注的方法在业务模块代码执行之前执行,其不能阻止业务模块的执行,除非抛出异常;

    • @AfterReturning:该注解标注的方法在业务模块代码执行之后执(正常返回)行;

    • @AfterThrowing:该注解标注的方法在业务模块抛出指定异常后执行;

    • @After:该注解标注的方法在所有的Advice执行完成后执行,无论业务模块是否抛出异常,类似于finally的作用;

    • @Around:该注解功能最为强大,其所标注的方法用于编写包裹业务模块执行的代码,其可以传入一个ProceedingJoinPoint用于调用业务模块的代码,无论是调用前逻辑还是调用后逻辑,都可以在该方法中编写,甚至其可以根据一定的条件而阻断业务模块的调用;

    • @DeclareParents:其是一种Introduction类型的模型,在属性声明上使用,主要用于为指定的业务模块添加新的接口和相应的实现。

    • @Aspect:严格来说,其不属于一种Advice,该注解主要用在类声明上,指明当前类是一个组织了切面逻辑的类,并且该注解中可以指定当前类是何种实例化方式,主要有三种:singleton、perthis和pertarget,具体的使用方式后面会进行讲解。

     

  • Target(目标对象)

    织入 Advice 的目标对象.。目标对象可以被一个或者多个切面织入。

     

  • Weaving(织入)

    将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程,就是执行method同时执行advice。

     

  • AOP代理(AopProxy)

    在Spring中有两种形式的代理:JDK动态代理和CGLib代理。后面会详细介绍。

 


Pt1.2 AOP切点配置规则

(1) execution

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。

 

如下是execution表达式的语法:

 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

这里问号表示当前项可以有也可以没有,其中各项的语义如下:

  • modifiers-pattern:方法的可见性,如public,protected;

  • ret-type-pattern:方法的返回值类型,如int,void等;

  • declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;

  • name-pattern:方法名类型,如buisinessService();

  • param-pattern:方法的参数类型,如java.lang.String;

  • throws-pattern:方法抛出的异常类型,如java.lang.Exception;

 

如下是一个使用execution表达式的例子:

 execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))

上述切点表达式将会匹配使用public修饰,返回值为任意类型,并且是com.spring.BusinessObject类中名称为businessService的方法,方法可以有多个参数,但是第一个参数必须是java.lang.String类型的方法。上述示例中我们使用了..通配符,关于通配符的类型,主要有两种:

 

  • *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。

如下示例表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法:

 execution(* com.spring.service.BusinessObject.*())

下述示例表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法:

 execution(* com.spring.service.Business*.*())

 

  • ..通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。

如下示例表示匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数:

 execution(* com.spring.service..*.businessService())

这里需要说明的是,包路径service...businessService()中的..应该理解为延续前面的service路径,表示到service路径为止,或者继续延续service路径,从而包括其子包路径;后面的.businessService(),这里的*表示匹配一个单词,因为是在方法名前,因而表示匹配任意的类。

如下示例是使用..表示任意个数的参数的示例,需要注意,表示参数的时候可以在括号中事先指定某些类型的参数,而其余的参数则由..进行匹配:

 execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))

 

(2) within

within表达式的粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕。如下是within表达式的语法:

 within(declaring-type-pattern)

 

within表达式只能指定到类级别,如下示例表示匹配com.spring.service.BusinessObject中的所有方法:

 within(com.spring.service.BusinessObject)

 

within表达式路径和类名都可以使用通配符进行匹配,比如如下表达式将匹配com.spring.service包下的所有类,不包括子包中的类:

within(com.spring.service.*)

 

如下表达式表示匹配com.spring.service包及子包下的所有类:

within(com.spring.service..*)

 

(3) args

args表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么。这里需要注意的是,args指定的参数必须是全路径的。如下是args表达式的语法:

args(param-pattern)

 

如下示例表示匹配所有只有一个参数,并且参数类型是java.lang.String类型的方法:

args(java.lang.String)

 

也可以使用通配符,但这里通配符只能使用..,而不能使用*。如下是使用通配符的实例,该切点表达式将匹配第一个参数为java.lang.String,最后一个参数为java.lang.Integer,并且中间可以有任意个数和类型参数的方法:

args(java.lang.String,..,java.lang.Integer)

 


Pt2 Spring AOP使用示例

Pt2.1 AOP日志(XML)

  • pom.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 https://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.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

 

  • spring.xml配置AOP信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 自动扫描装配 -->
    <context:component-scan base-package="com.example.springdemo"/>

    <!--启用spring的一些annotation -->
    <context:annotation-config/>

    <!-- AOP配置 -->
    <bean id="logAspect" class="com.example.springdemo.aop.log.LogAspect"></bean>

    <aop:config>
        <aop:aspect id="log" ref="logAspect">
            <aop:pointcut id="logPointcut" expression="execution(* com.example.springdemo.controller.LogController.LogDemo(..))"/>
            <aop:before method="logBefore" pointcut-ref="logPointcut"/>
            <aop:after method="logAfter" pointcut-ref="logPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

 

  • 加载spring.xml

@Configuration
@ImportResource(locations = {"classpath:spring.xml"})
public class ConfigClass {
}

 

  • 定义切面

public class LogAspect {

    public LogAspect() {
        System.out.println("init");
    }

    public void logBefore() {
        System.out.println("Before execution log.");
    }

    public void logAfter() {
        System.out.println("After execution log.");
    }
}

 

  • 定义Controller

@RestController
public class LogController {

    @RequestMapping(value = "logAop", method = RequestMethod.GET)
    public String LogDemo() {
        return "Execute Log Aop test.";
    }
}

 

  • 运行Controller

Before execution log.
After execution log.

 


Pt2.2 JDBC(Annotation)

  • pom.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 https://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.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

 

  • AOP配置

@Component
@Aspect
public class JdbcAspect {

    // 定义切点
    @Pointcut("execution(* com.example.springdemo.controller.JdbcController.jdbcDemo(..))")
    public void jdbcPointcut() {
    }

    @Before("JdbcAspect.jdbcPointcut()")
    public void jdbcBefore() {
        System.out.println("begin transaction");
    }

    @AfterReturning("JdbcAspect.jdbcPointcut()")
    public void jdbcAfter() {
        System.out.println("commit transaction");
    }

    @AfterThrowing("JdbcAspect.jdbcPointcut()")
    public void jdbcException() {
        System.out.println("rollback transaction");
    }
}

 

  • Controller

@RestController
public class JdbcController {

    @RequestMapping(value = "jdbcAop", method = RequestMethod.GET)
    public String jdbcDemo() {
        return "Execute jdbc Aop test.";
    }
}

 

  • 测试请求

​​​​​​​

begin transaction 
commit transaction

 

 


Pt3 Spring AOP核心类

Pt3.1 BeanPostProcessor

BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口,它可以监听容器触发的Bean生命周期事件。向容器中注册BeanPostProcessor之后,容器中管理

  • 7
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值