Spring--AOP使用
Spring的AOP也是基于AspectJ,和安卓中(之前文章:Android--AOP架构设计之使用AspectJ监测方法耗时)使用相比,Spring集成了它,所以配置起来方便很多
一、概念
- 重新整理下AOP的概念:
术语 | 描述 |
---|---|
连接点 Joint point | 增强的方法,也就是我们想要处理的方法。包括:方法,成员变量访问,异常处理程序执行等 |
切入点 Pointcut | 表示一组连接点,连接点使用表达式集中起来,切入点也是我们AOP编程中需要着重处理的地方 |
通知 Advice | 用于区别对切入点调用之前,之后,还是返回值,异常处理等的逻辑处理。通知也是AOP编程中需要着重处理的地方 |
目标对象 Target | 被代理的对象 |
切面 Aspect | 表示AOP编程的入口 |
织入 Weaving | 创建代理对象并实现功能增强的声明并运行过程 |
- 表达式:
我们需要通过表达式来指定连接点,规则如下
execution([注解(可省略)] [权限修饰符(可省略)] [返回值类型] [类的全路径名].[方法名](参数 列表) )
如:
execution(* com.aruba.mapper.*.add(..))
表示com.aruba.mapper包下,所有类的add方法
execution(* com.aruba.mapper.*.add*(..))
表示com.aruba.mapper包下,所有类的,方法名为add前缀的方法
二、项目准备
1. 导入依赖:
<!--spring切面包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.5</version>
</dependency>
<!--aop联盟包-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--Apache Commons日志包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2. Spring配置文件:
开启包扫描和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:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--包扫描-->
<context:component-scan base-package="com.aruba"></context:component-scan>
<!--aop自动生成代理对象-->
<aop:aspectj-autoproxy/>
</beans>
3. 类和接口定义:
public interface DeptMapper {
int add();
}
@Repository
public class DeptMapperImpl implements DeptMapper {
@Override
public int add() {
System.out.println("DeptMapperImpl add ..");
return 0;
}
}
加上@Repository注解,让Spring容器自动实例化
三、AOP编程
1. 准备切面
@Aspect
@Component
public class MyAspect {
}
使用@Aspect注解表示处理入口,@Component注解让Spring容器自动实例化
2. 定义切入点
在切面MyAspect中定义切入点,使用Pointcut注解配合连接点表达式注解方法,方法名随意:
@Pointcut("execution(* com.aruba.mapper.*.add*(..))")
public void addPointcut(){}
注解后,com.aruba.mapper包下的所有类的add前缀的方法都成为了一个切入点,切入点名称为方法名addPointcut()
3. 通过通知注解实现方法增强
先使用@Before注解配合切入点名称:
@Before("addPointcut()")
public void methodBefore() {
System.out.println("MyAspect methodBefore..");
}
4. 测试方法
@Test
public void test1() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
DeptMapper deptMapper = applicationContext.getBean(DeptMapper.class);
deptMapper.add();
}
结果:
四、通知注解
1. 前置通知
上面我们使用了@Before注解,表示在目标方法调用之前,进行增强,称为前置通知,我们把被通知注解的方法称为增强方法
前置通知中还能获取目标方法的传参
接口中新增一个带参数的方法并实现:
public interface DeptMapper {
int add();
int add(int id,String name);
}
@Repository
public class DeptMapperImpl implements DeptMapper {
@Override
public int add() {
System.out.println("DeptMapperImpl add ..");
return 0;
}
@Override
public int add(int id, String name) {
System.out.println("DeptMapperImpl add ..");
return 0;
}
}
在增强方法中,新增一个JoinPoint入参:
@Before("addPointcut()")
public void methodBefore(JoinPoint joinPoint) {
System.out.println("MyAspect methodBefore..");
System.out.println(Arrays.toString(joinPoint.getArgs()));
}
测试方法:
@Test
public void test2() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
DeptMapper deptMapper = applicationContext.getBean(DeptMapper.class);
deptMapper.add(1,"zhangsan");
}
结果:
2. 后置通知
在目标方法调用后执行增强方法,使用@After注解
在切面中定义增强方法:
@After("addPointcut()")
public void methodAfter(JoinPoint joinPoint) {
System.out.println("MyAspect methodAfter..");
}
重新运行测试方法的结果:
3. 返回通知
在目标方法正常运行后执行增强方法,和后置不同的是,出现异常,则不会调用增强方法,并且可以获取目标方法返回值,使用AfterReturning注解
在切面中定义增强方法:
//returning 指定返回值对应增强方法的参数名
@AfterReturning(pointcut = "addPointcut()", returning = "ret")
public void methodAfterReturning(JoinPoint joinPoint, Object ret) {
System.out.println("MyAspect methodAfterReturning.. :" + ret);
}
结果:
4. 异常通知
在目标方法执行异常时调用增强方法,使用@AfterThrowing注解
在切面中定义增强方法:
@AfterThrowing(pointcut = "addPointcut()", throwing = "ex")
public void methodAfterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("MyAspect methodAfterThrowing.. ex:" + ex);
}
实现类写个异常:
@Override
public int add(int id, String name) {
System.out.println("DeptMapperImpl add ..");
int a = 1 / 0;
return 0;
}
结果:
5. 环绕通知
在目标方法执行前和执行后,实现功能增强,需要手动调用目标方法执行,并将返回值返回,风格为责任链模式,使用@Around注解
在切面中定义增强方法:
@Around("addPointcut()")
public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("MyAspect methodAround.. start");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("MyAspect methodAround.. end");
return proceed;
}
ProceedingJoinPoint为连接点,通过proceed方法执行目标方法,也可以传入新的参数
结果:
五、其他配置
除了xml中定义外,还可以在切面上使用注解表示包扫描和开启生成代理:
@Aspect
@Component
@ComponentScan("com.aruba")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class MyAspect {
当有多个切面时,可以使用Order注解指定执行顺序,数字越小,优先级越高,数字越小,其代理位置越靠近注入位置:
@Aspect
@Component
@ComponentScan("com.aruba")
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Order(1)
public class MyAspect {