深入理解 Spring AOP:面向切面编程的原理与应用
一、概述
AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。它以通过预编译方式和运行期动态代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
(一)AOP与OOP之间的区别
OOP:将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),
AOP:面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
(二)AOP的运用场景
实际项目中通常会将系统分为两大部分,一部分是核心业务,一部分是非核业务。在编程实现时我们首先要完成的是核心业务的实现,非核心业务一般是通过特定方式切入到系统中,这种特定方式一般就是借助AOP进行实现。
(三)AOP原理与术语
Aop运行原理:
Aop术语:
切面(Aspect):一个使用@Aspect注解描述的类的对象。@
通知(Advice):在切面上某一特定的连接点上要执行的使用特定通知注解描述的方法。一般用于提供非核心事务。
连接点(JoinPoint):程序执行过程中某个特定的运行节点。程序运行到该节点前后会执行通知方法。
切入点(PointCut):对多个连接点的一种定义,一般可以理解为多个连接点的集合。
连接点与切入点:
连接点可以理解为被代理类的每个被拦截的方法。
切入点可以理解被拦截的类。使用@Pointcut注解通过切入点表达式定义。
二、开发步骤
(一)原理:
通过@Aspect注解标识AOP中的切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行。
通过@Pointcut注解描述切面中的一个特定方法结合实参(切入点表达式)来定义切入点(连接点集合)。该特定方法无实际意义,但不可或缺,其主要作用是承载@Pointcut注解。
通过5种通知注解描述切面方法,这些方法用于进行功能扩展或控制目标对象的招待。
其中@Around描述的方法最为特殊,方法用于控制目标方法的执行。即,目标方法的执行是从@Around描述的方法开始,并从@Around描述的方法结束。在@Around方法执行过程中会调用其它4种通知注解描述的通知方法。
该注解描述的方法需要ProceedingJoinPoint类型的形参。该类型定义了一个连接点。该类型的对象用于封装要执行的目标方法相关的信息。该类型的对象只能作为@Around注解描述的方法的形参。
其余4种通知方法通常用于扩展功能。
此4种通知所描述的方法只能使用JoinPoin类型形参来定义目标方法。
特别注意
JoinPoin的包为:org.aspectj.lang.JoinPoint;千万不能引错包。
当JoinPoin引成:org.aopalliance.intercept.Joinpoint;时
(二)所需依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(三)开发步骤
(四)五种通知方法的执行流程
三、开发增强
(一)通知增强
1.作用:通知注解主要用于描述控制目标方法执行或对目标方法进行扩展的方法。
2.通知分类:
在基于Spring Aop编程的过程中,基于AspectJ框架标准,Spring中定义了五种类型的通知:
@Around:访问目标类或方法之前进入该注解描述的方法。
@Before:如果存在该注解描述的方法,则在@Around描述的方法中,调用(ProceedJoinPoint)jp.proceed()方法(即目标方法)之前调用该注解描述的方法。
@AfterReturning:如果存在该注解描述的方法,则在jp.proceed()方法成功执行之后立即执行该注解描述的方法。
@AfterThrowing:如果存在该注解描述的方法,则在jp.proceed()方法异常之后立即执行该注解描述的方法。
@AfterThrowing注解常用属性:
value属性:用于指定切入点表达式或切入点表达式的引用。
throwing属性:指定要处理的异常。该属性的属性值必须与描述方法的异常参数名相同。如果不同则会抛出异常如下图所示:
@After:如果存在该注解描述的方法,@AfterReturning|@AfterThrowing描述的方法执行之后执行该注解描述的方法。
当@After注解描述的方法执行之后,程序会将控制权交还于@Around描述的方法,继续执行@Around描述的方法后续语句。
使用说明:
在切面类中使用什么通知,由业务决定,并不需要把所有通知都写入切面类。
3.通知注解使用方式:
方式一:可以通过切入点表达式作为实参直接传递给通知注解的方式使用。
应用场景:一个切面类中需要处理的连接点的通知方法较少时可以使用该方式。
特点:使用该方式时,省略由此类连接点组成的切入点的定义。
方式二:可以通过将切入点表达式的引用作用实参传递给注解的方式使用。
应用场景:一个切面类中需要处理的连接点的通知方法较多。
特点:
使用该方式时,需要使用@Pointcut注解定义此类连接点构成的切入点。该注解的实参则为切入点表达式。
而切入点表达式的引用即为:@Pointcut注解描述的方法。
(二)切入点表达式增强
1.作用:用于指定要扩展功能的类或方法。
2.分类:
bean表达式:用于匹配指定bean对象的所有方法。
within表达式:用于匹配包下所有类内的所有方法。
excution表达式:用于按指定语法规则匹配到具体方法。
@annotation表达式:用于匹配指定注解描述的方法。
3.使用位置:
位置一:切入点表达式做为实参赋值于@Pointcut注解的value属性。
示例:@Pointcut("bean(goodsSvsImpl)")
位置二:切入点表达式做为实参赋值给通知注解。
示例:@Around("bean(goodsSvsImpl)")
4.bean表达式(重点):
应用场景:
bean表达式一般应用于类级别,实现粗粒度的切入点表达式的定义。
使用方式:
方式一:通过具体bean对象定义切入点。
示例:bean("userServiceImpl")。
特点:此时的切入点则由该对象的所有方法对应的连接点组成。
方式二:通过通配符*带指定后缀的bean对象定义切入点。
示例:bean("*ServiceImpl")。
特点:此时的切入点则由带后缀的所有bean对象中的所有方法对应的连接点组成。
使用说明:
如果通过具体bean对象指定切入点表达式时,则该bean对象是由Spring容器管理的一个bean对象,表达式内部的名字应该是Spring容器中某个bean的name。(Spring管理的bean对象的命名规则:在默认情况下,Spring管理的bean对象名字是其类名首字母小写)。
5.within表达式(了解):
应用场景:
within表达式应用于包、类级别,实现粗粒度的切入点表达式的定义。
使用方式:
within("aop.service.UserSvsImpl"):指定包中具体类的所有方法对应的连接点构成的切入点。
within("aop.service.*"):指定当前包下的所有类的所有方法对应的连接点构成的切入点。
within("aop.service..*"):指定当前包及其子包中所有类的所有方法对应的连接点构成的切入点。
使用说明:
如果within应用于类级别时,此时,表达式中的类名必须是类名,不是Spring管理的该类的对象的名字。
适用场景:
对所有业务bean都要进行功能增强,但是bean名字没有规则。
按业务模块(不同包下的业务)对bean对象进行业务功能增强。
6.execution表达式(重点):
应用场景:
execute表达式应用于方法级别,实现细粒度的切入点定义。
语法:
execute(returnType packageName.className.methodName([paramList]))。
示例:
execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
execution(* aop.service..*.*(..)) 万能配置。
7. @annotation表达式(重点):
应用场景:
@annotation表达式应用于方法级别,实现细粒度的切入点表达式定义。
开发步骤:
第一步:定义自定义注解。
第二步:使用自定义注解描述要拦截的目标方法。
第三步:使用@annotation(自定义注解)定义切入点表达式,并使用该切面表达式赋值给通知注解和定义扩展功能的方法。
(三)切面优先级设置实现
(四)Spring AOP关键对象与术语总结
四、SpringAOP数据库事务管理
(一)SpringAOP数据库事务管理方式
1.声明式数据库事务管理原理
默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生了checked异常,则该数据库操作还是会提交commit。
checked异常:通常是Exception类的子类(非RuntimeException类)
表示无效,不是程序中可以预测的。比如无效的用户输入,文件不存在,网络或数据库链接错误等不由程序内部控件产生的异常。
此类异常必须在代码中显式处理。使用try-catch或throw方式。
定义此类异常必须继承Exception类。
unchecked异常:通常是RuntimeException类的子类。
表示程序逻辑错误。
不需要在代码中显式地捕获处理此类异常。
2.Spring声明式数据库事务管理方法:
通过start transaction|begin显示开启事务
通过commit|rollback结束事务。
MySql数据库默认开启数据库事务,通过手动提交或执行回滚来结束事务。
3.Spring事务管理特点
Spring的事务管理是线程安全的。
事务方法的嵌套调用会产生事务传播。
父类声明的@Transaction会对子类的所有方法进行事务增强。子类覆盖重写父类方式可覆盖其@Transaction中的声明配置。
当@Transaction描述类时,类中的方法可以通过属性配置来覆盖类上的@Transaction配置。(类上配置全局是可读写,可在某个方法上改为只读)
(二)@Transaction实现声明式数据库事务管理
1.@Transaction注解属性
属性说明:
2.使用规则
描述对象:接口、接口方法和实现类、接口实现方法。
Spring团队建议:该注解应用于描述实现类或实现类的方法。在接口上使用时,只能当设置了基于接口的代理时,该注解才生效。
方法的访问权限:该注解只能描述public修饰的方法。
@Transaction的事务开启是基于接口的或者是基于类的代理对象被创建。因此在同一个类中的一个方法调用另一个带事务的方法时,将不会开启事务。
多线程下事务管理因为线程不属于Spring托管,故线程不能够默认使用Spring事务,也不能获取Spring注入的Bean。
在被Spring声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。
一个使用了@Transaction的方法,如果方法内包含多线程的使用,方法内部出现异常,不会回滚线程中调用方法的事务。
3.@Transaction 注解的实质
该注解使用JDBC的事务方式进行事务管理。并通过动态代理机制实现。
(三)Spring事务特性
1.实现事务的隔离级别
通过@Transaction的isolation属性指定。
实现示例:
2.实现事务传播行为:
通过@Transaction的propagation属性指定。
实现方式:
事务嵌套:
(四)Spring事务回滚规则
(五)多线程事务管理