当前位置:   article > 正文

使用自定义注解和切面AOP实现Java程序增强_signature.getdeclaringtypename

signature.getdeclaringtypename

1.注解介绍

1.1注解的本质

Oracle官方对注解的定义为:

Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.

注解是元数据的一种形式,它提供有关程序的数据,该数据不属于程序本身。 注解对其注释的代码操作没有直接影响。

而在 JDK 的Annotation接口中有一行注释如此写到:

  1. /**
  2. * The common interface extended by all annotation types.
  3. * ...
  4. */
  5. public interface Annotation {...}

这说明其他注解都扩展自 Annotation 这个接口,也就是说注解的本质就是一个接口。

以 Spring Boot 中的一个注解为例:

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Indexed
  5. public @interface Component {
  6. String value() default "";
  7. }

它实际上相当于:

public interface Component extends Annotation{...}

而 @interface 可以看成是一个语法糖

1.2注解的要素

依然来看 @Component 这个例子:

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Indexed
  5. public @interface Component {
  6. String value() default "";
  7. }

在注解定义上有几个注解 @Target, @Retention, @Documented ,被称为 元注解 。

所谓元注解就是说明注解的注解

Java 中的元注解共有以下几个:

1.2.1 @Target

@Target 顾名思义,这个注解标识了被修饰注解的作用对象。我们看看它的源码:

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Target {
  5. /**
  6. * Returns an array of the kinds of elements an annotation type
  7. * can be applied to.
  8. * @return an array of the kinds of elements an annotation type
  9. * can be applied to
  10. */
  11. ElementType[] value();
  12. }

可以看到,这个注解的 value 值是一个数组,这也就意味着注解的作用对象可以有多个。 其取值范围都在 ElementType 这个枚举之中:

  1. public enum ElementType {
  2. /** 类、接口、枚举定义 */
  3. TYPE,
  4. /** 字段,包括枚举值 */
  5. FIELD,
  6. /** 方法 */
  7. METHOD,
  8. /** 参数 */
  9. PARAMETER,
  10. /** 构造方法 */
  11. CONSTRUCTOR,
  12. /** 局部变量 */
  13. LOCAL_VARIABLE,
  14. /** 元注解 */
  15. ANNOTATION_TYPE,
  16. /** 包定义 */
  17. PACKAGE...
  18. }

不同的值代表被注解可修饰的范围,例如 TYPE 只能修饰类、接口和枚举定义。这其中有个很特殊的值叫做 ANNOTATION_TYPE , 是专门表示元注解的。

在回过头来看 @Component 这个例子, Target 取值为 TYPE 。熟悉 Spring Boot 的同学也一定知道, @Component 确实是不能放到方法或者属性前面的。

1.2.2@Retention

@Retention 注解指定了被修饰的注解的生命周期。定义如下:

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Retention {
  5. /**
  6. * Returns the retention policy.
  7. * @return the retention policy
  8. */
  9. RetentionPolicy value();
  10. }

可以看到这个注解带一个 RetentionPolicy 的枚举值:

  1. public enum RetentionPolicy {
  2. SOURCE,
  3. CLASS,
  4. RUNTIME
  5. }
  1. SOURCE
  2. CLASS
  3. RUNTIME

1.2.3 @Documented

这个注解比较简单,表示是否添加到 java doc 中。

1.2.4 @Inherited

这个也比较简单,表示注解是否被继承。这个注解不是很常用。

注意:元注解只在定义注解时被使用!

1.3 注解的构成

从上面的元注解可以了解到,一个注解可以关联多个 ElementType ,但只能有一个 RetentionPolicy :

 

Java 中有三个常用的内置注解,其实相信大家都用过或者见过。不过在了解了注解的真实面貌以后,不妨重新认识一下吧!

1.4 Java内置注解

1.4.1 @Override

@Override 它的定义为:

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface Override {
  4. }

可见这个注解没有任何取值,只能修饰方法,而且RetentionPolicy 为 SOURCE,说明这是一个仅在编译阶段起作用的注解。

它的真实作用想必大家一定知道,就是在编译阶段,如果一个类的方法被 @Override 修饰,编译器会在其父类中查找是否有同签名函数,如果没有则编译报错。可见这确实是一个除了在编译阶段就没什么用的注解。

1.4.2 @Deprecated

@Deprecated 它的定义为:

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
  4. public @interface Deprecated {
  5. }

这个注解也没有任何取值,能修饰所有的类型,永久存在。这个注解的作用是,告诉使用者被修饰的代码不推荐使用了,可能会在下一个软件版本中移除。这个注解仅仅起到一个通知机制,如果代码调用了被@Deprecated 修饰的代码,编译器在编译时输出一个编译告警。

1.4.3 @SuppressWarnings

@SuppressWarnings 它的定义为:

  1. @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface SuppressWarnings {
  4. /**
  5. * The set of warnings that are to be suppressed by the compiler in the
  6. * annotated element. Duplicate names are permitted. The second and
  7. * successive occurrences of a name are ignored. The presence of
  8. * unrecognized warning names is <i>not</i> an error: Compilers must
  9. * ignore any warning names they do not recognize. They are, however,
  10. * free to emit a warning if an annotation contains an unrecognized
  11. * warning name.
  12. *
  13. * <p> The string {@code "unchecked"} is used to suppress
  14. * unchecked warnings. Compiler vendors should document the
  15. * additional warning names they support in conjunction with this
  16. * annotation type. They are encouraged to cooperate to ensure
  17. * that the same names work across multiple compilers.
  18. * @return the set of warnings to be suppressed
  19. */
  20. String[] value();
  21. }

这个注解有一个字符串数组的值,需要我们使用注解的时候传递。可以在类型、属性、方法、参数、构造函数和局部变量前使用,声明周期是编译期。

这个注解的主要作用是压制编译告警的。

2.AOP介绍(AspectJ暂不讨论)

2.1 Spring AOP基本概念

  1. 是一种动态编译期增强性AOP的实现
  2. 与IOC进行整合,不是全面的切面框架
  3. 与动态代理相辅相成
  4. 有两种实现:基于jdk动态代理、cglib

2.2 Spring AOP与AspectJ区别

  1. Spring的AOP是基于动态代理的,动态增强目标对象,而AspectJ是静态编译时增强,需要使用自己的编译器来编译,还需要织入器
  2. 使用AspectJ编写的java代码无法直接使用javac编译,必须使用AspectJ增强的ajc增强编译器才可以通过编译,写法不符合原生Java的语法;而Spring AOP是符合Java语法的,也不需要指定编译器去编译,一切都由Spring 处理。

2.3 使用步骤

  1. 定义业务组件
  2. 定义切点(重点)
  3. 定义增强处理方法(切面方法)

这边用下面例子的AOP类来进行说明 (基于Spring AOP的)

  1. /**
  2. * @Author Song
  3. * @Date 2020/5/26 9:50
  4. * @Version 1.0
  5. */
  6. @Slf4j
  7. @Aspect
  8. @Component
  9. public class EagleEyeAspect {
  10. @Pointcut("@annotation(com.ctgu.song.plantfactory.v2.annotation.EagleEye)")
  11. public void eagleEye() {
  12. }
  13. @Around("eagleEye()")
  14. public Object around(ProceedingJoinPoint pjp) throws Throwable {
  15. long begin = System.currentTimeMillis();
  16. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  17. HttpServletRequest request = attributes.getRequest();
  18. Signature signature = pjp.getSignature();
  19. MethodSignature methodSignature = (MethodSignature) signature;
  20. Method method = methodSignature.getMethod();
  21. EagleEye eagleEye = method.getAnnotation(EagleEye.class);
  22. String desc = eagleEye.desc();
  23. log.info("============请求开始==========");
  24. log.info("请求链接:{}", request.getRequestURI().toString());
  25. log.info("接口描述:{}", desc);
  26. log.info("请求类型:{}", request.getMethod());
  27. log.info("请求方法:{}.{}", signature.getDeclaringTypeName(), signature.getName());
  28. log.info("请求IP:{}", request.getRemoteAddr());
  29. log.info("请求入参:{}", JSON.toJSONString(pjp.getArgs()));
  30. Object result = pjp.proceed();
  31. long end = System.currentTimeMillis();
  32. log.info("请求耗时:{}ms", end - begin);
  33. log.info("请求返回:{}", JSON.toJSONString(result));
  34. log.info("=============请求结束===========");
  35. return result;
  36. }
  37. }

这边先不看代码的具体内容,先简单介绍一下用到AOP中常用的注解

  • @Aspect : 指定切面类;
  • @Pointcut :公共切入点表达式
  • 通知方法前置通知(@Before) 目标方法执行之前,执行注解的内容后置通知(@After)目标方法执行之后,执行注解的内容返回通知 (@AfterReturning)目标方法返回后,执行注解的内容异常通知 (@AfterThrowing)目标方法抛出异常后,执行注解的内容环绕通知 (@Around)目标方法执行前后,分别执行一些代码

注意 定义好切片类后要将其加入Spring容器内才能使用哦 (可以使用@Component注解)

3. 具体实现(一个例子)

1.首先定义一个注解,代码如下

  1. /**
  2. * @Author Song
  3. * @Date 2020/5/26 9:44
  4. * @Version 1.0
  5. */
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Target(ElementType.METHOD)
  8. @Documented
  9. public @interface EagleEye {
  10. /**
  11. * @Retention(RetentionPolicy.RUNTIME)
  12. * 定义了注解的生命周期为运行时
  13. * <p>
  14. * @Target(ElementType.METHOD)
  15. * 定义了注解的作用域为方法
  16. * <p>
  17. * Documented
  18. * 标识该注解可以被JavaDoc记录
  19. * <p>
  20. * 定义注解名称为EagleEye(鹰眼,哈哈~~)
  21. * <p>
  22. * 定义一个元素desc,用来描述被修饰的方法
  23. * <p>
  24. * 接口描述
  25. *
  26. * @return
  27. */
  28. String desc() default "";
  29. }

2.定义切片内并写好自己想要增强的方法

直接贴代码了~~

  1. /**
  2. * @Author Song
  3. * @Date 2020/5/26 9:50
  4. * @Version 1.0
  5. */
  6. @Slf4j
  7. @Aspect
  8. @Component
  9. public class EagleEyeAspect {
  10. @Pointcut("@annotation(com.ctgu.song.plantfactory.v2.annotation.EagleEye)")
  11. public void eagleEye() {
  12. }
  13. @Around("eagleEye()")
  14. public Object around(ProceedingJoinPoint pjp) throws Throwable {
  15. long begin = System.currentTimeMillis();
  16. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  17. HttpServletRequest request = attributes.getRequest();
  18. Signature signature = pjp.getSignature();
  19. MethodSignature methodSignature = (MethodSignature) signature;
  20. Method method = methodSignature.getMethod();
  21. EagleEye eagleEye = method.getAnnotation(EagleEye.class);
  22. String desc = eagleEye.desc();
  23. log.info("============请求开始==========");
  24. log.info("请求链接:{}", request.getRequestURI().toString());
  25. log.info("接口描述:{}", desc);
  26. log.info("请求类型:{}", request.getMethod());
  27. log.info("请求方法:{}.{}", signature.getDeclaringTypeName(), signature.getName());
  28. log.info("请求IP:{}", request.getRemoteAddr());
  29. log.info("请求入参:{}", JSON.toJSONString(pjp.getArgs()));
  30. Object result = pjp.proceed();
  31. long end = System.currentTimeMillis();
  32. log.info("请求耗时:{}ms", end - begin);
  33. log.info("请求返回:{}", JSON.toJSONString(result));
  34. log.info("=============请求结束===========");
  35. return result;
  36. }
  37. }

在@Pointcut里通过@annotation来配置切点,代表我们的AOP切面会切到所有用EagleEye注解修饰的类。

然后使用@Around环绕通知在被注解的方法前后执行一些代码

Object result = pjp.proceed();

这行代码之前就是 执行目标方法之前需要执行的代码 ,这行代码之后就是 执行目标方法之后需要执行的代码

3. 注解的使用

只需要在需要被注解的方法上面使用自己的注解就行了 这里拿我自己项目中的一个Controller中的方法举例

  1. @EagleEye(desc = "分页查询实验")
  2. @GetMapping("/experiment")
  3. @ApiOperation("分页查询实验")
  4. public RsBody<Page<ExperimentVO2>> pageExperiment(ExperimentQueryDTO queryDTO) {
  5. log.info("请求分页查询实验的方法pageExperiment,请求参数为{}", queryDTO.toString());
  6. RsBody<Page<ExperimentVO2>> rsBody = new RsBody<>();
  7. IPage<Experiment> page = experimentV2Service.page(new Page<>(queryDTO.getCurrent() - 1, queryDTO.getSize()), new LambdaQueryWrapper<Experiment>()
  8. .like(queryDTO.getExperimentId() != null, Experiment::getExperimentId, queryDTO.getExperimentId())
  9. .eq(queryDTO.getExperimentStatus() != null, Experiment::getExperimentStatus, queryDTO.getExperimentStatus())
  10. .between(queryDTO.getStartTime() != null && queryDTO.getEndTime() != null, Experiment::getStartTime, queryDTO.getStartTime(), queryDTO.getEndTime())
  11. .orderBy(true, false, Experiment::getExperimentId));
  12. //组装Vo
  13. List<ExperimentVO2> experimentVOList = new ArrayList<>();
  14. for (Experiment experiment : page.getRecords()) {
  15. ExperimentVO2 experimentVO = new ExperimentVO2();
  16. experimentVO.setExperimentId(experiment.getExperimentId());
  17. PlantInfo byPlantId = plantService.findByPlantId(experiment.getPlantId());
  18. if (byPlantId != null) {
  19. experimentVO.setPlantName(byPlantId.getPlantName());
  20. } else {
  21. experimentVO.setPlantName("植物被删除");
  22. }
  23. experimentVO.setStartTime(experiment.getStartTime());
  24. experimentVO.setEndTime(experiment.getEndTime());
  25. experimentVO.setExperimentPurpose(experiment.getExperimentPurpose());
  26. experimentVO.setExperimentDescription(experiment.getExperimentDescription());
  27. experimentVO.setExperimentAddress(experiment.getExperimentAddress());
  28. experimentVO.setExperimentPersonName(userService.findById(experiment.getExperimentPersonId()).getUserName());
  29. experimentVO.setCronType(experiment.getCronType());
  30. experimentVO.setExperimentStatus(experiment.getExperimentStatus());
  31. experimentVO.setExperimentResult(experiment.getExperimentResult());
  32. experimentVOList.add(experimentVO);
  33. }
  34. Page<ExperimentVO2> pageVo = new Page<ExperimentVO2>();
  35. pageVo.setPages(page.getPages());
  36. pageVo.setRecords(experimentVOList);
  37. pageVo.setTotal(page.getTotal());
  38. pageVo.setSize(page.getSize());
  39. pageVo.setCurrent(page.getCurrent());
  40. return rsBody.setBody(true).setData(pageVo);
  41. }

4.测试情况

好的 万事俱备 让我们运行一下程序 并访问这个方法 (过程略过)

很有意思吧~~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/615729
推荐阅读
相关标签
  

闽ICP备14008679号