路漫漫其修远兮, 吾将上下而求索!
AOP
Aspect Oriented Programming 意思是面向切面编程, 可能是因为java的面向对象编程在处理一些日志, 授权等操作时, 为了减少重复代码, 降低耦合程度. 所以引入了一种切面编程的概念. 通俗的理解切面编程 可以理解为对多个方法的增强操作.
基础知识
本文对基础不过多讲解, 提供以下学习地址, 比较全面, 但是强烈推荐各位在学习Spirng相关知识的时候去阅读官网文档, 一般的博客都是一些程序猿自己理解整理的知识, 很多都有一大推的问题, 因为他们也没有理解清楚, 所以一定要通过官网文档去学习, 然后辅助博客去理解一些难点
资料 | 地址 |
---|---|
Spring官网 | https://docs.spring.io/spring-framework/docs/current/reference/html/core.html |
AOP基础 | https://blog.youkuaiyun.com/qq_41701956/article/details/84427891 |
引入依赖
<!-- AOP支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>test</scope>
</dependency>
<!--aspectJ织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
一. 开启@AspectJ支持
官方文档:
To enable @AspectJ support with Java @Configuration, add the @EnableAspectJAutoProxy annotation, as the following example shows:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
意思是: 使用java注解方式配置时, 要在配置类上加上 @EnableAspectJAutoProxy 开启Sping对AspectJ的默认支持
定义切面
切面中定义了两个切点, 切点内连接点有重叠, 为了验证通知的优先级
/**
* @description: 定义一个切面
* 注意:
* 1. 最高优先级到最低优先级:@Around、@Before、@After、@AfterReturning、@afterhrowing。
* 但是,请注意,@After-advice方法将在同一方面的任何@afterreturn或@afterhrowing-advice方法之后有效地调用
* 2. 一个切面内定义了两个重叠的切点时, 如果此时再分别定义通知, 此时顺序无法判定, 官网推荐我们把这两个切点合并
* 成一个切点, 或者再定义一个切面 通过@Order 指定优先级(数据越小, 优先级越高)
* @author: wangml
* @date: 2021/8/11 09:00
*/
@Aspect
@Order(100)
@Component
public class NotVeryUsefulAspect {
private static final Logger logger = LoggerFactory.getLogger(NotVeryUsefulAspect.class);
@Pointcut("execution(* com.huntkey.rx..service.*.*(..))")
public void pointMed1() {};
@Pointcut("execution(* com.huntkey.rx..service.UserService.delUser(..))")
public void pointMed2() {};
@Before("pointMed1()")
public void beforeMedExecute() {
logger.info("----切面方法1被执行前调用----");
}
@AfterReturning("pointMed1()")
public void afterReturnMedExecute() {
logger.info("----切面方法1执行成功后调用----");
}
@After("pointMed1()")
public void afterMedExecute() {
logger.info("----切面方法1被执行后调用--------");
}
@Around("pointMed2()")
public Object aroundMed2Execute(ProceedingJoinPoint pjp) throws Throwable {
logger.info("******切面方法2被调用********");
// 修改入参
Object[] args = pjp.getArgs();
logger.info("初始参数: {}", args);
// 获取方法签名
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
// 获取方法的参数列表
Class[] parameterTypes = methodSignature.getParameterTypes();
if (String.class.isAssignableFrom(parameterTypes[0])) {
// 方法参数第一个参数类型时 String
args[0] = "88888";
}
logger.info("修改后的参数: {}", args);
logger.info("########环绕, 方法执行前处理#########");
Object result = pjp.proceed(args);
logger.info("########环绕, 方法执行后处理#########");
logger.info("返回的数据: {}", result);
return result;
}
}
定义一个UserService
/**
* @description: UserService
* @author: wangml
* @date: 2021/8/11 09:25
*/
@Service
public class UserService {
public void addUser() {
System.out.println("insert a user into user table");
}
public String delUser(String id) {
System.out.println("delete a user from user table where id = " + id);
return "delete user success!";
}
}
新建测试类
/**
* @description:AOP使用测试类
* @author: wangml
* @date: 2021/8/11 09:32
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class AopTest {
@Autowired
private UserService userService;
// @Autowired
// private EmployeeService employeeService;
@Test
public void addUser() {
userService.addUser();
}
@Test
public void delUser() {
userService.delUser("1111");
}
// @Test
// public void addEmployee() {
// employeeService.addEmployee();
// }
//
// @Test
// public void delEmployee() {
// employeeService.delEmployee("111");
// }
}
分析: 当执行addUser 时, 由于方法在切点pointMed1的连接点表达式范围内, 所以会执行 切点pointMed1的通知, beforeMedExecute, afterReturnMedExecute, afterMedExecute里面内容都会被执行, 执行结果如下, 符合预期
2021-08-11 17:38:03.200 INFO 13388 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1被执行前调用----
insert a user into user table
2021-08-11 17:38:03.216 INFO 13388 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1执行成功后调用----
2021-08-11 17:38:03.217 INFO 13388 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1被执行后调用--------
当执行delUser时, 两个切点的连接点表达式都满足, 都会执行, 但是由于 @Around > @Before > @AfterReturning > @After 所以 切点pointMed2 会先执行, 结果如下, 符合预期
2021-08-11 17:39:28.614 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ******切面方法2被调用********
2021-08-11 17:39:28.614 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : 初始参数: 1111
2021-08-11 17:39:28.617 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : 修改后的参数: 88888
2021-08-11 17:39:28.617 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ########环绕, 方法执行前处理#########
2021-08-11 17:39:28.617 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1被执行前调用----
delete a user from user table where id = 88888
2021-08-11 17:39:28.632 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1执行成功后调用----
2021-08-11 17:39:28.632 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1被执行后调用--------
2021-08-11 17:39:28.632 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ########环绕, 方法执行后处理#########
2021-08-11 17:39:28.632 INFO 14100 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : 返回的数据: delete user success!
特别注意:
- 环绕可以控制实际方法是否执行, 通过 proceed来实现, 如果没有写, 则方法不会执行, 而且调用过程是链式调用, 和过滤器FilterChain的处理相似.
- 环绕可以用来在 proceed前 处理在方法传递参数, proceed后 处理方法返回数据, 如果方法是void 类型默认返回null.
- 一般优先使用环绕(官网说法)
定义另一个切面
主要用来验证不同切面优先级, 以及使用自定义注解 定义切点的使用
/**
* @description: 切面拦截所有使用了注解的方法
* @author: wangml
* @date: 2021/8/11 14:16
*/
@Aspect
@Order(10)
@Component
public class VeryUsefulAspect {
public static final Logger logger = LoggerFactory.getLogger(VeryUsefulAspect.class);
@Pointcut("@annotation(com.huntkey.rx.sceo.anno.Idempotent)")
public void pointCutMed(){};
@Around("pointCutMed()")
public Object aroundAnn(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取注解中传递的数据
Object[] args = joinPoint.getArgs();
logger.info("不处理方法参数, 参数值为: {}", args);
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
Idempotent annotation = method.getAnnotation(Idempotent.class);
String value = annotation.value();
String name = annotation.name();
if (!"".equals(value) || !"".equals(name)) {
// 当注解内方法返回值不是默认值时, 有重新赋值
logger.info("*****注解数据操作********");
logger.info("Idempotent注解数据, value: {} name: {}", value, name);
}
// 具体执行的方法
Object proceed = joinPoint.proceed();
// proceed 是 方法执行后返回的数据, 若方法为void 则默认null.
logger.info("***方法执行后返回数据: {}", proceed);
return proceed;
}
}
自定义注解@Idempotent
/**
* @description: 方法注解
* @author: wangml
* @date: 2021/8/11 14:12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
}
定义一个EmployeeService
/**
* @description: 员工Service(测试AOP注解使用)
* @author: wangml
* @date: 2021/8/11 14:48
*/
@Service
public class EmployeeService {
private static final Logger logger = LoggerFactory.getLogger(EmployeeService.class);
public void addEmployee() {
logger.info("insert a employee into employee table");
}
@Idempotent(name = "wangml")
public String delEmployee(String id) {
logger.info("delete a employee from employee table where id = {}", id);
return "delete employee success!";
}
}
分析: addEmployee 没有添加@Idempotent 所以只有NotVeryUsefulAspect 中的切点pointMed1
中连接点表达式满足, 执行方法, 输出如下, 符合预期
2021-08-11 18:04:37.884 INFO 5228 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1被执行前调用----
2021-08-11 18:04:37.899 INFO 5228 --- [ main] c.h.rx.sceo.service.EmployeeService : insert a employee into employee table
2021-08-11 18:04:37.900 INFO 5228 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1执行成功后调用----
2021-08-11 18:04:37.900 INFO 5228 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1被执行后调用--------
delEmployee 添加了@Idempotent, 所以新定义的切面VeryUsefulAspect 中 pointCutMed切点的连接点表达式满足 和 NotVeryUsefulAspect 中的切点pointMed1中连接点表达式满足, 但是VeryUsefulAspect 的切面优先级 高于NotVeryUsefulAspect 切面, 因此 VeryUsefulAspect 中的通知先于 NotVeryUsefulAspect 执行. 执行方法, 输入如下, 符合预期
2021-08-11 18:09:41.215 INFO 7796 --- [ main] c.huntkey.rx.sceo.aop.VeryUsefulAspect : 不处理方法参数, 参数值为: 111
2021-08-11 18:09:41.219 INFO 7796 --- [ main] c.huntkey.rx.sceo.aop.VeryUsefulAspect : *****注解数据操作********
2021-08-11 18:09:41.219 INFO 7796 --- [ main] c.huntkey.rx.sceo.aop.VeryUsefulAspect : Idempotent注解数据, value: name: wangml
2021-08-11 18:09:41.219 INFO 7796 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1被执行前调用----
2021-08-11 18:09:41.236 INFO 7796 --- [ main] c.h.rx.sceo.service.EmployeeService : delete a employee from employee table where id = 111
2021-08-11 18:09:41.236 INFO 7796 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1执行成功后调用----
2021-08-11 18:09:41.236 INFO 7796 --- [ main] c.h.rx.sceo.aop.NotVeryUsefulAspect : ----切面方法1被执行后调用--------
2021-08-11 18:09:41.236 INFO 7796 --- [ main] c.huntkey.rx.sceo.aop.VeryUsefulAspect : ***方法执行后返回数据: delete employee success!
总结
- aop虽然功能强大, 但是最好不要定义太多, 不然检查问题的时候会晕死, 因为可能会有层层嵌套, 每次都可以对数据进行处理.
- 适用于减少重复性代码, 很多业务类都需要进行原来没有的某一处理逻辑时, 可以使用