Spring AOP的应用
目录
1、maven坐标配置与xml头配置
2、代理方式的选择与配置
3、AOP的三种配置方式
3.1、XML模式
3.1.1 创建目标类和方法
3.1.2 创建切面
3.1.3 切面xml配置与表达式说明
3.1.4 单测
3.2 纯注解模式
3.2.1 开启注解相关配置
3.2.2 创建目标类和方法
3.2.3 创建切面并配置切面
3.2.4 单测
3.3 XML+注解模式
3.3.1 与纯注解模式配置的区别
3.3.2 单测
AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、⽇志代码、事务控制代码、性能监控代码。
AOP的实现,主要靠动态代理技术,在运行期对需要使用的业务逻辑方法进行增强。
1、maven坐标配置与xml头配置
<!-- Spring AOP核心包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!-- Spring AOP注解配置包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!--junit 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
Spring基于XML的AOP配置前准备
在已经配置了ioc的xml文件上进行添加 bean.xml
<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.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
2、代理方式的选择与配置
Spring 实现AOP思想使⽤的是动态代理技术。
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。
当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。
当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术。
⽆论被代理对象是否实现接⼝,只要不是final修饰的类都可以采⽤cglib提供的⽅式创建代理对象。不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。配置方式有以下两种
-
使⽤aop:config标签配置
<aop:config proxy-target-class="true">
-
使⽤aop:aspectj-autoproxy标签配置
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP的⽀持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
3、AOP的三种配置方式
Spring的AOP和IOC一样,支持三类配置方式:
第⼀类:使⽤XML配置
第⼆类:使⽤纯注解配置
第三类:使⽤XML+注解组合配置
3.1、XML模式
3.1.1 创建目标类和方法
public class TargetObject{
public void printLog(String content) {
System.out.println("printLog打印目标内容:" + content);
}
}
3.1.2 创建切面
切面是指增强的代码,增强的代码类,就是切面类。比如:事务切面类,里面定义的方法就是事务相关的,像开启事务,提交事务,回滚事务等等。
public class LogUtil {
public void beforeLog() {
System.out.println("AOP切面前置通知打印");
}
public void afterLog() {
System.out.println("AOP切面后置通知打印");
}
public void afterReturnLog() {
System.out.println("AOP切面返回值通知打印");
}
public void afterThrowLog() {
System.out.println("AOP切面异常通知打印");
}
public void aroundLog() {
System.out.println("AOP切面环绕通知打印");
}
}
3.1.3 切面xml配置与表达式说明
bean.xml
<!-- 将相关Bean注入IOC容器-->
<bean id="logUtil" class="com.test.LogUtil"/>
<bean id="targetObject" class="com.test.TargetObject"/>
<!-- 声明AOP配置 -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut id="cutPrintLog" expression="execution(public * com.test.TargetObject.printLog(java.lang.String))"/>
<!-- 配置切面 -->
<aop:aspect id="aoplog" ref="logUtil">
<!-- 配置前置通知 -->
<aop:before method="beforeLog" pointcut-ref="cutPrintLog"/>
<!-- 配置后置通知 -->
<aop:after method="afterLog" pointcut-ref="cutPrintLog"/>
<!-- 配置返回值通知 -->
<aop:after-returning method="afterReturnLog" pointcut-ref="cutPrintLog"/>
<!-- 配置异常通知 -->
<!--<aop:after-throwing method="afterThrowLog" pointcut-ref="cutPrintLog"/>-->
<!-- 配置前置通知 -->
<!--<aop:around method="aroundLog" pointcut-ref="cutPrintLog"/>-->
</aop:aspect>
</aop:config>
属性说明:
method:⽤于指定通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
切入点表达式使用示例
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
全匹配⽅式:
public void com.test.TargetObject.printLog(java.lang.String)
访问修饰符可以省略
void com.test.TargetObject.printLog(java.lang.String)
返回值可以使⽤*,表示任意返回值
* com.test.TargetObject.printLog(java.lang.String)
包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
* ....TargetObject.printLog(java.lang.String)
包名可以使⽤..表示当前包及其⼦包
* ..TargetObject.printLog(java.lang.String)
类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
* ...(java.lang.String)
参数列表,可以使⽤具体类型
基本类型直接写类型名称 : int
引⽤类型必须写全限定类名:java.lang.String
参数列表可以使⽤*,表示任意参数类型,但是必须有参数
* *..*.*(*)
参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
* *..*.*(..)
全通配⽅式:
* *..*.*(..)
3.1.4 单测
@Test
public void test01() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
TargetObject target = (TargetObject) context.getBean("targetObject");
target.printLog("aaa");
}
打印结果:
AOP切面前置通知打印
printLog打印目标内容:aaa
AOP切面后置通知打印
AOP切面返回值通知打印
3.2 纯注解模式
3.2.1 开启注解相关配置
@Configuration
@ComponentScan("com.test")
// 开启注解支持
@EnableAspectJAutoProxy
public class LoggingConfig {
}
3.2.2 创建目标类和方法
@Component("targetObject")
public class TargetObject{
public void printLog(String content) {
System.out.println("printLog打印目标内容:" + content);
}
}
3.2.3 创建切面并配置切面
@Component
@Aspect
public class LogUtil {
@Pointcut("execution(* com.test.TargetObject.printLog(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforePrintLog(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置通知:beforePrintLog,参数是:"+
Arrays.toString(args));
}
@AfterReturning(value = "pointcut()",returning = "rtValue")
public void afterReturningPrintLog(Object rtValue){
System.out.println("后置通知:afterReturningPrintLog,返回值 是:"+rtValue);
}
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowingPrintLog(Throwable e){
System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e);
}
@After("pointcut()")
public void afterPrintLog(){
System.out.println("最终通知:afterPrintLog");
}
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("pointcut()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
//定义返回值
Object rtValue = null;
try{
//前置通知
System.out.println("前置通知");
//1.获取参数
Object[] args = pjp.getArgs();
//2.执⾏切⼊点⽅法
rtValue = pjp.proceed(args);
//后置通知
System.out.println("后置通知");
}catch (Throwable t){
//异常通知
System.out.println("异常通知");
t.printStackTrace();
}finally {
//最终通知
System.out.println("最终通知");
}
return rtValue;
}
}
3.2.4 单测
@Test
public void test02() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LoggingConfig.class);
TargetObject target = (TargetObject) context.getBean("targetObject");
target.printLog("aaa");
}
打印结果:
前置通知
前置通知:beforePrintLog,参数是:[aaa]
printLog打印目标内容:aaa
后置通知
最终通知
最终通知:afterPrintLog
后置通知:afterReturningPrintLog,返回值 是:null
3.3 XML+注解模式
3.3.1 与纯注解模式配置的区别
将 3.2.1 的LoggingConfig去掉,用xml配置;
将 3.2.2 和 3.2.3 的 @Component 去掉,用xml注入
<!-- XML中开启Spring对注解AOP的⽀持 -->
<aop:aspectj-autoproxy/>
<bean id="logUtil" class="com.test.LogUtil"/>
<bean id="targetObject" class="com.test.TargetObject"/>
3.3.2 单测
@Test
public void test01() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
TargetObject target = (TargetObject) context.getBean("targetObject");
target.printLog("aaa");
}
打印结果:
前置通知
前置通知:beforePrintLog,参数是:[aaa]
printLog打印目标内容:aaa
后置通知
最终通知
最终通知:afterPrintLog
后置通知:afterReturningPrintLog,返回值 是:null