4.6.2 在 Spring 中启用 AspectJ 注解支持
• 要在 Spring 应用中使用 AspectJ 注解, 必须在 classpath 下包含 AspectJ 类库.
• 将 Schema 下的 aop 添加到 <beans> 根元素中.
• 要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>, 称之为AOP的自动代理
• 当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.
4.6.3 用 AspectJ 注解声明切面
• 要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.
• 在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
• 通知是标注有某种注解的简单的 Java 方法.
• AspectJ 支持 5 种类型的通知注解:
-- @Before: 用于配置前置通知。指定增强的方法在切入点方法(⽬标⽅法)之前执⾏.
-- @After: 用于配置后置通知。指定增强的方法在切入点方法(⽬标⽅法)之后执⾏, ⽆论是否有异常都会执⾏.
-- @AfterReturning: 用于配置返回后通知。指定增强的方法在切入点方法(⽬标⽅法)之后执⾏, 有异常不会执⾏.
-- @AfterThrowing:用于配置异常抛出通知。指定增强的方法在出现异常时执行.
-- @Around: 用于配置环绕通知。指定增强的方法在切入点方法(⽬标⽅法)之前和之后都执行.
通知的配置语法:@通知注解("切点表达式")
4.6.4 切入点表达式说明
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
• 访问修饰符可以省略(修饰符加了中括号, 代表可以省略)
• 返回值类型、包名、类名、方法名可以使用星号 * 代表任意
• 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
• 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表(如果参数有一个就写一个, 有两个就写两个, 不过可以使用 .. 表示任意个数)
例如:
全匹配方式:
execution(public void com.sy.aop.Target.method(参数))
访问修饰符可以省略:
execution( void com.sy.aop.Target.method(com.sy.pojo.User))
返回值可以使用*号,表示任意返回值:
execution( * com.sy.aop.Target.method(com.sy.pojo.User))
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
execution( * *.*.*.Target.method(com.sy.pojo.User))
使用..来表示当前包,及其子包
execution( * com..Target.method(com.sy.pojo.User))
类名可以使用*号,表示任意类
execution( * com..*.method(com.sy.pojo.User))
方法名可以使用*号,表示任意方法
execution( * com..*.*(com.sy.pojo.User))
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
execution( * com..*.*(*))
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
execution( * com..*.*(..))
全通配方式:
execution(* *..*.*(..))
注意:
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.sy.service.impl.*.*(..))
五、Spring基于注解的AOP配置
基于注解的aop开发步骤:
-
创建目标接口和目标类(内部有切点)
-
创建切面类(内部有增强方法)
-
将目标类和切面类的对象创建权交给 spring
-
在切面类中使用注解配置织入关系
-
在配置文件中开启组件扫描和 AOP 的自动代理
-
测试程序
构建Maven工程并添加依赖
XML
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
创建 Spring 的配置文件并导入约束
XML
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解扫描器-->
<context:component-scan base-package="com.study"></context:component-scan>
<!--配置基于注解的AspactJ(AOP的自动代理)-->
<aop:aspectj-autoproxy> </aop:aspectj-autoproxy>
</beans>
前置后置 环绕 异常
java
// 切面类
@Component //创建当前对象并存入IOC容器
@Aspect //标注当前MyAspect类是一个切面类
@Order(3)
public class MyAspect {
/**
* 抽取切点表达式(重用切入点表达式): 根据方法名复用即可
* 声明切入点
*/
@Pointcut("execution(* com.sy.service.*.*(..))")
public void myPointcut(){
}
/**
* 在add方法执行前,执行beforeMethod方法
* 目标方法前执行的通知,前置通知
* JoinPoint: 连接的对象,该对象包含了与目标方法相关的一些信息
* execution: 配置切入点表达式,指定切入点
*/
// @Before("execution(public int com.sy.service.impl.CalculatorServiceImpl.add(int,int))")
@Before("myPointcut()")
public void beforeMethod(JoinPoint joinPoint){
//获取目标方法名
String method = joinPoint.getSignature().getName();
//获取目标方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("MyAspect ==> 这是一个beforeMethod方法, 在 " + method + " 方法前执行了, 参数有:"+ Arrays.asList(args));
}
/**
* 目标方法后执行的通知,后置通知
* 1.目标方法不管有没有执行异常,后置通知都会执行
* 2.后置通知获取不到目标方法的返回值
*/
// @After("execution(* com.sy.service.impl.CalculatorServiceImpl.*(..))")
@After("myPointcut()")
public void afterMethod(JoinPoint joinPoint){
//获取目标方法名
String method = joinPoint.getSignature().getName();
//获取目标方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("MyAspect ==> 这是一个afterMethod方法, 在 " + method + " 方法后执行了, 参数有:"+ Arrays.asList(args));
}
/**
* 目标方法正常执行结束后;返回通知
* returning:用于指定接收目标方法的返回值,必须与通知方法的形参名一致
*/
// @AfterReturning(value = "execution(* com.sy.service.*.*(..))",returning = "result")
@AfterReturning(value = "myPointcut()", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
//获取方法名
String method = joinPoint.getSignature().getName();
//获取方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("MyAspect ==> 这是一个afterReturningMethod方法, 在 " + method + " 方法返回结果后执行了, 参数有:"+ Arrays.asList(args)+ ", 结果是:" + result);
}
/**
* 目标方法抛出异常后执行:异常通知
* throwing:用于指定接收目标方法的异常信息,必须与通知方法的形参名一致
*/
// @AfterThrowing(value = "execution(* com.sy.service.*.*(..))", throwing = "e")
@AfterThrowing(value = "myPointcut()", throwing = "e")
public void afterThrowingMethod(JoinPoint joinPoint, Exception e){
//获取方法名
String method=joinPoint.getSignature().getName();
//获取方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("MyAspect ==> 这是一个afterThrowingMethod方法, 在" + method + "方法执行异常后执行了, 参数有:" + Arrays.asList(args) + ", 异常是:" + e);
}
/**
* 环绕着整个目标方法执行:环绕通知
* 环绕通知综合了前置 后置 返回 异常 四个通知的功能
*/
// @Around("execution(* com.sy.service.impl.CalculatorServiceImpl.*(..))")
@Around("myPointcut()")
public Object aroundMethod(ProceedingJoinPoint point){
try {
// 1.目标方法前执行的通知:前置通知
String method = point.getSignature().getName();
Object[] args = point.getArgs();
System.out.println("前置通知 ==> 目标方法为:" + method + " -- 参数有: " + Arrays.asList(args));
//point.proceed():执行目标方法
Object result = point.proceed();
// 3.返回通知
System.out.println("返回通知 ==> 目标方法为:" + method + " -- 参数有: " + Arrays.asList(args) + " -- 结果为: " + result);
return result;
} catch (Throwable throwable) {
throwable.printStackTrace();
// 4.异常通知
System.out.println("异常通知 ==> 目标方法为:" + point.getSignature().getName() + ", 异常是: " + throwable);
}finally {
// 2.目标方法后执行的通知:后置通知
String method = point.getSignature().getName();
Object[] args = point.getArgs();
System.out.println("后置通知 ==> 目标方法为: " + method + " -- 参数有: " + Arrays.asList(args));
}
return null;
}
}