AOP 与 注解的那些事儿~
持续原创输出,点击上方蓝字关注我
目录
前言 什么是AOP? AOP的相关概念(面试常客) Spring Boot 如何整合AOP自定义一个注解? 使用拦截器如何自定义注解? 内部调用导致AOP注解失效 总结
前言
注解相信大家都用过,尤其是Spring Boot
这个框架,比如@Controller
。
这篇文章就来介绍下Spring Boot
中如何自定义一个注解,顺带介绍一下Spring Boot
与 AOP
如何整合。
什么是AOP?
AOP
即是面向切面,是Spring
的核心功能之一,主要的目的即是针对业务处理过程中的横向拓展,以达到低耦合的效果。
举个栗子,项目中有记录操作日志的需求、或者流程变更是记录变更履历,无非就是插表操作,很简单的一个save
操作,都是一些记录日志或者其他辅助性的代码。一遍又一遍的重写和调用。不仅浪费了时间,又将项目变得更加的冗余,实在得不偿失。
此时AOP
的就该出场了,能够在不改变原逻辑的基础上实现相关功能。
AOP的相关概念(面试常客)
要理解Spring Boot
整合Aop
的实现,就必须先对面向切面实现的一些Aop
的概念有所了解,不然也是云里雾里。
「切面(Aspect)」:一个关注点的模块化。以注解@Aspect
的形式放在类上方,声明一个切面。
「连接点(Joinpoint)」:在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候都可以是连接点。
「通知(Advice)」:通知增强,需要完成的工作叫做通知,就是你写的业务逻辑中需要比如事务、日志等先定义好,然后需要的地方再去用。增强包括如下五个方面:
@Before
:在切点之前执行@After
:在切点方法之后执行@AfterReturning
:切点方法返回后执行@AfterThrowing
:切点方法抛异常执行@Around
:属于环绕增强,能控制切点执行前,执行后,用这个注解后,程序抛异常,会影响@AfterThrowing
这个注解。
「切点(Pointcut)」:其实就是筛选出的连接点,匹配连接点的断言,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。
「引入(Introduction)」:在不改变一个现有类代码的情况下,为该类添加属性和方法,可以在无需修改现有类的前提下,让它们具有新的行为和状态。其实就是把切面(也就是新方法属性:通知定义的)用到目标类中去。
「目标对象(Target Object)」:被一个或者多个切面所通知的对象。也被称做被通知(adviced
)对象。既然Spring AOP
是通过运行时代理实现的,这个对象永远是一个被代理(proxied
)对象。
「AOP代理(AOP Proxy)」:AOP
框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring
中,AOP
代理可以是JDK
动态代理或者CGLIB
代理。
「织入(Weaving)」:把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ
编译器),类加载时和运行时完成。Spring
和其他纯Java AOP
框架一样,在运行时完成织入。
Spring Boot 如何整合AOP自定义一个注解?
在实际开发中对于横向公共的逻辑需要抽取出来,这时候就需要使用AOP
,比如日志的记录、权限的验证等等,这些功能都可以用注解轻松的完成。
下面介绍如何在Spring Boot
使用AOP
定义一个注解。
添加依赖starter
AOP
整合Spring Boot
有一个starter
,只需要添加依赖即可,如下:
<!--springboot集成Aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
开启AOP
在配置类上标注@EnableAspectJAutoProxy
注解即可开启AOP
,这个注解有什么用呢,源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {}
最重要的是如下一行代码:
@Import(AspectJAutoProxyRegistrar.class)
@Import
这个注解很熟悉了吧,快速注入一个类,这里是注入一个AnnotationAwareAspectJAutoProxyCreator
。
自定义一个注解
就以日志处理为例子,定义一个日志处理的注解,如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String value() default "";
}
定义一个切面
一个切面的满足条件如下:
类上标注了 @Aspect
注解注入到IOC容器中,比如 @Component
注解
定义的日志切面如下:
@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SysLogAspect {
}
@Order
指定了切面执行的优先级,假如有多个切面,肯定是要有先后的执行顺序,这样才能保证逻辑性。
定义切点表达式
这里需要拦截的肯定是@SysLog
这个注解,只要方法上标注了该注解都将会被拦截,表达式如下:
@Pointcut("@annotation(com.example.annotation_demo.annotation.SysLog)")
public void pointCut() {}
添加通知方法
既然是日志记录,肯定是在方法执行前,执行后都需要记录,因此需要定义一个环绕通知,如下:
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
//逻辑开始时间
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//todo,保存日志,自己完善
saveLog(point,beginTime);
return result;
}
测试
以上配置完成后即可使用,只需要在需要的方法上标注@SysLog
注解即可,如下:
@SysLog
@PostMapping("/add")
public String add(){
return "";
}
使用拦截器如何自定义注解?
使用AOP
自定义的注解在每个方法上都会被拦截验证,首先效率上就不高。
然而拦截器是在每个Controller
方法执行之前进行拦截,其他的方法都不会生效,比如service
方法。
比如权限的验证、防止瞬间重复点击等等需求就适合使用拦截器自定义的注解。
自定义一个注解
就以防止瞬间重复点击的例子来创建一个注解,如下:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
/**
* 默认失效时间5秒
*/
long seconds() default 5;
}
自定义拦截器
需要在请求执行之前完成验证,逻辑很简单,就是判断方法上有没有标注@RepeatSubmit
注解,代码如下:
/**
* description:重复提交注解的拦截器
*/
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod){
//只拦截标注了@RepeatSubmit该注解
HandlerMethod handlerMethod=(HandlerMethod)handler;
//获取controller方法上标注的注解
RepeatSubmit repeatSubmit = AnnotationUtils.findAnnotation(handlerMethod.getMethod(),RepeatSubmit.class);
//没有限制重复提交,直接跳过
if (Objects.isNull(repeatSubmit))
return true;
//todo 一个值,标志这个请求的唯一性,比如IP+userId+uri+请求参数
String flag="";
//存在即返回false,不存在即返回true
Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(flag, "", repeatSubmit.seconds(), TimeUnit.SECONDS);
if (ifAbsent!=null&&!ifAbsent)
//todo: 此处抛出异常,需要在全局异常解析器中捕获
throw new RepeatSubmitException();
}
return true;
}
}
注入的拦截器
将上述自定义的拦截器注入到Sprign Boot
中,这里不再演示了,前面教程有介绍过,请看:Spring Boot 第六弹,拦截器如何配置,看这儿~。
测试
在需要拦截方法上添加@RepeatSubmit
注解即可,如下:
@RepeatSubmit
@GetMapping("/add")
public String add(){
return "";
}
内部调用导致AOP注解失效
这个问题在事务中也是经常被忽略的问题,网上很多人说是AOP
的Bug
,其实在我看来这真不是一个BUG
,并且也是有办法解决的。
先来看一下失效的案例,如下:
public class ArticleServiceImpl{
@SysLog
public void A(){
......
}
public void B(){
this.A();
}
}
在上述的代码中,如果执行方法B
,则@SysLog
注解将会失效。
失效的原因
AOP
使用的是动态代理的机制,它会给类生成一个代理类,事务的相关操作都在代理类上完成。内部方式使用this
调用方式时,使用的是实例调用,并没有通过代理类调用方法,所以会导致事务失效。
解决方法
其实解决方法有很多,下面将会一一介绍。
1. 引入自身的Bean
在类内部通过@Autowired
将本身bean
引入,然后通过调用自身bean
,从而实现使用AOP
代理操作。代码如下:
public class ArticleServiceImpl{
/**
* 注入自身的Bean
*/
@Autowired
private ArticleService articleService;
@SysLog
public void A(){
......
}
public void B(){
articleService.A();
}
}
2. 通过ApplicationContext引入bean
通过ApplicationContext
获取bean
,通过bean
调用内部方法,就使用了bean
的代理类。
需要先创建一个ApplicationContext
的工具类获取ApplicationContext
,然后才能调用getBean()
方法,代码如下:
public class ArticleServiceImpl{
@SysLog
public void A(){
......
}
public void B(){
ApplicationContextUtils.getApplicationContext().getBean(ArticleService.class).A();
}
}
3. 通过AopContext获取当前类的代理类
此种方法需要设置@EnableAspectJAutoProxy
中的exposeProxy
为true
。
使用AopContext
获取当前的代理对象,代码如下:
public class ArticleServiceImpl{
@SysLog
public void A(){
......
}
public void B(){
((ArticleService)AopContext.currentProxy()).A();
}
}
总结
这篇文章介绍了AOP
的相关概念、AOP
实现自定义注解以及拦截器实现自定义注解,都是日常开发中必备的知识点,希望这篇文章对各位有所帮助。
源码已经上传,回复关键词
AOP注解
获取。
最后,别忘了点赞哦!!!
另外作者的第一本PDF
书籍已经整理好了,由浅入深的详细介绍了Mybatis基础以及底层源码,有需要的朋友回复关键词「Mybatis进阶」即可获取,目录如下:
AOP 与 注解的那些事儿~的更多相关文章
- 总结切面编程AOP的注解式开发和XML式开发
有段日子没有总结东西了,因为最近确实有点忙,一直在忙于hadoop集群的搭建,磕磕碰碰现在勉强算是能呼吸了,因为这都是在自己的PC上,资源确实有点紧张(搭建过程后期奉上),今天难得大家都有空(哈哈哈~ ...
- 利用spring AOP 和注解实现方法中查cache-我们到底能走多远系列(46)
主题:这份代码是开发中常见的代码,查询数据库某个主表的数据,为了提高性能,做一次缓存,每次调用时先拿缓存数据,有则直接返回,没有才向数据库查数据,降低数据库压力. public Merchant lo ...
- spring aop 使用注解方式总结
spring aop的注解方式:和xml的配置方式略有区别,详细如下: 1.首先还是建立需要的切面类:切面类里面定义好切点配置,以及所有的需要实现的通知方法. /** * */ package com ...
- 利用Spring AOP自定义注解解决日志和签名校验
转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...
- Spring 代理对象,cglib,jdk的问题思考,AOP 配置注解拦截 的一些问题.为什么不要注解在接口,以及抽象方法.
可以被继承 首先注解在类上是可以被继承的 在注解上用@Inherited /** * Created by laizhenwei on 17:49 2017-10-14 */ @Target({Ele ...
- ssm+redis整合(通过aop自定义注解方式)
此方案借助aop自定义注解来创建redis缓存机制. 1.创建自定义注解类 package com.tp.soft.common.util; import java.lang.annotation.E ...
- spring AOP自定义注解方式实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
- Spring的第四天AOP之注解版
Spring的第四天AOP之注解版 ssm框架 spring 在上一篇博客中,介绍了Spring的AOP的xml版本的使用,在这篇博客中,我将介绍一下,注解版的使用. 常用注解 注解 通知 @Aft ...
- [10] AOP的注解配置
1.关于配置文件 首先在因为要使用到扫描功能,所以xml的头文件中除了引入bean和aop之外,还要引入context才行: <?xml version="1.0" enco ...
随机推荐
- 题解:[COCI2011-2012#5] BLOKOVI
题解:[COCI2011-2012#5] BLOKOVI Description PDF : https://hsin.hr/coci/archive/2011_2012/contest5_tasks ...
- ucore操作系统学习笔记(二) ucore lab2物理内存管理分析
一.lab2物理内存管理介绍 操作系统的一个主要职责是管理硬件资源,并向应用程序提供具有良好抽象的接口来使用这些资源. 而内存作为重要的计算机硬件资源,也必然需要被操作系统统一的管理.最初没有操作系统 ...
- webpack5文档解析(上)
webpack5 声明:所有的文章demo都在我的仓库里 webpack5 起步 概念 webpack是用于编译JavaScript模块. 一个文件依赖另一个文件,包括静态资源(图片/css等),都会 ...
- day29 Pyhton 面向对象 继承进阶
一.内容回顾 初识继承 父类\基类\超类 子类\派生类 派生 :派生方法.派生属性 单继承 继承的语法class子类名(父类名) 解决开发中代码冗余的问题 子类继承了父类之后,可以调用父类中的方法 如 ...
- Oracle函数总结
<Trunc()> 描 述(实际应用):截取小数或者日期整数 简 介:https://baike.baidu.com/item/trunc/9657216?fr=al ...
- 【思维】UVA 11300 Spreading the Wealth
题目大意 vjudge链接 有n个人围圆桌而坐,每个人有Ai个金币,每个人可以给左右相邻的人一些金币. 若使得最终所有人金币数相等,求最小金币转移数. 数据范围 n<1000001 样例输入 3 ...
- UVA 10635 Prince and Princess—— 求LCS(最长公共子序列)转换成 求LIS(最长递增子序列)
题目大意:有n*n个方格,王子有一条走法,依次经过m个格子,公主有一种走法,依次经过n个格子(不会重复走),问他们删去一些步数后,重叠步数的最大值. 显然是一个LCS,我一看到就高高兴兴的打了个板子上 ...
- java Error opening registry key 'Software\JavaSoft\Java Runtime Environment'安装jdk1.7遇到的问题
最近开发项目要求jdk在1.7以上,我先卸载了jdk1.6,下载1.7下来安装好,配置下环境变量,可以是在输入java -version的时候发现: java Error opening regist ...
- dbvis 导出表结构 xls
1.dbvis 可以导出多种格式的文件,如SQL.XLS.TXT.HTML.JSON.CSV及XML. 需求场景(本场景是实际场景引申的场景,此处导出xls): 最近,遇到一个需求需要将表的结构数据导 ...
- 老板,来几道web玩玩
好久没做web了,没想到还能自己做出来555 [MRCTF2020]Ez_bypass 签到题8 给了源码,一个md5强类型比较,然后post传参,弱类型判断,直接1234567a绕过了 I put ...