首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

什么是AOP,以及在Springboot中自定义AOP

编程知识
2024年08月17日 14:20

AOP (Aspect Oriented Programming)一般译为面向切面编程

Aspect [ˈæspekt] n.方面;层面;(动词的)体
那么AOP 面相切面编程具体是指什么,它和之前的OOP 面相对象编程又有什么区别和联系。
先说OOP,面相对象编程简单来说,万物皆可视为对象,我们要做的就是将万物(业务逻辑中的虚拟物体),抽象为一个个对象,进而为这些抽象的物体丰富各种能力和特性(方法和属性)。从而抽象出一整段的业务逻辑,作为我们的系统。

但是在OOP的开发过程中,我们发现尽管我们已经抽象出很多对象了,但是对象之间的某些方法是有一些共性的,如果进一步抽象,则整体的抽象粒度过于小,抽象粒度过于复杂。在这种情况下,我们需要换一个角度,将这些共性的点,作为一个切入点,将我们的业务逻辑注入到里边去,直接去增强这些切入点。而这种增强的对象某个同性点的编程方式,我们就称之为AOP,即面相切面编程。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )

举个例子:

学校的老师,每天都需要统计上课工时,政府的公务员,每天都需要统计办公工时,同时办公室的电脑也要统计每天的开机时长。

他们本质上都是对象,如果统计每天的运行时间,我们现在有两个办法来实现:

1,各自实现各自的统计办法,实现简单,但是修改复杂,而且有大量重复逻辑。

2,定义统一的接口,老师、公务员、电脑实现同样的接口,这样减少了重复逻辑,但是又实现复杂,整个抽象粒度太细了。

此时我们就可以通过AOP编程的方式,将老师、公务员、机器的办公,作为一个切入点,在这个切入点作一些对象之外的处理工作。这就是所谓的面向切面编程。这看着有点像作弊,所以很多人将aop视为面向对象编程的一个补充。是从第三方的视角,来看待面向对象编程的,如下图:

下面我们来看看,如何在当下最流行的java框架springboot框架中,实现面向切面编程:
面相切面编程主要实现三个基本操作:
1、设置切入面 Aspect (放到哪里)
2、编写增加能力,即注入到业务逻辑中的新特性 (放什么)
3、织入,即将新特性注入到原有的业务逻辑中。(怎么放进去)
假设我们现在已经有一个简易的Springboot工程,实现两个字符串的连接:

首先我们添加相关的pom依赖:

1         <dependency>
2             <groupId>org.springframework.boot</groupId>
3             <artifactId>spring-boot-starter-aop</artifactId>
4         </dependency>
5         <dependency>
6             <groupId>org.aspectj</groupId>
7             <artifactId>aspectjweaver</artifactId>
8             <version>1.9.7</version>
9         </dependency>

接着我们按照3个基本操作来添加aop能力:

1、设置切入面
设置切面的常用方式有两种,我们依次来看
(1)使用注解的形式

 1 package com.example.demo.learnaop;
 2 
 3 
 4 import java.lang.annotation.ElementType;
 5 import java.lang.annotation.Retention;
 6 import java.lang.annotation.RetentionPolicy;
 7 import java.lang.annotation.Target;
 8 
 9 @Target(ElementType.METHOD)
10 @Retention(RetentionPolicy.RUNTIME)
11 public @interface LogAop {
12 }

如上先定义一个注解:@Target,我们设置为method,@Retention,我们设置为runtime,该注解可被标记到方法中,同时运行时期要使用该注解。(关于java的注解,属于java的基础知识,但是在新兴的框架中,他的作用越来越大,我抽时间会写一篇相关的文章)

定义如下的切面类:
随意定义一个切面方法,方法的注解@PointCut,标记好要增加的注解的全限定类名。
然后我们就可以在我们想要设置的切面出设置切入点了,如下

1 public class AopAdvice {
2 
3     @Pointcut("execution(* com.example.demo.learnaop.DoService.learnMinus(..))")
4     public void logAopCut() {
5         int a=1;
6         System.out.println("point cut 123 " );
7         //  log.warn("ex advice1");
8     }
9 }

业务代码像这样添加定义好的注解,如红色字体:

1     @LogAop //像这样
2     @Override
3     public String learnMinus(String para1, String para2) {
4         //   log.warn("start Minus");
5         System.out.println("service learn minus "+para1 +para2 );
6         return para1 + "-" + para2;
7     }

这种方式比较符合目前的编程思路,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )尽可能的使用各种注解来代替原有的各种配置,降低配置的维护难度。

(2)使用execution 表达式
我们可以不定义注解,直接在切面方法上设置,要切入的点,如下:

1 public class AopAdvice {
2 
3     @Pointcut("execution(* com.example.demo.learnaop.DoService.learnMinus(..))")
4     public void logAopCut() {
5         int a=1;
6         System.out.println("point cut 123 " );
7         //  log.warn("ex advice1");
8     }
9 }

execution后边的部分,我们使用的表达式称之为 execution表达式

这是一种类似于正则的表达式,总体的结构如下图

问号部分我们可填也可以不填,同时我们可以使用*,..来实现模糊匹配,

* 可以模糊匹配,某一个层级的选项,或者某一层级一部分的选项,比如我们想省略某一层级包名,也可以省略方法名的某一部分。
.. 可以用来省略多级选项。
限于篇幅有限,这里就不过多的介绍execution表达式了。
这样我们就可以直接根据全限定路径,直接指定某一层级方法作为切入点了。
这里有两点需要注意的是:
如果使用的注解表达式,则注解加入到接口中,是不能在实现类中添加切入点的,换句话说不会直接生效。
注意:使用execution表达式时,如果表达式匹配的是父类或接口,则对应子类的切入点是会生效的。这里也和java中注解不会直接继承,继承类和接口实现类,却可以替代类和接口中的方法是一个效果。

2、编写增强能力
我们继续在OPTAopAdvice类中添加如下方法:

方法的注解可以依次使用
@Before 切入面执行执行
@After 切入面返回之后
@Around 切入面环绕
@AfterReturning 切入面正常返回后
@AfterThrowing 切入面异常返回后
@After 是包含@AfterReturning @AfterThrowing两种场景的。
像下面这样,我们就可以定义几个增强能力

 1 public class AopAdvice {
 2 
 3   
 4     @Before("logAopCut()")
 5     public Object logBefore() {
 6         System.out.println("log before !!!" );
 7         return "123456654rjdkkgjlkjg";
 8     }
 9 
10     @Before("optAopCut()")
11     public Object optBefore() {
12         System.out.println("opt before !!!" );
13         return "123456654rjdkkgjlkjg";
14     }
15 
16     @After("optAopCut()")
17     public void optAfter() {
18         System.out.println("opt after !!!" );
19 
20     }
21 
22     @After("logAopCut()")
23     public void logAfter() throws Throwable {
24         System.out.println("log after !!!" );
25 
26     }
27 
28     @Around("optAopCut()")
29     public Object optAround1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
30         System.out.println("around start1 " );
31         Object proceed = proceedingJoinPoint.proceed();
32         System.out.println("around end1 " );
33         return proceed;
34     }
35 
36     @Around("optAopCut()")
37     public Object optAround2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
38         System.out.println("around start2 " );
39         Object proceed = proceedingJoinPoint.proceed();
40         System.out.println("around end 2" );
41         return proceed;
42     }
43 }

3、织入
这一步理论上来说最复杂,但是和具体业务逻辑又距离最远,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )所以spring早已替我们封装好了
我们只需要在OPTAopAdvice类上添加@Aspect @Component,分别表示要进行切入处理,和进行springboot的bean管理。

整体的代码如下:

controller层

 1 package com.example.demo.learnaop;
 2 
 3 import lombok.extern.slf4j.Slf4j;
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import org.springframework.web.bind.annotation.GetMapping;
 6 import org.springframework.web.bind.annotation.RequestParam;
 7 import org.springframework.web.bind.annotation.RestController;
 8 
 9 import java.util.Date;
10 
11 /**
12  * @discription
13  */
14 @Slf4j
15 @RestController
16 public class Controller {
17 
18     @Autowired
19     private DoService doService;
20 
21     @Autowired
22     private DoServiceImpl doServiceImpl;
23 
24     @Deprecated
25     @GetMapping("/learn/add")
26     public String learnAdd(@RequestParam("para1") String para1, @RequestParam("para2") String para2) {
27         //   log.debug("show plugin Profile {} ,{}", para1, para2);
28         System.out.println("controller learn add " + para1 + para2);
29         return doService.learnMinus(para1, para2) + doService.learnAdd(para1, para2);
30     }
31 
32     @Deprecated
33     @GetMapping("/learn/minus")
34     public String learnMinus(@RequestParam("para1") String para1, @RequestParam("para2") String para2) {
35         //   log.debug("show plugin Profile {} ,{}", para1, para2);
36         System.out.println("controller learn Minus " + para1 + para2);
37         Date date = new Date();
38         return doService.learnMinus(para1, para2) + date.getTime() + date.getSeconds() + "";
39     }
40 
41 }

service层

 1 package com.example.demo.learnaop;
 2 
 3 /**
 4  * @discription
 5  */
 6 public interface DoService {
 7 
 8     String learnAdd(String para1, String para2);
 9 
10 
11     String learnMinus(String para1,  String para2);
12 
13 }

 

 1 package com.example.demo.learnaop;
 2 
 3 
 4 import lombok.extern.slf4j.Slf4j;
 5 import org.springframework.stereotype.Service;
 6 
 7 /**
 8  * @discription
 9  */
10 @Slf4j
11 @Service
12 public class DoServiceImpl implements DoService {
13     @Override
14     public String learnAdd(String para1, String para2) {
15         System.out.println("service learn add "+para1 +para2 );
16         return para1 + "+" + para2;
17     }
18 
19     @OPTAop
20     @Override
21     public String learnMinus(String para1, String para2) {
22         //   log.warn("start Minus");
23         System.out.println("service learn minus "+para1 +para2 );
24         return para1 + "-" + para2;
25     }
26     
27     @Override
28     public void learnNothing() {
29         System.out.println("service learn do nothing  " );
30 
31     }
32 }

服务端口我们设置为8081,

请求如下url

http://127.0.0.1:8081/learn/minus?para1=1a&para2=2b

控制台输出如下

 1 controller learn Minus 1a2b
 2 around start1 
 3 around start2 
 4 log before !!!
 5 opt before !!!
 6 service learn minus 1a2b
 7 opt after !!!
 8 log after !!!
 9 around end 2
10 around end1 

注意看这里有两个细节

1、执行顺序,

around先执行,然后才会执行 before、after 接着又跳转回around,也就是说before 和after 更接近切面点,这一点我们在处理诸如分布式锁的场景要考虑到。

2、可以在一个切入点加入多个方法,

切入顺序一般是按照代码的加载顺序(书写顺写)来加入的,尽管是在一个切入点加入了多个增强方法,但是只执行一遍切入面的代码。(他的原理是什么呢?如何模仿或者实现呢,我后文会详细介绍)

以上说的比较多,这里我们总结一下,

1、aop是面向对象的补充,是针对多个对象的共同特性,我们统一增强能力的一个途径。

2、自定义aop编程只要实现3部分:设置切入点,编写增强能力,织入

From:https://www.cnblogs.com/jilodream/p/18364443
本文地址: http://shuzixingkong.net/article/1186
0评论
提交 加载更多评论
其他文章 Jetpack架构组件学习(5)——Hilt 注入框架使用
原文: Jetpack架构组件学习(5)——Hilt 注入框架使用-Stars-One的杂货小窝 本篇需要有Kotlin基础知识,否则可能阅读本篇会有所困难! 介绍说明 实际上,郭霖那篇文章已经讲得比较明白了(具体参考链接都贴在下文了),这里简单总结下: 如果按照之前我们的MVC写法,我们可以直接在
Jetpack架构组件学习(5)——Hilt 注入框架使用 Jetpack架构组件学习(5)——Hilt 注入框架使用 Jetpack架构组件学习(5)——Hilt 注入框架使用
debian10环境安装rtpengine
操作系统 :debian 10.13_x64 rtpengine版本:10.5 最新的debian12环境可通过apt直接安装rtpengine,但工作中有时候还会涉及到debian10这样的老系统,今天记录下debian10环境安装rtpengine的笔记,并提供相关演示效果及资源下载。 我将从以
debian10环境安装rtpengine debian10环境安装rtpengine debian10环境安装rtpengine
T113s工业套件简述
T113s工业套件简述 提示 T113开发交流QQ群:120575746 此开发板的任何问题都可以在我们的论坛交流讨论&#160;https://forums.100ask.net/c/aw/ 硬件简述​ 100ASK_T113s3-Industrial-DevKit 是百问网设计的一款专门针对于工
T113s工业套件简述 T113s工业套件简述
并查集扩展
并查集扩展 目录并查集扩展普通并查集例题:1.洛谷P1197 星球大战2.洛谷P1955 程序自动分析带权并查集例题:1.洛谷P2024 食物链2.洛谷P1196 银河英雄传说3.洛谷P5937 Parity Game扩展域并查集例题:1.洛谷P1525 关押罪犯 普通并查集 例题: 1.洛谷P11
并查集扩展
第一章 内网环境搭建
用户须知 1.免责声明:本教程作者及相关参与人员对于任何直接或间接使用本教程内容而导致的任何形式的损失或损害,包括但不限于数据丢失、系统损坏、个人隐私泄露或经济损失等,不承担任何责任。所有使用本教程内容的个人或组织应自行承担全部风险。 详情免责声明 版权声明 交流群 原文教程 前言 1. 更多教程请
第一章 内网环境搭建 第一章 内网环境搭建 第一章 内网环境搭建
北漂日志第1话:惨淡销量、后续发展
北漂日志第1话:惨淡销量、后续发展 简单介绍下作者背景: 北漂两年,在一家小公司任职后端岗位 热爱编程,尝试开发App,打造品牌,实现副业转正 目前发布的产品有:离线背单词应用《深海记词》 开发 选择开发一款离线App是一个不错的选择,尤其是对于没有经验的独立开发者。 不需要考虑服务器的成本、繁琐的
北漂日志第1话:惨淡销量、后续发展 北漂日志第1话:惨淡销量、后续发展
用whl文件安装Anaconda中的GDAL
本文介绍在Anaconda环境下,基于.whl文件安装Python中高级地理数据处理库GDAL的方法~
用whl文件安装Anaconda中的GDAL 用whl文件安装Anaconda中的GDAL 用whl文件安装Anaconda中的GDAL
python中怎样指定open编码为ansi
本文简要介绍了Python中open函数打开文件时,可以通过encoding参数来指定文件的编码方式,给出了详细的代码示例。