
面向切面编程
2022/1/2大约 7 分钟
面向切面编程
Spring AOP是Spring框架中非常重要的功能模块之一,该模块提供了面向切面的编程支持,这种编程思想在事务处理、日志记录、权限控制、缓存等场景中被广泛使用。
1.AOP的概念
AOP(Aspect Oriented Programming)是一种面向切面的编程,它将应用程序的各个模块进行解耦,并把公共的代码抽取出来,通过切面进行统一处理。它与OOP的面向对象思想不同,AOP更关注应用程序的各个模块之间的关系,而不是对象之间的关系。
在OOP中,以类为基本单位进行模块化,而AOP则将应用程序的各个模块进行解耦,并把公共的代码抽取出来,通过切面进行统一处理。
这种横向的解耦,使得应用程序的各个模块之间更加松耦合,是传统的OOP无法实现的,因为后者实现的是父子关系的纵向复用,二者相辅相成,互为补充。
在AOP中,类与切面的关系如下:
从上图可知,通过切面Aspect分别在业务1和业务2中加入了日志记录、性能统计、安全控制等功能操作。
2.AOP的术语与实现逻辑
- Aspect:切面,封装横切到系统功能的类,也即定义切点、通知和连接点的组合。
- JoinPoint:连接点,即在程序中需要插入切面的位置(时间点)。
- Pointcut:切点,指需要处理的连接点,在Spring AOP中,所有的方法执行都是连接点,切入点(描述信息)确定需要处理的连接点。
- Advice:通知(增强处理),即在连接点上执行的切面逻辑。
- Introduction:引介,即在现有实现类(目标对象)中添加新的自定义方法或属性。
- Target:目标对象,即所有被切面所通知的对象。
- Proxy:代理,即创建目标对象的代理对象(子类),用于执行切面逻辑。
- Weaving:织入,即把切面逻辑代码插入到目标对象中,从而生成代理对象的过程。Spring AOP使用动态代理技术,即在运行时创建目标对象的代理对象,并把代理对象返回给调用方。
- Interceptor:拦截器,即在目标对象方法执行之前和之后执行切面逻辑。

3.基于注解的Spring AOP实现
3.1 Spring通知的分类
通知类型 | 介绍 | 应用 | 注解 |
---|---|---|---|
前置通知 | 在目标方法执行之前执行 | 权限管理 | @Before |
后置返回通知 | 在目标方法执行之后执行,抛出异常便不执行 | 关闭流、删除临时文件 | @AfterReturning |
环绕通知 | 在目标方法执行之前和之后执行,可以控制目标方法执行,也可以改变目标方法执行结果 | 日志管理与事务处理 | @Around |
异常通知 | 在目标方法抛出异常时执行 | 异常处理、日志记录 | @AfterThrowing |
后置最终通知 | 在目标方法执行之后执行,无论是否抛出异常 | 释放资源 | @AfterReturning |
引介通知 | 在目标类中添加新的方法或属性 | 修改目标类 | @Introduction |
3.2 基于注解开发AspectJ
本demo所需依赖:
- Spring四大基础包,
- Spring Commens Logging Bridge对应的Spring-jcl,
- AOP实现JAR包spring-aop,
- AspectJ实现JAR包aspectjweaver
接口
//创建接口aspectj/dao/TestDao.java
public interface TestDao {
//保存、修改、删除
public void save();
public void modify();
public void delete();
}
实现类
//创建实现类aspectj/dao/TestDaoImpl.java
@Repository("testDao")//对实现类声明Bean的注解,testDao为该Bean的name,可用于后续容器中取出该Bean
public class TestDaoImpl implements TestDao {
@Override
public void save() {
System.out.println("save");
}
@Override
public void modify() {
System.out.println("modify");
}
@Override
public void delete() {
System.out.println("delete");
}
}
// 启用行号
//切面类:在此类中编写各种类型的通知
@Aspect //@Aspect声明一个切面
@Component //@Component让此切面成为Spring容器管理的Bean(声明Bean的注解)
public class MyAspect {
//定义切入点,通知增强哪些方法(可以定义多个不同表达式的切入点,增强处理不同的方法)
//切入点表达式:"execution(* aspectj.dao.*.*(..))"
//execution(主体) *(任意返回类型) .*(任意类) .*(任意方法) (..)(任意参数)
@Pointcut("execution(* aspectj.dao.*.*(..))")
public void MyPointcut() {
}
//环绕通知
@Around("MyPointcut()")
//返回值为Object
//ProceedingJoinPoint(必要参数)为JoinPoint子接口,代表可执行的目标方法
//必须throw Throwable
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//开始
System.out.println("环绕开始:执行目标方法前,模拟开始事务");
//执行目标方法
Object result = pjp.proceed();
//结束
System.out.println("环绕结束:执行目标方法后,模拟关闭事务");
//返回目标方法的返回值
return result;
}
//前置通知
@Before("MyPointcut()")//MyPointcut是该切入点的定义方法
public void before(JoinPoint jp) {//参数非必要
System.out.print("前置通知:权限控制");
System.out.println(",目标对象:"+jp.getTarget()+",被增强处理的方法:"+jp.getSignature().getName());
}
//后置返回通知:发生异常不再执行
@AfterReturning("MyPointcut()")
public void afterReturning(JoinPoint jp) {
System.out.print("后置返回通知:模拟删除临时文件、关闭流");
System.out.println(",被增强处理的方法:"+jp.getSignature().getName());
}
//异常通知:发生异常才会执行
@AfterThrowing(value="MyPointcut()",throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("异常通知:程序执行异常"+e.getMessage());
}
//后置最终通知:发生异常仍然执行
@After("MyPointcut()")
public void after() {
System.out.println("后置最终通知:模拟控制资源");
}
}
//创建配置类
@Configuration//声明该类是一个配置类,相当于ch1的xml配置文件
@ComponentScan("aspectj")//自动扫描aspectj包中的注解,并注册为bean
@EnableAspectJAutoProxy//开启Spring对Aspectj的支持
public class config {
}
public class AOPTest {
//程序入口
public static void main(String[] args) {
//初始化Spring IOC容器
AnnotationConfigApplicationContext appCon
= new AnnotationConfigApplicationContext(config.class);//配置类.class
//从容器中获取增强后的目标对象(从容器中取出bean)
//appCon.getBean(接口名.class)或appCon.getBean(实例类名.class)
//getBean(Class type)参数示要加载的Bean的类型,要求在容器中该类型是唯一的。
TestDao testDaoAdvice = appCon.getBean(TestDao.class);
//执行方法
testDaoAdvice.save();
System.out.println("==========================");
testDaoAdvice.modify();
System.out.println("==========================");
testDaoAdvice.delete();
//关闭容器
appCon.close();
}
}
运行结果:
==========================
环绕开始:执行目标方法前,模拟开始事务
前置通知:权限控制,目标对象:aspectj.dao.TestDaoImpl@2cb2fc20,被增强处理的方法:save
save
后置返回通知:模拟删除临时文件、关闭流,被增强处理的方法:save
后置最终通知:模拟控制资源
环绕结束:执行目标方法后,模拟关闭事务
==========================
环绕开始:执行目标方法前,模拟开始事务
前置通知:权限控制,目标对象:aspectj.dao.TestDaoImpl@2cb2fc20,被增强处理的方法:modify
modify
后置返回通知:模拟删除临时文件、关闭流,被增强处理的方法:modify
后置最终通知:模拟控制资源
环绕结束:执行目标方法后,模拟关闭事务
==========================
环绕开始:执行目标方法前,模拟开始事务
前置通知:权限控制,目标对象:aspectj.dao.TestDaoImpl@2cb2fc20,被增强处理的方法:delete
delete
后置返回通知:模拟删除临时文件、关闭流,被增强处理的方法:delete
后置最终通知:模拟控制资源
环绕结束:执行目标方法后,模拟关闭事务
提示
本demo只依赖原生spring的部分jar包,没有使用springboot框架