原文地址 https://www.cnblogs.com/hello-daocaoren/p/9289590.html

本文内容:spring 中如何使用注解实现面向切面编程,以及如何使用自定义注解。

一个场景

比如用户登录,每个请求发起之前都会判断用户是否登录,如果每个请求都去判断一次,那就重复地做了很多事情,只要是有重复的地方,就有优化的空间。现在就把重复的地方抽取出来,暂且称之为 “拦截器”,然后每次请求之前就先经过 “拦截器”,这个编程的思想就可以称之为面向切面编程。AOP(Aspect Oriented Program)

最典型的应用就是事务管理和权限验证,还有日志统计,下文中的案例就是接口执行时间的统计。

spring 中使用 AOP(基于注解)

不得不说注解是个很巧妙的设计,使用很少量的信息描述数据,这类数据称之为元数据,描述数据的数据。关于注解的理解,这里有个传送门:http://www.importnew.com/10294.html

下面的案例是在 springBoot 中进行的,直观地感受一下如何使用注解完成 AOP。

  1. @Service
  2. public class UserService {
  3. public void getUser() {
  4. //To do something
  5. System.out.println("getUser() has been called");
  6. try {
  7. Thread.sleep(1000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }

切面是这样定义的:

  1. @Component
  2. @Aspect
  3. public class LoggerAspect {
  4. /**
  5. * getUser()执行之前执行
  6. */
  7. @Before("execution(* com.springboot.demo.service.UserService.getUser(..))")
  8. public void callBefore() {
  9. System.out.println("before call method");
  10. System.out.println("begin........................");
  11. }
  12. /**
  13. * getUser()执行之后执行
  14. */
  15. @After("execution(* com.springboot.demo.service.UserService.getUser(..))")
  16. public void callAfter() {
  17. System.out.println("after call method");
  18. System.out.println("end..............................");
  19. }
  20. }

来个单元测试验证一下:

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class UserServiceTest {
  4. @Autowired
  5. private UserService userService;
  6. @Test
  7. public void getUserTest() {
  8. userService.getUser();
  9. }
  10. }

具体案例

假如有以下的业务场景: UserService 业务类中有个 getUser() 这个方法,现在想统计一下这个方法的执行时间,可能需要测试这个接口的性能。通常做法是方法开始时获取系统当前时间,然后方法结束时获取当前时间,最后 excuteTime=endTime-startTime。

如果现在不仅是这个方法需要统计,还有 getUserByName()、getUserById() 需要统计,上述的方法明显很笨了。

使用 AOP 怎么解决? 抽取公共部分为一个切面,方法执行前记录时间,然后执行目标方法,最后,目标方法执行完成之后再获取一次系统时间。

具体实现如下:在 LoggerAspect 中再写一个方法,记录 getUser() 方法的执行时间。

  1. /**
  2. * 记录执行时间
  3. * @param point 切点
  4. * @return
  5. * @throws Throwable
  6. */
  7. @Around("execution(* com.springboot.demo.service.UserService.getUser(..))")
  8. public Object getMethodExecuteTime(ProceedingJoinPoint point) throws Throwable {
  9. System.out.println("---------------getMethodExecuteTime------------------");
  10. long startTime = System.currentTimeMillis();
  11. //调用目标方法
  12. Object result = point.proceed();
  13. long endTime = System.currentTimeMillis();
  14. long executeTime = endTime - startTime;
  15. System.out.println("executeTime=" + executeTime + "------------------");
  16. return result;
  17. }

@Around 将目标方法再次封装,控制了它的调用时机,以此来记录 getUser() 的执行时间。但是好像并没有达到记录 UserService 中的多个方法的执行时间的目的。

@Around(“execution(* com.springboot.demo.service.UserService.getUser(..))”)

其中指定了切点是 getUser() 这个方法,这里的表达式很丰富,可以设置为:

  • com.springboot.demo.service.UserService.*(..)

表示 UserService 中的每一个方法都是切点,甚至可以是这样:

  • com.springboot.demo.service..(..)

表示 service 包下的所有类的所有方法都是切点,但是这样很明显不够灵活,如果能自定义地控制就更好了。

自定义一个注解

如果用一个注解标注某个方法需要记录其执行时间,岂不是更加优雅。

  1. /**
  2. * @Description 标注某个方法需要记录执行时间
  3. * @Author YaoQi
  4. * @Date 2018/7/6 15:51
  5. */
  6. @Target(ElementType.METHOD)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Documented
  9. public @interface Logger {
  10. String value() default "";
  11. }

注解是用来描述数据的,上面的这个注解的意思是:这个注解将作用于方法,并且在运行时有效。但是这样只是标注了,如何读取这个标注的信息?

在 LoggerAspect 中加入这一个方法:

  1. /**
  2. * @param point
  3. * @return
  4. * @throws Throwable
  5. */
  6. @Around("@annotation(com.springboot.demo.annotation.Logger)")
  7. public Object getMethodExecuteTimeForLogger(ProceedingJoinPoint point) throws Throwable {
  8. System.out.println("---------------getMethodExecuteTime------------------");
  9. long startTime = System.currentTimeMillis();
  10. Object result = point.proceed();
  11. long endTime = System.currentTimeMillis();
  12. long executeTime = endTime - startTime;
  13. System.out.println("executeTime=" + executeTime + "------------------");
  14. return result;
  15. }

哪个方法需要记录执行时间就将 @Logger 放在对应的方法上:

  1. @Logger
  2. public void getUser() {
  3. System.out.println("getUser() has been called");
  4. }