Aop概述
OOP 原则之一是每个类都应该有一个单一的职责。因此,我们创建了多个类来共同处理一项任务,其中每个类负责处理特定的功能。然而,一些关注点打破了这个原则,而 AOP 帮助我们解决了这些问题。
什么是AOP (Aspect Oriented Programming)呢?AOP是面向切面编程,相比较OOP面向对象编程来说,Aop关注的不再是程序中的某个类、某些方法,Aop考虑的是一种面到面的切入,即层与层之间的切入,所以称之为切面。AOP的底层实现原理是 动态代理 (JDK + CGLIB)。
Aop可以做什么呢?主要应用于日志记录、性能统计、安全控制、事务处理等公共功能性(非业务性)的重复使用。
比如美元支付和人民币支付都实现了支付这个接口,现在需要在支付业务中增加日志功能,这个需要可以通过 装饰器模式或代理模式 实现。但如果需要添加日志功能的业务类数量很多,此时手动为每个业务类增加装饰器或代理,工程量比较大且耦合度也比较大。
上面的需求可以通过AOP模式实现,实现代码复用且达到松耦合的要求。
Aop的特点:
- 降低模块与模块之间的耦合度,提高业务代码的聚合度(高内聚低耦合)
- 提高了代码的复用性
- 提高系统的扩展性(高版本兼容低版本)
- 可以在不影响原有功能的基础上添加新的功能。
Aop 术语
- Joinpoint(连接点)
- Pointcut(切入点)
- Advice(通知)
- Aspect(切面)
- Target(目标对象)
- Weave(织入)
- Introduction(引入)
Joinpoint(连接点)
被拦截的每个点,spring中指被拦截的每一个方法,Spring Aop 一个连接点代表一个方法的执行,即被拦截的方法或被代理的方法。
Pointcut(切入点)
对连接点进行拦截的定义(匹配规则定义,规定拦截哪些方法,对哪些方法进行处理),Spring有专门的表达式语言定义。即定义拦截条件,满足条件的方法才被拦截(代理)被切入。
Advice(通知)
拦截到每一个 Joinpoint(连接点)后所要作的操作。即代理方法,扩展委托(被代理)的方法(扩展被拦截的方法)。
通知类型 |
标签 |
说明 |
前置通知 |
before |
连接点执行前的行为 |
返回通知 |
afterReturn |
连接点执行正常结束后的行为 |
异常抛出通知 |
afterThrow |
连接点执行发生异常后的行为 |
最终通知 |
after |
连接点执行结束后的行为,无论是否发生异常,类似finally |
环绕通知 |
around |
包围一个连接点(Joinpoint)的通知。最强大的一种通知类型(包含了上面4种通知),环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行(类似nodejs的中间件概念) |
Aspect(切面)
切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截到方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面是横切关注点的抽象(即切面类,在这个切面类中定要切入点和通知)。
Target(目标对象)
被代理的对象
Weave(织入)
将切面应用到目标对象并生成代理对象的这个过程叫做织入。
Introduction(引入)
在不修改原有程序代码的情况下,在程序运行期间为类动态添加方法或者字段的过程叫做引入。
概念关系图
Aop实现
环境搭建
pom.xml 导入依赖包
- srping-context
- aspectjweaver
pom.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId> <artifactId>shsxt-spring-aop</artifactId> <version>1.0-SNAPSHOT</version>
<name>shsxt-spring-aop</name> <url>http://www.example.com</url>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties>
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> </dependencies>
<build> </build> </project>
|
配置 Spring 配置
spring.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="org.example"/>
<aop:aspectj-autoproxy /> </beans>
|
使用注解实现Aop
定义业务类
UserService1 2 3 4 5 6 7 8 9 10
| package org.example.service;
import org.springframework.stereotype.Service;
@Service public class UserService { public void test(String userName){ System.out.println("User service test..." + userName); } }
|
定义切面
LogCut1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| package org.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component;
@Component @Aspect public class LogCut {
@Pointcut("execution(* org.example.service..*.*(..))") public void cut() {}
@Before(value="cut()") public void before() { System.out.println("cut切入点的前置通知..."); }
@AfterReturning(value="cut()") public void afterReturn(){ System.out.println("cut切入点的返回通知..."); }
@After(value="cut()") public void after(){ System.out.println("cut切入点的最终通知..."); }
@AfterThrowing(value="cut()", throwing = "e") public void afterThrow(Exception e){ System.out.println("cut切入点的异常通知..." + e.getMessage()); }
@Around(value="cut()") public Object around(ProceedingJoinPoint pjp){ System.out.println("cut切入点 环绕通知的 前置消息"); Object result = null; try{ result = pjp.proceed(); System.out.println(pjp.getTarget() + "===" + pjp.getSignature()); System.out.println("cut切入点 环绕通知的 返回消息"); } catch(Exception e){ e.printStackTrace(); System.out.println("cut切入点 环绕通知的 异常消息"); } catch(Throwable throwable){ throwable.printStackTrace(); }finally{ System.out.println("cut切入点 环绕通知的 最终消息"); }
return result; } }
|
测试
UserServiceTest1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package org.example;
import static org.junit.Assert.assertTrue;
import org.example.service.UserService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserServiceTest { @Test public void test(){ ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = ac.getBean("userService", UserService.class); userService.test("隔壁老王"); } }
|
运行结果如图
使用配置文件实现Aop
与使用注解实现Aop的区别是把注解改成配置文件。
定义切面
LogCut21 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package org.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.stereotype.Component;
@Component public class LogCut2 { public void cut() {}
public void before() { System.out.println("cut切入点的前置通知..."); }
public void afterReturn(){ System.out.println("cut切入点的返回通知..."); }
public void after(){ System.out.println("cut切入点的最终通知..."); }
public void afterThrow(Exception e){ System.out.println("cut切入点的异常通知..." + e.getMessage()); }
public Object around(ProceedingJoinPoint pjp){ System.out.println("cut切入点 环绕通知的 前置消息"); Object result = null; try{ result = pjp.proceed(); System.out.println(pjp.getTarget() + "===" + pjp.getSignature()); System.out.println("cut切入点 环绕通知的 返回消息"); } catch(Exception e){ e.printStackTrace(); System.out.println("cut切入点 环绕通知的 异常消息"); } catch(Throwable throwable){ throwable.printStackTrace(); }finally{ System.out.println("cut切入点 环绕通知的 最终消息"); }
return result; } }
|
配置文件配置Aop
spring.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="org.example"/>
<aop:config> <aop:aspect ref="logCut2"> <aop:pointcut id="cut" expression="execution(* org.example.service..*.*(..))"/>
<aop:before method="before" pointcut-ref="cut" /> <aop:after-returning method="afterReturn" pointcut-ref="cut"/> <aop:after method="after" pointcut-ref="cut"/> <aop:after-throwing method="afterThrow" throwing="e" pointcut-ref="cut"/> <aop:around method="around" pointcut-ref="cut"/> </aop:aspect> </aop:config> </beans>
|