创建名为spring_aop_annotation的新module,过程参考9.1节
注意:AOP需要在IOC的基础上实现,因此需要导入IOC的依赖
<?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>
<groupId>org.rain</groupId>
<artifactId>spring_aop_annotation</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- Spring-IOC的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- spring-AOP的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package org.rain.spring.aop.annotation;
/**
* @author liaojy
* @date 2023/8/12 - 17:43
*/
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
package org.rain.spring.aop.annotation;
import org.springframework.stereotype.Component;
/**
* @author liaojy
* @date 2023/8/12 - 17:45
*/
// @Component注解保证这个目标类能够放入IOC容器
@Component
public class CalculatorImpl implements Calculator {
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
package org.rain.spring.aop.annotation;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author liaojy
* @date 2023/8/12 - 17:56
*/
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LoggerAspect {
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
对指定的package进行扫描,将使用组件注解的类的对象(本示例是目标对象和切面对象),交给spring的ioc容器来管理
-->
<context:component-scan base-package="org.rain.spring.aop.annotation"></context:component-scan>
<!--
开启基于注解的AOP功能,该功能会为目标对象自动生成代理
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
/*
* @Before注解:用于将方法标识为前置通知(方法)
* @Before注解的value属性值为切入点表达式,其作用是将该前置通知(方法)安插到对应目标方法的连接点上
* */
@Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
public void beforeMethod(){
System.out.println("LoggerAspect,前置通知");
}
由控制台日志可知,切面类的前置通知(方法),通过切入点表达式,作用到了目标方法的连接点上
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
// 注意:这里不能直接获取目标对象来使用;因为使用了AOP之后,IOC容器中就只有对应目标对象的代理对象;
// 如果强行获取目标对象,则报错:NoSuchBeanDefinitionException
//Calculator calculator = ioc.getBean(CalculatorImpl.class);
// 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class);
// 只能通过代理对象来访问目标对象中的方法
calculator.add(1,2);
}
该示例中(前置)通知方法引入了连接点参数,通过连接点参数,可以动态获取(切入点表达式)对应的目标方法的名称和参数列表
/*
* @Before注解:用于将方法标识为前置通知(方法)
* @Before注解的value属性值为切入点表达式,其作用是将该前置通知(方法)安插到对应目标方法的连接点上
* */
@Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
// joinPoint参数:可以获取(通过切入点表达式定位出的)连接点的相关信息
public void beforeMethod(JoinPoint joinPoint){
// 获取连接点所对应目标方法的名称
String methodName = joinPoint.getSignature().getName();
// 获取连接点所对应目标方法的参数列表
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}
// @Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
/**
* 第一个*表示任意访问修饰符和返回值类型,
* 第二个*表示该类的任意方法名称,
* (..)表示方法的任意参数列表
* 在类的位置也可以使用*,表示当前包下所有的类,
* 在包的位置也可以使用*,表示当前包下所有的子包,
*/
@Before("execution(* org.rain.spring.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}
@Pointcut("execution(* org.rain.spring.aop.annotation.CalculatorImpl.*(..))")
public void pointCutOne(){}
// @Before注解的value属性值,可以设置为使用了@Pointcut注解标识的方法名,从而复用该@Pointcut注解定义的切入点表达式
@Before("pointCutOne()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}
// 复用其他切面类中@Pointcut注解定义的切入点表达式,
// @Before注解的value属性值,需要设置为使用了@Pointcut注解标识的(全限定类名+)方法名
@Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}
// @After注解:用于将方法标识为后置通知(方法)
@After("pointCutOne()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->后置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}
由控制台日志可知,后置通知在目标对象方法的finally子句中执行(一般用于释放资源)
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
// 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class);
// 只能通过代理对象来访问目标对象中的方法
calculator.div(1,0);
}
/**
* @AfterReturning注解:用于将方法标识为返回通知(方法)
* returning属性:指定(返回)通知方法中的某个参数(名),用于接收目标对象方法的返回值
*/
@AfterReturning(value = "pointCutOne()",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->返回通知,方法名:"+methodName+",结果:"+ result);
}
由控制台日志可知,返回通知在目标对象方法的返回值之后执行
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
// 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class);
// 只能通过代理对象来访问目标对象中的方法
calculator.div(1,1);
}
/**
* @AfterThrowing注解:用于将方法标识为异常通知(方法)
* throwing属性:指定(异常)通知方法中的某个参数(名),用于接收目标对象方法出现的异常
*/
@AfterThrowing(value = "pointCutOne()",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->异常通知,方法名:"+methodName+",异常:"+ ex);
}
由控制台日志可知,异常通知在目标对象方法的catch子句中执行
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
// 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class);
// 只能通过代理对象来访问目标对象中的方法
calculator.div(1,0);
}
前置通知
目标操作
后置通知
返回通知或异常通知
本示例
前置通知
目标操作
返回通知或异常通知
后置通知
环绕通知和动态代理的形式,非常相似
/**
* @Around注解:用于将方法标识为环绕通知(方法)
* 环绕通知(方法)使用的参数是ProceedingJoinPoint类型
* 环绕通知(方法)的返回值,必须和目标对象方法的返回值一致
*/
@Around("pointCutOne()")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
String methodName = proceedingJoinPoint.getSignature().getName();
Object[] args = proceedingJoinPoint.getArgs();
Object result = null;
try {
System.out.println("LoggerAspect-->环绕前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
// 表示目标对象方法的执行
result = proceedingJoinPoint.proceed();
System.out.println("LoggerAspect-->环绕返回通知,方法名:"+methodName+",结果:"+ result);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("LoggerAspect-->环绕异常通知,方法名:"+methodName+",异常:"+ throwable);
}finally {
System.out.println("LoggerAspect-->环绕后置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}
return result;
}
注意:因为环绕通知包括了其他四种通知,所以一般要么配置其他四种通知,要么只配置环绕通知;本示例为了展示效果才同时配置
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
// 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class);
// 只能通过代理对象来访问目标对象中的方法
calculator.div(1,1);
}
package org.rain.spring.aop.annotation;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author liaojy
* @date 2023/8/15 - 7:49
*/
@Aspect
@Component
public class ValidateAspect {
}
@Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
public void beforeMethod(){
System.out.println("ValidateAspect-->前置通知");
}
由控制台日志可知,ValidateAspect切面的前置通知方法生效了,但执行顺序在LoggerAspect切面的前置通知方法的后面
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
// 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class);
// 只能通过代理对象来访问目标对象中的方法
calculator.div(1,1);
}
package org.rain.spring.aop.annotation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author liaojy
* @date 2023/8/15 - 7:49
*/
@Aspect
@Component
// @Order注解:用于设置切面的优先级,value属性值越小,优先级越高,默认值为Integer的最大值
@Order(2023)
public class ValidateAspect {
@Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
public void beforeMethod(){
System.out.println("ValidateAspect-->前置通知");
}
}
由控制台日志可知,ValidateAspect切面的前置通知方法的执行顺序,在LoggerAspect切面的前置通知方法的前面
这是因为ValidateAspect切面的@Order注解的value属性值已设为2023,要小于LoggerAspect切面所使用的默认值(Integer的最大值2147483647)
AspectJ本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,但最终效果是动态的。
weaver就是织入器,Spring只是借用了AspectJ中的注解。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章