自定义注解实现AOP

  • AOP简要概述
  • 自定义注解annotation
  • 配置AOP切面
  • 使用

AOP简要概述

AOP:
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式或运行时动态代理实现程序功能增强的一种技术,如在方法执行前、或执行后、或是在执行中出现异常后这些地方进行拦截处理或叫做增强处理。AOP现有两个主要的流行框架,即Spring AOP和Spring + AspectJ。
两者的不同
Spring AOP:
1、采用的是动态织入,基于动态代理来实现,默认如果被代理的类实现一个接口,用JDK提供的动态代理实现,如果没有则使用CGLIB实现。
2、Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现
3、在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ的那么好
AspectJ:
1、是静态织入,指在编译时期就织入,即:编译出来的class文件,字节码就已经被织入了,所以说它生成的类是没有额外运行时开销的。
(有如下几个织入的时机:)
​ 1、编译期织入(Compile-time weaving): 如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
​ 2、编译后织入(Post-compile weaving): 也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
​3、类加载后织入(Load-time weaving): 指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。

现在Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

动态代理:
所谓的动态代理就是说AOP框架不会去改动原有代码,而是在内存中临时为方法生成一个AOP对象,这个对象在特定的切点做了增强处理。Spring AOP中的动态代理主要有两种实现方式,JDK动态代理和CGLIB动态代理。
JDK动态代理:
通过反射来接收被代理的类,并且要求被代理的类实现一个接口,它的核心是InvocationHandler接口和Proxy类。如果被代理类没有实现接口,那么Spring AOP就会选择使用CGLIB来动态代理目标类。
CGLIB(Code Generation Library):
是一个代码生成的类库,可以在运行时动态地生成某个类的子类,它是通过继承的方式做的动态代理,因此如果某个类被标记为final(无法被继承),那么它是无法使用CGLIB做动态代理的。
AOP常用使用场景:
日志记录、性能统计、权限控制、事务处理、异常处理、懒加载

自定义注解annotation

一些元注解的说明:

@Retention: 什么时候使用该注解,我们定义为运行时;

  • SOURCE, 编译器处理完该注解后不存储在class中
  • CLASS, 编译器把该注解存储在class字节码文件中
  • RUNTIME, 编译器把该注解存储在class字节码文件中,并且可以由JVM读取,在运行时可以通过反射获取到

@Target: 注解用于什么地方,我们定义为作用于方法和类上;

  • @Target(ElementType.TYPE) //接口、类、枚举、注解
  • @Target(ElementType.METHOD) //方法上
  • @Target(ElementType.FIELD) //字段、枚举的常量
  • @Target(ElementType.PARAMETER) //方法参数
  • @Target(ElementType.CONSTRUCTOR) //构造函数
  • @Target(ElementType.LOCAL_VARIABLE) //局部变量
  • @Target(ElementType.ANNOTATION_TYPE) //注解
  • @Target(ElementType.PACKAGE) ///包

@Documented: 这个Annotation可以被写入javadoc;

@Inherited: 这个Annotation 可以被继承,如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

自定义一个用于日志打印的注解

import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface MonitorLog {String description() default "";}

一个自定义注解就完成了。

配置AOP切面

aspectj相关注解的作用:
@Aspect :定义一个切面,标注在类上
@Pointcut :定义一个切点(在切点前后增强),定义需要拦截的内容,切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
execution表达式:
execution(* com.mutest.controller..*.*(..))表达式为例:

  • 第一个 * 号的位置:表示返回值类型,* 表示所有类型。
  • 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。
  • 第二个 * 号的位置:表示类名,* 表示所有类。
  • (…):这个星号表示方法名, 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
    annotation() 表达式:
    annotation() 方式是针对某个注解来定义切点。

5种切入方式:
@Around :环绕增强,可以在切入点前后织入代码,并且可以自由的控制何时执行切点
@Before :在切点之前,织入相关代码
@After :在切点之后,织入相关代码
@AfterReturning :在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景
@AfterThrowing :用来处理当织入的代码抛出异常后的逻辑处理
5种增强类型和具体实现类的执行顺序为:aroud、before、实现类、afterReturning、after、around

定义一个切面类MonitorLogAspect,声明一个切点,并织入

@Aspect
@Component
public class MonitorLogAspect{private final static Logger logger = LoggerFactory.getLogger(MonitorLogAspect.class);/** 以自定义 @MonitorLog 注解为切点 */@Pointcut("@annotation(com.webser.interceptor.MonitorLog)")public void aspectMethod() {}/** 定义环绕增强 可以监控和统计API执行时间,以计算API的性能*/@Around("aspectMethod()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();  //记录调用接口的开始时间Object result = joinPoint.proceed();  //执行切点,会依次调用@Before -> 接口逻辑代码 -> @After -> @AfterReturninglong end = System.currentTimeMillis();  //记录结束时间logger.info("Time-Consuming: {} ms", end - start);  //记录耗时return result;  //返回接口参数结果}/** 在切点之前织入 可以做一些信息的统计,比如获取用户的请求 URL 以及用户的 IP 地址等等*/@Before("aspectMethod()")public void doBefore(JoinPoint joinPoint) throws Throwable {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 获取 @WebLog 注解的描述信息String methodDescription = getAspectLogDescription(joinPoint);// 打印请求相关参数logger.info("============================== Start ==============================");// 打印请求 urllogger.info("URL : {}", request.getRequestURL().toString());// 打印 Http methodlogger.info("HTTP Method : {}", request.getMethod());// 打印调用 controller 的全路径以及执行方法logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());// 打印请求的 IPlogger.info("IP : {}", request.getRemoteAddr());// 打印请求入参logger.info("Request Args : {}", new Gson().toJson(joinPoint.getArgs()));}
}

使用

我们的切点是自定义注解 @MonitorLog, 所以我们仅仅需要在 Controller 控制器的每个接口方法添加 @MonitorLog 注解即可,如果我们不想某个接口打印出日志,不加注解就可以了。

 @PostMapping("/user/login")@MonitorLogpublic User userLogin(@RequestBody User user) {logger.info("user login...");return user;}

对于那些性能要求较高的应用,不想在生产环境中打印日志,只想在开发环境或者测试环境中使用,要怎么做呢?我们只需为切面添加 @Profile 就可以了,如下所示:

@Aspect
@Component
@Profile({"dev","test"})
public class MonitorLogAspect{...
}

这样就指定了只能作用于 dev 开发环境和 test 测试环境,生产环境 prod 是不生效的。
AOP如何实现的?
AspectJ是如何在没有修改源代码的情况下增加新功能的呢?注解@Aspect
多切面如何指定优先级?
假设说我们的服务中不止定义了一个切面,比如说我们针对 Web 层的接口,不止要打印日志,还要校验 token 等。要如何指定切面的优先级呢?也就是如何指定切面的执行顺序?
我们可以通过 @Order(i)注解来指定优先级,根据i的大小来确定优先级。
在切点之前,@Order 从小到大被执行,也就是说越小的优先级越高;
在切点之后,@Order 从大到小被执行,也就是说越大的优先级越高;

Spring Boot 自定义注解实现AOP切面织入相关推荐

  1. Java手写Spring的AOP(切面织入)小Demo--盲僧代理击飞 亚索接大实例

    在学习Spring的过程中总会很迷茫,打个@Aspect注解切面功能就有了,只要脱离了框架感觉自己还是什么都没会,所以自己猜了一下切面的思路,大概写了个小Demo,欢迎交流. 涉及知识:JDK动态代理 ...

  2. Spring Boot 自定义注解支持EL表达式(基于 MethodBasedEvaluationContext 实现)

    自定义注解 自定义 DistributeExceptionHandler 注解,该注解接收一个参数 attachmentId . 该注解用在方法上,使用该注解作为切点,实现标注该注解的方法抛异常后的统 ...

  3. Spring Boot自定义注解+AOP实现日志记录

    访问Controller打印的日志效果如下: *********************************Request请求*********************************** ...

  4. 从Spring源码探究AOP代码织入的过程

    随着不断地使用Spring,以及后续的Boot.cloud,不断的体会到这个拯救Java的生态体系的强大,也使我对于这个框架有了极大的好奇心,以至于产生了我为什么不能写一个这样的框架的思考. 通过自学 ...

  5. spring中自定义注解(annotation)与AOP中获取注解___使用aspectj的@Around注解实现用户操作和操作结果日志

    spring中自定义注解(annotation)与AOP中获取注解 一.自定义注解(annotation) 自定义注解的作用:在反射中获取注解,以取得注解修饰的类.方法或属性的相关解释. packag ...

  6. 一个简单的例子,学习自定义注解和AOP

    转载自   一个简单的例子,学习自定义注解和AOP 记得今年年初刚开始面试的时候,被问的最多的就是你知道Spring的两大核心嘛?那你说说什么是AOP,什么是IOC?我相信你可能也被问了很多次了. 1 ...

  7. 自定义注解加AOP怎么玩?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 乱敲代码 来源 | 公众号「乱敲代码」 前言 ...

  8. 通过自定义注解与aop统一存储操作记录

    模块开发完成后,接到通知需要添加操作记录功能,看着那一堆接口,如果一个方法一个方法的加,那真是太麻烦了.为了偷懒,就百度了一下,发现可以通过自定义注解和aop的形式来统一添加操作记录,只需要在每个方法 ...

  9. java方法设置切点_如何通过自定义注解实现AOP切点定义

    面向切面编程(Aspect Oriented Programming, AOP)是面向对象编程(Object Oriented Programming,OOP)的强大补充,通过横切面注入的方式引入其他 ...

  10. 40 个 Spring Boot 常用注解

    以下文章来源方志朋的博客,回复"666"获面试宝典 作者 | 谭朝红 链接 | ramostear.com 一.Spring Web MVC 与 Spring Bean 注解 Sp ...

最新文章

  1. 新书来了!《ActionScript 3.0游戏设计基础(第2版)》
  2. 技术负责人要停止写代码吗?
  3. Java 性能优化的五大技巧
  4. 区块链太太太太太难了,我花了一分钟才搞懂!
  5. PHP5各个版本的新功能和新特性总结
  6. 【Python】Pandas groupby加速处理数据
  7. 强烈推荐:SiteServer CMS开源免费的企业级CMS系统!
  8. Python总结:RuntimeError: matplotlib does not support generators as input
  9. 常用WebServices返回数据的4种方法比较 (转)
  10. 深入理解Java-final关键字
  11. yolov5继续训练_震惊! 它来了!YOLOv5它来了!
  12. ad17 linux 版本,Altium Designer这些不同版本的软件之间的区别有多大呢?
  13. 自动网络设计(NAS)Randomly Wired Neural Networks 何凯明团队
  14. Phoenix创建Hbase二级索引_尚硅谷大数据培训
  15. 74cms前台getshell漏洞
  16. 计算机专业师范类毕业论文,师范生毕业论文范文
  17. aptana+php++插件,aptana插件
  18. 台式计算机关闭屏幕快捷键,关闭电脑屏幕的快捷键
  19. 成都盛迈坤电商:提高店铺商品评分的方法
  20. 留存/复购cohort

热门文章

  1. 基于vue开发一个组件库
  2. 用计算机算重积分,北京邮电大学计算机学院高等数学重积分的计算.ppt
  3. JVM垃圾回收机制【简单介绍】
  4. clamav获取病毒库版本号
  5. 面试必备 | 机器学习这十大算法你确定会了吗?
  6. svn和git有什么区别
  7. NPDP,优秀产品经理必备证书
  8. Android模拟器加载自定义镜像
  9. Vscode新建vue模板
  10. T19136 交通指挥系统 题解