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¶2=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部分:设置切入点,编写增强能力,织入
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |