AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、日志代码、事务控制代码、性能监控代码。
上图描述的就是未采⽤AOP思想设计的程序,当我们红⾊框中圈定的⽅法时,会带来⼤量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独⽴性很差。⽽下图中是采⽤了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运⽤动态代理技术,在运⾏期对需要使⽤的业务逻辑⽅法进⾏增强。
Spring 实现AOP思想使⽤的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现
任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过
我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。两种代理模式之前文章有过介绍->代理模式
Spring的AOP配置中,也和IOC配置一样,支持三类配置方式:
需求:横切逻辑代码是打印⽇志,希望把打印⽇志的逻辑织⼊到⽬标⽅法的特定位置(service层transfer方法)
引入aop坐标:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.1.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.13version>
dependency>
在applicationContext.xml添加AOP配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
"></bean>
<bean id="logUtils" class="com.lagou.edu.utils.LogUtils">bean>
<aop:config>
<aop:aspect id="LogAspect" ref="logUtils">
<aop:pointcut id="pt1" expression="execution(public void com.lagou.edu.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String , int))"/>
<aop:before method="beforeMethod" pointcut-ref="pt1">aop:before>
<aop:after-returning method="afterMethod" pointcut-ref="pt1">aop:after-returning>
<aop:after-throwing method="exceptionMethod" pointcut-ref="pt1">aop:after-throwing>
<aop:after method="successMethod" pointcut-ref="pt1">aop:after>
aop:aspect>
aop:config>
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
全匹配⽅式:
public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
访问修饰符可以省略
void
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
返回值可以使⽤*,表示任意返回值
*
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
*
....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Accou
nt)
包名可以使⽤..表示当前包及其⼦包
*
..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
* ...(com.lagou.pojo.Account)
参数列表,可以使⽤具体类型
基本类型直接写类型名称 : int
引⽤类型必须写全限定类名:java.lang.String
参数列表可以使⽤*,表示任意参数类型,但是必须有参数
* *..*.*(*)
参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
* *..*.*(..)
全通配⽅式:
* *..*.*(..)
<aop:config proxy-target-class="true">
使⽤aop:aspectj-autoproxy标签配置
<aop:aspectj-autoproxy proxy-target-class="true">aop:aspectjautoproxy>
<aop:before method="printLog" pointcut-ref="pointcut1">
aop:before>
正常执⾏时通知
<aop:after-returning method="afterReturningPrintLog" pointcutref="pt1">aop:after-returning>
异常通知
异常通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏产⽣异常之后,异常通知执⾏。如果切⼊点⽅法执⾏没有产⽣异常,则异常通知不会执⾏。
异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产⽣的异常信息。
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"
>aop:after-throwing>
最终通知执⾏时机
最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏。
最终通知执⾏时,可以获取到通知⽅法的参数。同时它可以做⼀些清理操作。
<aop:after method="afterPrintLog" pointcut-ref="pt1">aop:after>
环绕通知
环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,实现⼿动触发切⼊点⽅法的调⽤。
<aop:around method="aroundPrintLog" pointcut-ref="pt1">aop:around>
XML 中开启 Spring 对注解 AOP 的⽀持,注释掉了纯XML配置下的aop配置,添加开启注解驱动配置。
<aop:aspectj-autoproxy proxy-target-class="true"/>
示例
package com.lagou.edu.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogUtils {
@Pointcut(value = "execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))" )
public void pt1(){
}
/**
* 业务逻辑开始之前执行
*/
@Before("pt1()")
public void beforeMethod(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
System.out.println("业务逻辑开始执行之前执行");
}
/**方法结束执行(无论是否异常都执行)
*
*/
@After("pt1()")
public void afterMethod(){
System.out.println("业务逻辑结束执行之后执行");
}
/**业务逻辑正常时执行
*
*/
@AfterReturning(value = "pt1()",returning = "retVal")
public void successMethod(Object retVal){
System.out.println("业务逻辑正常时执行");
}
/**异常时执行
*
*/
@AfterThrowing("pt1()")
public void exceptionMethod(){
System.out.println("异常时执行");
}
/**环绕通知
*
* @param proceedingJoinPoint
*/
@Around("pt1()")
public void arrountMethod(ProceedingJoinPoint proceedingJoinPoint){
//如果不屑这句话原有业务逻辑就不会被执行
//Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
try{
System.out.println("环绕通知前置通知");
Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
System.out.println("环绕通知后置通知");
} catch (Throwable throwable) {
System.out.println("环绕通知异常通知");
throwable.printStackTrace();
} finally {
System.out.println("环绕通知正常通知");
}
System.out.println("环绕通知。。。。");
}
}
在使⽤注解驱动开发aop时,我们要明确的就是,是注解替换掉配置⽂件中的下⾯这⾏配置
<aop:aspectj-autoproxy proxy-target-class="true"/>
在配置类中使⽤如下注解进⾏替换上述配置
@Component
@Aspect
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class LogUtils {
@Pointcut(value = "execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))" )
public void pt1(){
}
...
}