Spring AOP-用代理代替繁琐逻辑
Spring AOP
基础概念
AOP 是一种面向切面的编程思想,通俗来讲,这里假如我们有多个方法。
@Component
public class Demo {
public void say1() {
System.out.println("say1~~~~~~~");
}
public void say2() {
System.out.println("say2~~~~~~~");
}
public void say3() {
System.out.println("say3~~~~~~~");
}
}
此时,如果我们要在每个方法执行完毕后,再输出一句话,则需要在每个方法里面都再加一个方法。
public void say1() {
System.out.println("say1~~~~~~~");
System.out.println("XX say good!!!");
}
public void say2() {
System.out.println("say2~~~~~~~");
System.out.println("XX say good!!!");
}
public void say3() {
System.out.println("say3~~~~~~~");
System.out.println("XX say good!!!");
}
这种方式,就会显得代码十分的冗余且不够优雅。
我们想一下,该实现的逻辑是在我们要在每个方法后面(切点)实现一个差不多的逻辑(切面实现),通过类似于下图所示的方式,将和主要业务无关的代码抽离出来,实现代码的解耦。
类似于下图所示的方式:
Spring 实现
首先,我们在一个 Spring Web 程序中,引入 spring-aop 的相关 jar 包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后,我们构建一个切面类,在该类里面,我们来定义要切入的点,以及切入后该做什么。
@Aspect
@Component
public class LogAspect {
@After("execution(public * say*(..))")
public void saveLog() {
System.out.println("XX say good!!!");
}
}
在这里,首先我们用 @Aspect
来声明这是一个切面,然后用 @Component
来让 Spring 容器可以扫描到该类。
紧接着,我们定义一个方法 saveLog()
,该方法的目的是在执行完 say1()
后,可以输出一条日志,所通过的方式便是注解: @After("execution(public * say*(..))")
。
有关于 aop 可以使用的注解,已经注解里配置的切点表达式,在后续再进行展开。
最后,我们在启动类上加上 @EnableAspectJAutoProxy
即可。
最后的实现效果,如下所示:
概念详解
切面
Aspect,要抽象出来的横跨多个地方的功能。
连接点
Joinpoint,定义在应用程序流程的何处插入切面进行执行。
切入点
Pointcut,一组连接点的集合。
其实在 AOP 中,这些概念点并不重要,重要是理解,以及如何在实战中进行演练。
可用切面
- before:先执行拦截代码,如果拦截代码错误,目标代码不执行;
- after:先执行目标代码,无论目标代码执行正确与否,都会执行拦截代码;
- afterReturning:和after不同的是,只有目标代码正确返回,才会执行拦截代码;
- afterThrowing:和after不同的是,只有目标代码抛出异常,才会执行拦截代码;
- around:能完全控制代码执行,并可以在目标代码前后,任意执行拦截代码。
切点表达式
execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
- motifiers-pattern?:修饰符,public、protect、private、*(所有类型);
- ret-type-pattern:返回值;
- declaring-type-pattern?:类路径匹配;
- name-pattern:方法名,支持*,_占位符;
- param-pattern:参数匹配,..代表所有参数类型;
- throws-pattern?异常类型匹配
其中?代表该项是可选项。
另外切点表达式是可以组合的,用 || 或 && 可以进行逻辑组合。(不止是 execution,也可以跟其他的切点表达式进行组合)
// 匹配所有方法,无法使用
execution(* *(..))
// 匹配所有 com.demo 包下的公有的,返回值为void的,方法名是say为前缀的,参数随意的方法
execution(public void com.demo say*(..))
@annotation
当执行的方法上有指定的注解,则算是匹配成功。
我认为该方式会更加的灵活些,在下面的实战演练中,我用的就是该方式,其拦截规则可以充分自定义,且可以在注解中,定义一些自己需要的值,然后在切面中进行使用。
args
用来匹配方法参数的。
- args():匹配不带参数的方法;
- args(type(String)):匹配一个参数,且类型为String的方法;
- args(..):匹配任意参数方法;
- args(String,..):匹配任意参数方法,但第一个参数类型是String的方法;
- args(..,String):匹配任意参数方法,但最后一个参数类型是String的方法;
该方法其实就是 execution 的变种形式,了解即可。
@args
也是用来匹配方法参数的,但是其匹配的逻辑是方法参数带有执行注解的方法。
其他方法,如 within、this、target、@target、@within、bean 不多做介绍了,平常用的也不多,以后有兴趣,或者在实际使用中有所涉及,再进行补充。
实战演练
在实战中,我们通过注解的方式来进行切入,
定义注解
/**
* 操作行为注解,通过该注解获取数据详情
*
* @author iceWang
* @date 2020/9/10
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperatorAnnotation {
String bodyType();
String operatorType();
}
在这里,我们定义一个注解,后续在要拦截的方法上,加上该注解即可。
其中 bodyType 代表我们要操作的实体类型,OperatorType 代表我们要操作的行为类型。
业务逻辑
@OperatorAnnotation(bodyType = LogAspect.BODY_TYPE_COMPANY, operatorType = LogAspect.OPERATOR_TYPE_DELETE)
public String deleteCompany(String companyUniqueId) {
Optional.of(companyMapper.deleteCompany(companyUniqueId))
.filter(result -> result > 0)
.orElseThrow(() -> new IllegalArgumentException("无法删除,请稍后再试!"));
return companyUniqueId;
}
因为个人原因,这里我们只展示一部分代码——根据 id 删除公司,定义实体类型为 company,操作类型为删除,为后续插入日志做数据铺垫。
切面定义
@Aspect
@Component
public class LogAspect {
public static final String BODY_TYPE_COMPANY = "company";
public static final String OPERATOR_TYPE_DELETE = "delete";
@AfterReturning(value = "@annotation(OperatorAnnotation)", returning = "result")
public void saveOperatorLog(JoinPoint joinPoint, Object result) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
OperatorAnnotation operatorAnnotation = methodSignature.getMethod().getAnnotation(OperatorAnnotation.class);
String bodyType = operatorAnnotation.bodyType();
String operatorType = operatorAnnotation.operatorType();
if (bodyType.contains(BODY_TYPE_COMPANY) && operatorType.contains(OPERATOR_TYPE_DELETE)) {
saveOperatorLog(bodyType, operatorType, result);
return;
}
}
/**
* 返回日志操作实体类
* @param bodyType
* @param operatorType
* @return
*/
private Operator getOperator(String bodyType, String operatorType) {
return Operator.builder()
.bodyType(bodyType)
.operatorType(operatorType)
.createTime(LocalDateTime.now())
.build();
}
/**
* 保存日志操作实体类
* @param bodyType
* @param operatorType
* @param result
*/
private void saveOperatorLog(String bodyType, String operatorType, Object result) {
Operator operator = getOperator(bodyType, operatorType);
operator.setOperatorUser(mdUserInfo.getPhone());
operator.setBody(result.toString());
operatorMapper.insert(operator);
}
}
在切面中,首先,我们用反射的方式来获取方法上的注解,通过注解获取实际的操作实体类型和操作类型,然后根据不同的实体类型和操作类型,执行不同的方法,将日志插入数据库中。
文章在公众号「iceWang」第一手更新,有兴趣的朋友可以关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯!
Spring AOP-用代理代替繁琐逻辑的更多相关文章
- 死磕Spring之AOP篇 - Spring AOP自动代理(三)创建代理对象
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- Hibernate 延迟加载的代理模式 和 Spring AOP的代理模式
Hibernate 延迟加载的代理模式 和 Spring AOP的代理模式 主题 概念 Hibernate 延迟加载的代理模式 Spring AOP的代理模式 区别和联系 静态代理和动态代理 概念 代 ...
- 死磕Spring之AOP篇 - Spring AOP自动代理(一)入口
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP自动代理(二)筛选合适的通知器
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- Spring AOP动态代理原理与实现方式
AOP:面向切面.面向方面.面向接口是一种横切技术横切技术运用:1.事务管理: (1)数据库事务:(2)编程事务(3)声明事物:Spring AOP-->声明事物 2.日志处理:3.安全验证 ...
- 什么是 Spring AOP 和代理
https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9403056301388627935% ...
- spring AOP 动态代理和静态代理以及事务
AOP(Aspect Oriented Programming),即面向切面编程 AOP技术,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装 ...
- Spring AOP动态代理实现,解决Spring Boot中无法正常启用JDK动态代理的问题
Spring AOP底层的动态代理实现有两种方式:一种是JDK动态代理,另一种是CGLib动态代理. JDK动态代理 JDK 1.3版本以后提供了动态代理,允许开发者在运行期创建接口的代理实例,而且只 ...
- 求求你,下次面试别再问我什么是 Spring AOP 和代理了!
https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9403056301388627935% ...
随机推荐
- super与this的区别,更进一步的区别!——Java学习
文章目录 this与super的含义 前言 例证 this super 总结 this与super的含义 在Java中,this有两层含义: 指示隐式参数的引用(就是当前对象的引用) 调用该类的其他构 ...
- Butterfly侧边栏引入一言
此教程涉及修改源码 背景 在修改每页显示7篇文章后,出现了这种情况. 这是完美主义(强迫症)的我所不能忍受的,有什么可以占据这里的呢?{% btn 'https://hitokoto.cn/',一言, ...
- 2、java数据类型转换
当数据类型不一样时,将会发生数据类型转换. 1.自动类型转换(隐式) 1. 特点:代码不需要进行特殊处理,自动完成. 2. 规则:数据范围从小到大. System.out.println(1024); ...
- CSS动画基础知识
CSS动画就是通过CSS (Cascading Style Sheet,层叠样式表)代码搭建的网页动画.它允许设计师和开发人员通过编辑网站的CSS代码来添加页面动画,从而轻松取代传统动画图片或flas ...
- 虚拟机解释器与bytecode对接
心头一直有个疑问,jvm虚拟是如何对接class中的字节码的?或者说在未进入 JIT优化阶段时,解释器是如何对接的? 大概阐述 hotspot通过C++代码在堆上申请一块空间,向里面填充一组指令,然后 ...
- 用WEB方式开发WPF桌面程序
因为疫情影响,公司裁员,结束了一年多的web开发经历,重新开始做桌面,新公司用的是WPF(居然用的是winform style...),当然这跟本文没有关系...上篇博客写的用后台api和前台浏览器控 ...
- 目录扫描、Nmap
一.基本定义 1.目录扫描: 扫描站点的目录,寻找敏感文件(目录名.探针文件.后台.robots.txt.备份文件等). 2.目录:站点结构,权限控制不严格. 3.探针文件:服务器配置信息,例:php ...
- SpringBoot集成Swagger2,3分钟轻松入手!
一.引入maven <dependency> <groupId>io.springfox</groupId> <artifactId>springfox ...
- 第5章 if 语句
第5章 if 语句 5.1 一个简单示例 cars = ['audi', 'bmw', 'subaru', 'toyota'] for car in cars: if car == 'bmw': pr ...
- python编写汉诺塔 Hanoi
#hanoi.py count = 0 def hanoi(n, src, dst, mid): #src为原1号柱子 dst 目标3号柱子 mid中间2号过渡柱子 global count #对全局 ...