目录
1 什么是aop
aop是通过预编译方式和运行期动态代理实现程序功能统一维护的一种技术。aop是又叫做面向切面编程,是oop的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容,是函数式编程的一种衍生泛型。利用aop可以对业务逻辑的各个部分进行隔离,从而使得各个部分耦合度降低,提高程序的重用性,同时提高了开发的效率。
2 业务场景
参考博客:细说Spring——AOP详解(AOP概览)_啦啦啦的博客-CSDN博客_aop
我们可能会在不同的方法中用到同样的代码逻辑。比如:日志记录,性能统计、安全控制、事务处理、异常处理。
我们可以用几张图来表达下(以上博客觉得图画的比较直观):
由上图可知,当我们实现了不同的功能时,中间用到了同样的代码逻辑,如果我们不怕麻烦的话方式一:每个方法调用的话都调用下该段逻辑,结果可以完成,但会产生大量的冗余代码。
方法二:将该通用的代码逻辑抽取出来,当有方法调用时,就执行该方法。这种方式比方式一有了一定的提高。
方式三:将方法注入到接入的某个地方
3 aop中的相关术语
连接点(JoinPoint):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行.
切点(Pointcut):每个程序都有很多连接点,即连接点是程序类中客观存在的事物,aop通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询数据库中数据的条件。切点和连接点是一对多的关系,我们一个切点可以定位多个连接点。
增强(Advice):advice定义了在pointcut里面定义程序点具体要做的操作,它通过before,after,around来区别是在每个joint point之前,还是代替执行的代码
切面(aspect):切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,springAop就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中
引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样即使一个业务类原本没有实现某个接口,通过aop的引介功能,我们可以动态的为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类
目标对象(Target):增强逻辑的织入目标类。如果没有aop,目标业务类需要实现所有逻辑,而在aop帮助下,目标业务类只需要实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用aop动态织入到特定的连接点上
织入(Weaving):织入是将增强添加对目标连接点上的过程。aop像是一台织布机,将目标类,增强或者引介通过aop这台织布机编织到一起。根据不同的织入要求,aop有三种织入的方式
a:编译期织入,这要求使用特殊的Java编译器
b: 类装载期织入,这要求使用特殊的类装载器
c: 动态代理织入,在运行期为目标类添加增强生成子类的方式
spring采用动态代理织入,而AspectJ采用编译器织入和类装载器期织入
代理对象(aop Proxy):既增强后产生的对象
参照上述博客,我们可以根据生活中的例子来理解下上述概念:
某次某高中高三理科4班进行摸底考试,考试成绩下来之后,成绩有好有坏,班主任决定对成绩总成绩在500分以下的学生进行心理谈话来促使他们学习进步。
我们可以做一个简单的类比:
JoinPoint可以使我们理科4班中所有可能被谈话的学生
Pointcut切点就是我们根据特征找到我们要切入的点即是500分一下
Advice增强就是我们要进行的动作即是进行心理谈话
aspect就是我们切点和增强的组合
Spring Aop底层实现,是通过jdk动态代理或者CGlib代理在运行时期在对象初始化阶段织入代码的
jdk动态代理是基于接口实现的
CGlib是基于类的集成实现的
4 aspect切面通知的种类
@Before
前置通知:在目标方法调用之前执行。注意:无论方法是否遇到异常都执行
@AfterReturning
后置通知:在目标方法执行后执行,前提是目标方法没有遇到异常,如果有异常则不执行通知
@AfterThrowing
异常通知:在目标方法抛出异常时执行,可以获取异常信息
@Around
环绕通知:最强大的通知类型,可以控制目标方法的执行(通过调用:proceedingJoinPoint.proceed())可以在目标执行全过程中执行
5 代码示例
步骤一:建立工程引入相应依赖
步骤二:定义业务逻辑类MainLogic.java
步骤三:定义切面类MainAspect.java
步骤四:将业务逻辑类和切面类加入到容器中MainConfigAop.java
步骤五:进行测试
步骤一:建立工程引入相应依赖
<?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>aop</artifactId>
<version>1.0-SNAPSHOT</version>
<name>aop</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</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.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
</dependencies>
</project>
步骤二:定义业务逻辑类MainLogic.java
public class MainLogic {
public int calculation(int a,int b){
System.out.println("**********进入主要业务逻辑类************");
int c = a/b;
return c;
}
public int calculationNo(int a,int b){
System.out.println("**********测试看这个业务类有没有切入进来************");
int c = a/b;
return c;
}
}
步骤三:定义切面类MainAspect.java
@Aspect
public class MainAspect {
/**
* 定义切点,并定义切点在哪些地方执行,采用@Pointcut注解完成,如@Pointcut(public * com.xxx.xxx.*.*(...))
* 规则:修饰符(可以不写,但不能用*)+返回类型+那些包的类+那些方法+方法参数 “*”代表不限 “..”两个点代表参数不限
* 定义切点抽取共用的切入点表达式
* @Pointcut("execution(public int com.liubujun.aop.MainLogic.*(..))")表示MainLogic类中的所有方法
* @Pointcut("execution(public int com.liubujun.aop.MainLogic.calculation(int,int))") 表示只有MainLogic中的calculation方法
*
*/
@Pointcut("execution(public int com.liubujun.aop.MainLogic.*(..))")
private void pointCutCommon(){};
/**
* 在目标方法之前引入;切入点表达式(指定在哪个方法切入)
* joinPoint.getSignature().getName() 知道调用的是哪个方法
* Arrays.toString(joinPoint.getArgs()) 方法调用的参数打印出来
* @param joinPoint
*/
@Before(value = "pointCutCommon()")
public void logStart(JoinPoint joinPoint){
System.out.println("Start>>>>>>>"+joinPoint.getSignature().getName()+">>>>>"+ Arrays.toString(joinPoint.getArgs()));
}
@After(value = "com.liubujun.aop.MainAspect.pointCutCommon()")
public void logAfter(JoinPoint joinPoint){
System.out.println("After>>>>>>>"+joinPoint.getSignature().getName()+">>>>>"+ Arrays.toString(joinPoint.getArgs()));
}
/**
* 用return 来接收返回值
* @param object
*/
@AfterReturning(value = "pointCutCommon()",returning = "object")
public void logReturn(Object object){
System.out.println("AfterReturning>>>>>>>返回结果"+object);
}
/**
* 用throw来接收异常
* @param joinPoint 此参数放在第一位
* @param e
*/
@AfterThrowing(value = "execution(public int com.liubujun.aop.MainLogic.*(..))",throwing = "e")
public void logException(JoinPoint joinPoint,Exception e){
System.out.println("AfterThrowing>>>>>>>"+joinPoint.getSignature().getName()+">>>>>异常信息{"+ e.getStackTrace()+"}");
}
}
步骤四:将业务逻辑类和切面类加入到容器中MainConfigAop.java
@EnableAspectJAutoProxy
@Configuration
public class MainConfigAop {
/**
* 逻辑类切入到容器中
* @return
*/
@Bean
public MainLogic mainLogic(){
return new MainLogic();
}
/**
* 切面类切入到容器中
* @return
*/
@Bean
public MainAspect mainAspect(){
return new MainAspect();
}
}
步骤五:进行测试一(抛出异常)
public class AppTest {
@Test
public void testAop(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigAop.class);
MainLogic mainLogic = applicationContext.getBean(MainLogic.class);
mainLogic.calculation(10,0);
applicationContext.close();
}
}
运行结果:
步骤五:进行测试二(正常返回)
public class AppTest {
@Test
public void testAop(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigAop.class);
MainLogic mainLogic = applicationContext.getBean(MainLogic.class);
mainLogic.calculation(10,2);
applicationContext.close();
}
}
运行结果: