Spring AOP
AOP(Aspect-Oriented Programming,面向切面编程),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离。从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 AOP用来封装多个类的公共行为,将与业务无关,却被业务模块共同调用的逻辑封装起来,减少系统的重复代码。减低模块之间的耦合度。AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用。
Spring AOP 是基于 AOP 编程模式的一个框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。有两种实现方式:基于接口的 JDK 动态代理和[基于继承的CGLIB 动态代理。
AspectJ 是一个基于 Java 语言的 AOP 框架。AspectJ ,提供了一个专门的编译器,在编译时提供横向代码的植入。
Spring JDK动态代理
基于接口的动态代理
特点
- 代理对象必须实现一个或多个接口
- 以接口的形式接收代理实例,而不是代理类
Spring CGLIB动态代理
基于子类的动态代理
特点
- 代理对象不能被 final 修饰
- 以类或接口形式接收代理实例
JDK代理和CGLIB代理的区别
JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。而 CGLIB 动态代理是利用 ASM 开源包,加载代理对象类的 class 文件,通过修改其字节码生成子类来处理。
JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不能声明成 final 类型。
JDK与CGLIB动态代理的性能比较
生成代理实例性能:JDK > CGLIB
代理实例运行性能:JDK > CGLIB
Spring AOP:基于AspectJ XML开发
AOP专业术语
- Joinpoint(连接点):所谓连接点是指那些,被拦截的点在spring中这些点指的是方法,因为Spring只支持方法类型的连接点
- Pointcut(切入点):指我们要对那些jointpoint进行拦截的定义
- advice(通知/增强):是指我们要对哪些Joinpoint之后要做的事情就是通知。通知的类型包括,前置通知,后置通知,异常通知,最终通知,环绕通知。
- Introduction(引介):引介是一种特殊的通知,在不修改代码的前提下,Introduction可以在运行期为类动态添加一些方法或Field
- Targt(目标对象):代理的目标对象
- weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,AspectJ采用编译期织入和类装载其期织入。
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
- Aspect(切面):是切入点和通知(引介)的结合。
基于 XML 的声明式是指通过 Spring 配置文件的方式来定义切面、切入点及通知,而所有的切面和通知都必须定义在 aop:config 元素中。命名空间:
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
...
</beans>
定义切面使用<aop:aspect>
元素定义切面
可以将定义好的Bean转换为切面Bean,所以在使用<aop:aspect>
之前需要先定义一个普通的spring Bean,示例
<aop:config>
<!-- id 用来定义该切面的唯一表示名称,ref 用于引用普通的 Spring Bean -->
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
定义切入点<aop:pointcut>
<aop:pointcut>
用来定义一个切入点,当 <aop:pointcut>
元素作为<aop:config>
元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当<aop:pointcut>
元素作为<aop:aspect>
元素的子元素时,表示该切入点只对当前切面有效。示例
<aop:config>
<!-- id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。
execution 格式为:
execution(modifiers-pattern returning-type-pattern declaring-type-pattern name- pattern(param-pattern)throws-pattern)
其中:
returning-type-pattern、name-pattern、param-pattern 是必须的,其它参数为可选项。
modifiers-pattern:指定修饰符,如 private、public。
returning-type-pattern:指定返回值类型,*表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。
declaring-type-pattern:指定方法的包名。
name-pattern:指定方法名,*代表所有,set* 代表以 set 开头的所有方法。
param-pattern:指定方法参数(声明的类型),(..)代表所有参数,(*)代表一个参数,(*,String)代表第一个参数可以为任何值,第二个为 String 类型的值。
throws-pattern:指定抛出的异常类型。
例如:execution(* net.biancheng.*.*(..))表示匹配 net.biancheng 包中任意类的任意方法。-->
<aop:pointcut id="myPointCut"
expression="execution(* net.biancheng.service.*.*(..))"/>
</aop:config>
定义通知
<aop:aspect id="myAspect" ref="aBean">
<!-- 前置通知 -->
<aop:before pointcut-ref="myPointCut" method="..."/>
<!-- 后置通知 -->
<aop:after-returning pointcut-ref="myPointCut" method="..."/>
<!-- 环绕通知 -->
<aop:around pointcut-ref="myPointCut" method="..."/>
<!-- 异常通知 -->
<aop:after-throwing pointcut-ref="myPointCut" method="..."/>
<!-- 最终通知 -->
<aop:after pointcut-ref="myPointCut" method="..."/>
....
</aop:aspect>
Spring AOP:基于AspectJ注解开发
@AspectJ 注解有以下两种方法:
- 使用@Configuration和@EnableAspectJAutoProxy注解
@Configuration
@EnableAspectJAutoProxy
public class Appconfig {
}
- 基于XML配置,在 XML 文件中添加标签启用 @AspectJ注解
<aop:aspectj-autoproxy>
定义切面@Aspect
AspectJ 类和其它普通的 Bean 一样,可以有方法和字段,不同的是 AspectJ 类需要使用 @Aspect 注解,
定义切入点@Pointcut
// 要求:方法必须是private,返回值类型为void,名称自定义,没有参数
@Pointcut("execution(*net.biancheng..*.*(..))")
private void myPointCut() {
}
定义通知
常用注解:
注解 | 作用 |
---|---|
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于ThrowAdvice。 |
@After | 用于定义最终final通知,不管是否异常,该通知都会执行。 |
通知的执行顺序:前置通知–>代码块–>后置通知(AfterReturning)或异常通知–>最终通知
实例:
logger类
package com.chenrui.utils;
import org.aspectj.lang.ProceedingJoinPoint;
public class logger {
public void beforePrintLog(){
System.out.println("beforePrintLog前置通知");
}
public void afterReturningPrintLog(){
System.out.println("afterReturning");
}
public void afterThrowingPrintLog(){
System.out.println("afterThrowing");
}
public void afterPrintLog(){
System.out.println("afterPrintLog最终通知");
}
public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint){
Object rtValue = null;
try{
Object[] args = proceedingJoinPoint.getArgs();
System.out.println("aroundPrintLog前置通知");
rtValue = proceedingJoinPoint.proceed(args);
System.out.println("aroundPrintLog后置通知");
return rtValue;
}catch (Throwable t){
System.out.println("aroundPrintLog异常通知");
throw new RuntimeException(t);
}finally {
System.out.println("aroundPrintLog最终通知");
}
}
}
业务层
package com.chenrui.service.impl;
import com.chenrui.service.accountService;
public class accountServiceImpl implements accountService{
@Override
public void saveAccount() {
System.out.println("执行保存方法");
// int i=1/0;
}
@Override
public void updateAccount(int i) {
System.out.println("执行更新方法");
}
@Override
public void deleteAccount() {
System.out.println("执行删除方法");
}
}
bean.xml文件
<?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: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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置Service类-->
<bean id="accountService" class="com.chenrui.service.impl.accountServiceImpl"></bean>
<!--配置logger类-->
<bean id="logger" class="com.chenrui.utils.logger"></bean>
<!--配置aop-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.chenrui.service.impl.accountServiceImpl.*(..))"/>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="beforePrintLog" pointcut-ref="pt"></aop:before>
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt"></aop:after-returning>
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt"></aop:after-throwing>
<aop:after method="afterPrintLog" pointcut-ref="pt"></aop:after>
<!-- <aop:around method="aroundPrintLog" pointcut-ref="pt"></aop:around>-->
</aop:aspect>
</aop:config>
</beans>
测试类
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class aoptest {
@Test
public void aopTest(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
accountService accountService = (accountService)ac.getBean("accountService");
accountService.saveAccount();
// accountService.deleteAccount();
// accountService.updateAccount(2);
}
}
结果:
beforePrintLog前置通知
执行保存方法
afterReturning
afterPrintLog最终通知
加入环绕通知后结果:
beforePrintLog前置通知
aroundPrintLog前置通知
执行保存方法
aroundPrintLog后置通知
aroundPrintLog最终通知
afterPrintLog最终通知
afterReturning