天天看点

4、基于XML的AOP配置

一、面向切面编程(AOP)

1、AOP概述

1、面向切面编程(Aspect Oriented Programming,简称AOP)通过提供另一种思考程序结构的方式来补充面向对象编程 (Object Oriented Programming,简称OOP)。

2、OOP中模块化的关键单元是类,而在AOP中模块化的单元是切面。切面支持跨多种类型和对象的关注点(在AOP中通常称为“横切”关注点,例如事务管理)的模块化。

3、在OOP编程模式中,OOP将不同的业务对象的抽象成为一个个的类,不同的业务操作抽象成不同的方法,这样的好处是能更加清晰高效的逻辑单元划分!一个完整的业务逻辑就是调用不同的对象、方法来组合完成,这类似于流水线,核心业务和非核心业务都在里面,每一个步骤按照顺序执行。这样看来,业务逻辑之间的耦合关系非常严重,核心业务的代码之间通常需要手动嵌入大量非核心业务的代码,比如日志记录、事务管理。对于这种跨对象和跨业务的重复的、公共的非核心逻辑,OOP没有特别好的处理方式。

4、在AOP编程模式中,AOP能将不同业务流程中的相同的非核心业务逻辑从源代码中彻底抽离出来,形成一个独立的服务(比如日志记录、权限校验、异常处理、事物机制)。而当程序在编译或运行的时候,又能在不修改源代码的情况下,动态的选择在程序执行流程中的某些地方,比如方法运行前后,抛出异常时等,将这些非核心服务逻辑插入到核心代码逻辑中。

5、AOP技术让业务中的核心模块和非核心模块的耦合性进一步降低,实现了代码的复用,减少了代码量,提升开发效率,并有利于代码未来的可扩展性和可维护性。

2、AOP术语

1、

Aspect(切面或方面)

:切入点(Pointcut)和该位置的通知(Advice)的结合,也可以说是被抽离出来的公共业务模块(比如:日志记录)。 对应Java代码中被

@AspectJ

注解标注的切面类或者在

XML

中配置的切面。

2、

Join Point(连接点)

:程序执行时的一些特定位置或点位,这些点位就是可能被AOP框架拦截并织入代码的地方;连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,

Spring只支持方法执行连接点

3、

Point Cut(切入点)

:用来匹配要进行切入的Join point集合的表达式,通过切点表达式(pointcut expression,类似正则表达式)可以确定符合条件的连接点作为切入点。

4、

Advice(通知)

:在连接点上执行的行为,提供了在AOP中需要在切入点(Pointcut)所选择的连接点(Join point)处进行扩展现有行为的手段。

5、

Introduction(引介)

:一种特殊的通知,在不修改类代码的前提下,可以在运行期为类动态地添加一些额外的方法或属性。

6、

Target Object(目标对象)

:需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为被通知对象;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象。

7、

AOP Proxy(AOP代理)

:一个类被AOP织入增强后,就会产生一个代理对象。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。

8、

Weaving(织入)

:是指把切面应用到目标对象来创建新的代理对象的过程。织入的时期可以是编译时织入(AspectJ),也可以使用运行时织入(Spring AOP)。

3、AOP中五种通知类型

1、

Before advice(前置通知)

:在切入点方法之前执行的通知,但这个通知不能阻止切入点之前的执行流程,除非它抛出一个异常。

2、

After Returning advice(后置通知)

:在切入点方法正常执行完成后要执行的通知(比如:一个方法没有抛出任何异常,正常返回)。

3、

After Throwing advice(异常通知)

:在切入点方法抛出异常而退出时执行的通知。

4、

After Finally advice(最终通知)

:无论切入点方法正常返回还是异常返回,都要执行的通知。

5、

Around advice(环绕通知)

:可以在切入点方法调用前后完成自定义的行为。它也会选择是否继续执行或直接返回它自己的返回值或抛出异常来结束执行。

4、Spring AOP与AspectJ

1、AspectJ:是由Eclipse开源的一个AOP框架,基于Java平台,致力于提供了最完整的AOP实现方式,官方地址。

2、Spring AOP:是Spring提供的一个AOP框架。目的并不是提供最完整的AOP实现,相反,其目的是在AOP实现和SpringIOC之间提供紧密的集成,以帮助解决企业应用程序中的大多数常见的需求和问题(方法织入)。

3、Spring支持无缝集成AspectJ框架,因此也能使用AspectJ的全部功能;Spring2.0以后新增了对AspectJ切点表达式的支持,AspectJ框架在1.5版本时,通过JDK5的注解技术引入了一批注解,比如@AspectJ、@Pointcut、相关Advice注解,支持使用注解的声明式方式来定义切面,Spring同样支持使用和AspectJ相同的注解。

4、Spring AOP相比于AspectJ,它的学习难度更低,更容易上手。

5、织入方式

1、

AspectJ属于静态织入

:它使用了专门的称为AspectJ编译器 (ajc) 的编译器,在Java源码被编译的时候,就将切面织入到目标对象所属类的字节码文件中,并不会生成新的代理类字节码。因此,AspectJ在运行时不做任何事情,没有任何额外的开销,因为切面在类编译的时候就织入了。

2、

Spring AOP属于动态织入

:在运行时动态将要增强的代码织入到目标类中,是通过

动态代理

技术完成的,如

Java JDK的动态代理

(Proxy,底层通过反射实现)或者

CGLIB的动态代理

(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术。

6、JDK动态代理与CGLIB动态代理

1、

JDK动态代理

:JDK动态代理是由JDK提供的工具类Proxy实现的,动态代理类是在运行时生成指定接口的代理类,每个代理实例(实现需要代理的接口)都有一个关联的调用处理程序对象,此对象实现了InvocationHandler,最终的业务逻辑是在InvocationHandler实现类的invoke方法上。

2、

CGLIB动态代理

:是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承的方式实现代理。

3、两者对比:

  • CGLIB相比于JDK动态代理更加强大,JDK动态代理只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么JDK动态代理就没法使用了。
  • CGLIB原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型。
  • 从执行效率上看,CGLIB动态代理效率较高。
  • Spring AOP的默认代理方式是JDK代理,在SpringBoot2.x版本中默认采用的是CGLIB代理

4、请参考设计模式中的代理模式

二、XML方式实现AOP

1、引入AOP依赖

1、Spring AOP需要引入两个依赖

spring-aop

aspectjweaver

2、可以直接引入

spring-context

依赖,因为它已经引入了其他核心的依赖

spring-aop、spring-beans、spring-core、spring-expression

等。

3、

aspectjweaver

用来解析切入点表达式的,因为Spring支持AspectJ的切入点表达式的语法。
<!--spring 核心组件所需依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

<!--用于解析AspectJ的切入点表达式语法-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>
           

2、添加AOP名称空间

1、Spring的原始配置文件仅支持IoC的配置,如果想要使用aop的XML配置,我们需要手动引入AOP名称空间(

xmlns:aop

),然后就能使用aop相关标签。

2、aop标签用于配置Spring中的所有AOP,包括Spring自己的基于代理的AOP框架和Spring与AspectJ 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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd">

</beans>
           

3、简单示例

/**
 * @Date: 2023/1/6
 * 切面类
 * 注意:切面类需要注入到IOC容器中
 */
@Component
public class LogAspect {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("前置通知");
    }

    /**
     * 异常通知
     */
    public void afterThrowing(Exception e) {
        System.out.println("异常通知,异常为:" + e.getMessage());
    }

    /**
     * 后置通知
     */
    public void afterReturning(Object result) {
        System.out.println("后置通知,返回值为:" + result);
    }

    /**
     * 最终通知
     */
    public void afterFinally() {
        System.out.println("最终通知");
    }

    /**
     * 环绕通知
     * 一定要有ProceedingJoinPoint类型的参数
     */
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕通知 --- 进入方法");
        // 需要手动放行方法,否则方法不会执行
        Object proceed = point.proceed();
        System.out.println("环绕通知 --- 退出方法");
        return proceed;
    }
}
           
/**
 * @Date: 2023/1/6
 * 目标类,也需要注入到IOC容器中
 */
@Service
public class CalculateService {
    public Integer add(int i, int j) {
        return i + j;
    }

    public int sub(int i, int j) {
        return i + j;
    }

    public int multiply(int i, int j) {
        return i * j;
    }

    public double divide(int i, int j) {
        return i / j;
    }
}
           
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.itan.aop.*" />

    <!-- aop的相关配置 -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="logAspect">
            <!-- 配置切点 -->
            <aop:pointcut id="pointCutMethod" expression="execution(* com.itan.aop.service.*.*(..))"/>
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pointCutMethod" />
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="pointCutMethod" />
            <!-- 异常通知,如果没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的的名称,类型是Throwable,它是所有错误和异常类的顶级父类 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointCutMethod" throwing="e" />
            <!-- 后置通知,returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
            <aop:after-returning method="afterReturning" pointcut-ref="pointCutMethod" returning="result" />
            <!-- 最终通知 -->
            <aop:after method="afterFinally" pointcut-ref="pointCutMethod" />
        </aop:aspect>
    </aop:config>
</beans>
           
/**
 * @Date: 2023/1/6
 * 测试类
 */
public class AopTest1 {
    @Test
    public void test01() {
        // 通过配置文件创建容器对象
        ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
        CalculateService calc = context.getBean(CalculateService.class);
        calc.add(2,3);
        System.out.println("==========================");
        calc.divide(2,0);
    }
}
/**
 * 运行结果:
 * 环绕通知 --- 进入方法
 * 前置通知
 * 环绕通知 --- 退出方法
 * 后置通知,返回值为:5
 * 最终通知
 * ==========================
 * 环绕通知 --- 进入方法
 * 前置通知
 * 异常通知,异常为:/ by zero
 * 最终通知
 */
           

4、aop:config配置

1、aop相关的配置都写在

<aop:config>

标签中,用于实现Spring自动代理机制。

2、

<aop:config>

标签中可以包含

<aop:pointcut>、<aop:advisor>、<aop:aspect>

等标签,

必须要按照该顺序声明这些标签

3、标签属性说明:

  • proxy-target-class

    是否为被代理类生成CGLIB子类,只为接口生成代理子类(即:是否使用CGlib动态代理),默认为false,使用的是JDK代理

  • expose-proxy

    是否将代理的Bean暴露给用户,如果暴露,就可以通过AopContext类获得,默认不暴露

5、Spring AOP使用的代理方式

1、

Spring AOP的默认代理方式是JDK动态代理,如果目标对象没有实现任何接口,则会选择使用CGLIB代理

2、如果实现了接口,可以通过

proxy-target-class

属性强制使用CGLIB代理。
/**
 * @Date: 2023/1/6
 * 接口类
 */
public interface CalculateService {
    Integer add(int i, int j);

    int sub(int i, int j);

    int multiply(int i, int j);

    double divide(int i, int j);
}

/**
 * @Date: 2023/1/6
 * 接口类实现类
 */
@Service
public class CalculateServiceImpl implements CalculateService {
    public Integer add(int i, int j) {
        return i + j;
    }

    public int sub(int i, int j) {
        return i + j;
    }

    public int multiply(int i, int j) {
        return i * j;
    }

    public double divide(int i, int j) {
        return i / j;
    }
}
           
<!-- aop的相关配置 -->
<aop:config>
    <aop:pointcut id="pointCutMethod" expression="execution(* com.itan.aop.*.*.*(..))" />
    <!-- 配置切面 -->
    <aop:aspect ref="logAspect">
        <!-- 前置通知 -->
        <aop:before method="before" pointcut-ref="pointCutMethod" />
        <!-- 最终通知 -->
        <aop:after method="afterFinally" pointcut-ref="pointCutMethod" />
    </aop:aspect>
</aop:config>
           
@Test
public void test01() {
    // 通过配置文件创建容器对象
    ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
    CalculateService calc = context.getBean(CalculateService.class);
    System.out.println(calc.getClass());
    calc.add(2,3);
}
/**
 * 运行结果:可以看到返回的是一个com.sun.proxy.$Proxy16类型的,说明默认使用的是JDK代理
 * class com.sun.proxy.$Proxy16
 * 前置通知
 * 最终通知
 */
           
1、强制使用CGLIB代理,修改XML中的配置为

<aop:config proxy-target-class="true">

,运行程序,发现使用CGLIB代理。
4、基于XML的AOP配置
2、目标对象不实现接口,将实现类去掉实现的接口

CalculateService

,运行程序,发现使用CGLIB代理。
4、基于XML的AOP配置

三、切面相关配置

1、aop:aspect切面

1、

<aop:aspect>

标签用于配置切面,里面可以包含

<aop:pointcut>、<aop:5种通知类型>

等标签,

必须要按照该顺序声明这些标签

2、标签属性说明:

  • id

    :一个切面的唯一标识符。
  • ref

    :用于引用外部专门定义的通知Bean(也就是切面类),切面类中定义了一系列通知的方法。
  • order

    :切面的排序,当有多个通知在同一个切入点执行时,指定通知执行的先后顺序,未指定该属性时默认值为Integer.MAX_VALUE;

    值越小的切面,其内部定义的前置通知越先执行,后置通知越后执行,相同的order的切面按照切面从上到下定义顺序先后执行前置通知,反向执行后置通知

/**
 * @Date: 2023/1/6
 * 第一个切面类
 */
@Component
public class OrderAspect1 {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("OrderAspect1前置通知");
    }

    /**
     * 后置通知
     */
    public void afterReturning(Object result) {
        System.out.println("OrderAspect1后置通知,返回值为:" + result);
    }
}
           
/**
 * @Date: 2023/1/6
 * 第二个切面类
 */
@Component
public class OrderAspect2 {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("OrderAspect2前置通知");
    }

    /**
     * 后置通知
     */
    public void afterReturning(Object result) {
        System.out.println("OrderAspect2后置通知,返回值为:" + result);
    }
}
           
<!-- aop的相关配置 -->
<aop:config>
    <aop:pointcut id="pointCutMethod" expression="execution(* com.itan.aop.service.*.*(..))" />
    <!-- 配置切面1,order值设置为1000 -->
    <aop:aspect ref="orderAspect1" order="1000">
        <!-- 前置通知 -->
        <aop:before method="before" pointcut-ref="pointCutMethod" />
        <!-- 后置通知,returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
        <aop:after-returning method="afterReturning" pointcut-ref="pointCutMethod" returning="result" />
    </aop:aspect>
    <!-- 配置切面2,order值设置为100 -->
    <aop:aspect ref="orderAspect2" order="100">
        <!-- 前置通知 -->
        <aop:before method="before" pointcut-ref="pointCutMethod" />
        <!-- 后置通知,returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
        <aop:after-returning method="afterReturning" pointcut-ref="pointCutMethod" returning="result" />
    </aop:aspect>
</aop:config>
           
@Test
public void test03() {
    // 通过配置文件创建容器对象
    ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
    CalculateService calc = context.getBean(CalculateService.class);
    calc.add(2,3);
}
/**
 * 运行结果:可以看到切面的order值越小的前置通知先执行,后置通知最后执行
 * OrderAspect2前置通知
 * OrderAspect1前置通知
 * OrderAspect1后置通知,返回值为:5
 * OrderAspect2后置通知,返回值为:5
 */
           

2、配置通知

1、

<aop:aspect>

切面标签中可以使用对应的5种通知标签:
  • <aop:before>

    :前置通知,目标方法执行之前执行。
  • <aop:after-returning>

    :后置通知,目标方法执行之后执行,目标方法异常时,不执行;后置通知的方法能够接收切入点方法的返回值作为参数,只需要配置returning属性,returning属性值就是后置通知方法的参数名,参数类型需要与返回值类型匹配(基本类型可以自动装拆箱,不能自动强转),或者向上兼容(可使用父类,父接口接收),否则后置通知不会被执行。
  • <aop:after-throwing>

    :异常通知,在前置通知、目标方法和后置通知中抛出异常之后可能会执行。异常通知的方法能够接收前置通知、切入点方法和后置通知中抛出的异常作为参数,只需要配置throwing属性,throwing属性值就是异常通知方法的参数名。参数类型需要与抛出的异常类型匹配或者向上兼容(可使用父类、父接口接收),否则异常通知不会被执行。
  • <aop:after>

    :最终通知,无论目标方法是否正常执行,它都会在后置通知或者异常通知后面执行。
  • <aop:around>

    :环绕通知,目标方法执行前后执行,目标方法异常时,环绕后方法不执行。
2、5种通知标签属性说明:
  • method

    :通知的方法名,在切面类中定义的通知方法。
  • pointcut-ref

    :用于指定切入点的表达式的引用。可以通过

    <aop:ponitcut>

    单独定义切入点。
  • pointcut

    :切入点表达式,用于指定该通知可以应用到的切入点集合。通过这个表达式可以匹配到某些方法作为该通知的切入点。
  • arg-names

    :按顺序使用“,”分隔的需要匹配的方法参数名字符串。用于配合切入点表达式,更加细致的匹配方法,更重要的是可以进行参数值的传递。
  • returning

    后置通知<aop:after-returning>的特殊参数,后置通知方法能够接收切入点方法的返回值作为方法入参,值就是参数名称

  • throwing

    异常通知<aop:after-throwing>的特殊参数,异常通知方法能够接收切入带点方法、前置通知、后置通知中抛出的异常作为方法入参,值就是参数名称

3、环绕通知(特殊)

1、环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点方法。

2、环绕通知使用

<aop:around>

标签配置,通常情况下,环绕通知都是独立使用的。

3、环绕通知的方法的第一个参数类型必须是

ProceedingJoinPoint

,它是

JoinPoint

的子接口,允许控制何时执行,是否执行连接点方法。

4、

在环绕通知方法中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。方法proceed()的返回值就是连接点方法的返回值;通知方法的返回值就是外部调用切入点方法获取的最终返回值,如果没有返回值,那么外部调用切入点方法获取的最终返回值为null;环绕通知的返回值类型应该和切入点方法的返回值类型一致或者兼容

4、基于XML的AOP配置
5、proceed方法中还可以传递一个数组,该数组就是切入点方法所需的参数。可以通过对

ProceedingJoinPoint

调用

getArgs

获取外部调用切入点方法时传递进来的参数数组,也可以在环绕通知的逻辑中自己设置参数。

4、JoinPoint

1、任何通知方法的第一个参数都可以声明为

org.aspectj.lang.JoinPoint

类型,JoinPoint是连接点方法的抽象,提供了访问当前被通知方法的目标对象,代理对象,方法参数等数据方法。

2、环绕通知的参数类型应该使用

ProceedingJoinPoint

,它是

JoinPoint

的一个实现;

所有传入的JoinPoint的实际类型都是MethodInvocationProceedingJoinPoint

3、JoinPoint的相关方法说明:

方法名 说明
String toString() 返回连结点方法的签名,返回值和参数类型使用简单类名
String toShortString() 返回连结点方法的简要签名,省略返回值、参数类型、类路径
String toLongString() 返回连结点方法的完整签名,返回值和参数类型使用全路径名
Object getThis() 返回当前AOP代理对象
Object getTarget() 返回当前AOP目标对象
Object[] getArgs() 返回当前被通知方法传递的实际参数值数组
Signature getSignature() 返回当前连结点方法的签名
SourceLocation getSourceLocation() 返回连接点方法所在类文件中的位置,相关方法不支持
String getKind() 返回当前连接点的类型。Spring AOP为method-execution
StaticPart getStaticPart() 返回连接点静态部分,实际上就是返回当前JoinPoint对象
4、ProceedingJoinPoint相关方法说明:
方法名 说明
Object proceed() throws Throwable 执行目标方法,默认使用外部传递的参数
Object proceed(Object[] args) throws Throwable 执行目标方法,使用该方法传递的数组的值作为参数

5、aop:pointcut切点表达式

1、每一个通知中,都可以配置自己的切入点表达式,使用

pointcut

属性,很多时候切入点表达式都是一样的,可以使用

<aop:pointcut>

标签定义一个独立的切入点表达式,使得多个切面和通知通过

pointcut-ref

属性引用同一个切入点表达式。

2、

<aop:pointcut>

标签属性说明:
  • id

    :用于给切入点表达式提供一个唯一标识,通过该标识引用对应的切面表达式。
  • expression

    :用于定义切入点表达式。
3、

<aop:pointcut>

标签作用位置:
  • 定义在

    <aop:aspect>

    标签内部,表示该表达式只能在当前切面内部的通知中引用。
  • 定义在

    <aop:config>

    标签内部,表示该表达式在所有切面的所有通知中都能引用。但是要定义在

    <aop:aspect>

    标签之前。
/**
 * @Date: 2023/1/29
 * 切面类,切面类需要注入到IOC容器中
 */
@Component
public class PointCutAspect {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("前置通知");
    }

    /**
     * 最终通知
     */
    public void afterFinally() {
        System.out.println("最终通知");
    }
}
           
/**
 * @Date: 2023/1/29
 * AOP目标切入点方法,需要注入到IOC容器中
 */
@Component
public class AopTargetPointcut {
    public void target1() {
        System.out.println("目标切入点方法1");
    }

    public void target2() {
        System.out.println("目标切入点方法2");
    }
}
           
<!-- aop的相关配置 -->
<aop:config>
    <!-- 配置一个所有切面的所有通知都能引用的表达式  aop:pointcut标签要定义在最前面 -->
    <aop:pointcut id="p1" expression="execution(* com.itan.aop.pointcut.AopTargetPointcut.target1())" />
    <!-- 配置切面1 -->
    <aop:aspect id="asp1" ref="pointCutAspect">
        <!-- 配置前置通知,引用切点表达式 -->
        <aop:before method="before" pointcut-ref="p1" />
    </aop:aspect>
    <!-- 配置切面2 -->
    <aop:aspect id="asp2" ref="pointCutAspect">
        <!-- 配置只能是当前切面内部的所有通知都能引用的表达式 -->
        <aop:pointcut id="p2" expression="execution(* com.itan.aop.pointcut.AopTargetPointcut.target2())" />
        <!-- 配置前置通知,引用切点表达式 -->
        <aop:before method="before" pointcut-ref="p2" />
        <!--pointcut-ref 引用切面表达式-->
        <aop:after method="afterFinally" pointcut-ref="p1" />
    </aop:aspect>
</aop:config>
           
@Test
public void test03() {
    // 通过配置文件创建容器对象
    ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
    AopTargetPointcut atp = context.getBean(AopTargetPointcut.class);
    // 调用方法
    atp.target1();
    System.out.println("============================");
    atp.target2();
}
/**
 * 运行结果如下:调用target1方法会执行前置和后置通知,调用target2方法只会执行前置通知,与配置保持一致
 * 前置通知
 * 目标切入点方法1
 * 最终通知
 * ============================
 * 前置通知
 * 目标切入点方法2
 */
           

四、切入点声明规则

1、切入点指示符

1、前面定义切点表达式时使用了大量的execution表达式,其中execution就是一个切入点指示符(pointcut designators,简称PCD),由于在Spring AOP中目前只支持方法调用作为连接点,所以Spring AOP的切入点指示符仅匹配方法执行的连接点。

2、Spring 5.x的AOP支持使用如下切入点指示符:

  • execution

    :通过匹配某些类型的某些方法签名来匹配连接点方法。
  • within

    :限定匹配特定类型的连接点方法。
  • this

    :通过匹配AOP的代理对象的类型来匹配连接点方法。
  • target

    :通过匹配AOP的目标对象类型来匹配连接点方法。
  • args

    :通过匹配方法参数的数量、类型、顺序来匹配连结点方法。
  • bean

    :通过匹配指定Bean实例内的连接点方法。
  • @target

    :通过匹配类型上的某些注解类型来匹配连接点方法。
  • @args

    :通过匹配方法参数的所属类型上的某些注解来匹配连结点方法。
  • @within

    :通过匹配类型及子类型上的某些注解类型来匹配连接点方法。
  • @annotation

    :通过匹配方法上的某些注解类型类匹配连接点方法。
3、切入点表达式还支持如下通配符:
  • *

    :任意数量的字符。
  • ..

    :包名与类名之间使用表示该包及其子包下的所有类;参数列表中使用表示任意参数。
  • +

    :该类型及其子类型。
4、切入点表达式还支持运算符:
  • &&(and)

    :表示两个条件都要匹配。
  • ||(or)

    :表示两个条件任意匹配一个。
  • !(not)

    :表示不能匹配该条件。

2、常用的execution指示符

1、execution的切入点表达式使用方法的签名来匹配切入点方法,是使用最多的一种方式。

2、语法格式说明:

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

4、基于XML的AOP配置
  • modifiers-pattern

    :方法的访问修饰符(可选)。
  • ret-type-pattern

    :方法的返回值类型全路径名,java.lang包下的类可简写类名。
  • declaring-type-pattern

    :方法所属类的全路径名(可选)。
  • name-pattern

    :方法名。
  • param-pattern

    :方法的参数列表类型的全路径类名,多个参数类型使用逗号分隔,java.lang包下的可以简写成类名,按照指定顺序匹配。
  • throws-pattern

    :方法的异常类型的全路径类名,多个参数类型使用逗号分隔,java.lang包下的可以简写成类名(可选)。
  • 带有?的表示可选项,表达式中匹配的类型都是声明的类型,而不是实际类型,且不会向下兼容,可以使用'+'向下兼容

// 任意公共方法的执行:
execution(public * *(..))

// 任何一个名字以“set”开始的方法的执行:
execution(* set*(..))

// AccountService接口定义的任意方法的执行:
execution(* com.itan.service.AccountService.*(..))

// 在service包中定义的任意方法的执行:
execution(* com.itan.service.*.*(..))

// 在service包或其子包中定义的任意方法的执行:
execution(* com.itan.service..*.*(..))

// 在service包中的任意连接点(在Spring AOP中只是方法执行):
within(com.itan.service.*)

// 在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):
within(com.itan.service..*)

// 实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):
this(com.itan.service.AccountService)// 'this'在绑定表单中更加常用

// 实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):
target(com.itan.service.AccountService) // 'target'在绑定表单中更加常用

/**
 * 任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行)
 * 'args'在绑定表单中更加常用; 请注意在例子中给出的切入点不同于execution(* *(java.io.Serializable)): 
 * args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。
 */
args(java.io.Serializable)

// 目标对象中有一个 @Transactional 注解的任意连接点 (在Spring AOP中只是方法执行)
@target(org.springframework.transaction.annotation.Transactional)// '@target'在绑定表单中更加常用

// 任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行):
@within(org.springframework.transaction.annotation.Transactional) // '@within'在绑定表单中更加常用

// 任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行)
@annotation(org.springframework.transaction.annotation.Transactional) // '@annotation'在绑定表单中更加常用

// 任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行)
@args(com.itan.security.Classified) // '@args'在绑定表单中更加常用

// 任何一个在名为'tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行)
bean(tradeService)

// 任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行)
bean(*Service)
           

继续阅读