6. Spring AOP 应用 AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、日志代码、事务控制代码、性能监控代码。
6.1 相关术语 1. 业务主线 在讲解AOP术语之前,我们先来看一下下面这两张图,它们就是第三部分案例需求的扩展(针对这些扩 展的需求,只进行分析,在此基础上去进一步回顾AOP,不进行实现)
上图描述的就是未采用AOP思想设计的程序,当我们红色框中圈定的方法时,会带来大量的重复劳动。 程序中充斥着大量的重复代码,使我们程序的独立性很差。而下图中是采用了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运用动态代理技术,在运行期对需要使用的业务逻辑方法进行增强。
2. AOP术语
连接点 :方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点是一种候选点切入点 :指定AOP思想想要影响的具体方法是哪些,描述感兴趣的方法Advice增强 :第一个层次:指的是横切逻辑 第二个层次:方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫做方位点,描述的是具体的特殊时机) Aspect切面 :切面概念是对上述概念的一个综合Aspect切面 = 切入点+增强 = 切入点(锁定方法) + 方位点(锁定方法中的特殊时机) + 横切逻辑 众多的概念,目的就是为了锁定要在哪个地方插入什么横切逻辑代码。
6.2 代理选择 Spring 实现AOP思想使用的是动态代理技术 默认情况下,Spring会根据被代理对象是否实现接口来选择使用JDK还是CGLIB。当被代理对象没有实现任何接口时,Spring会选择CGLIB。当被代理对象实现了接口,Spring会选择JDK官方的代理技术,不过 我们可以通过配置的方式,让Spring强制使用CGLIB。
6.3 AOP的配置方式 在Spring的AOP配置中,也和IoC配置一样,支持3类配置方式。
第一类:使用XML配置 第二类:使用XML+注解组合配置 第三类:使用纯注解配置 6.4 AOP实现 需求:横切逻辑代码是打印日志,希望把打印日志的逻辑织入到目标方法的特定位置(service层transfer方法)
1. xml模式 引入依赖 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aop</artifactId > <version > 5.1.12.RELEASE</version > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.9.4</version > </dependency >
核心配置 <bean id ="logUtils" class ="com.lagou.edu.utils.LogUtils" > </bean > <aop:config > <aop:aspect id ="logAspect" ref ="logUtils" > <aop:pointcut id ="pt1" expression ="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))" /> <aop:before method ="beforeMethod" pointcut-ref ="pt1" /> <aop:around method ="arroundMethod" pointcut-ref ="pt1" /> </aop:aspect > </aop:config >
**切入点表达式 **
上述配置实现了对 TransferServiceImpl 的 updateAccountByCardNo 方法进行增强,在其执行之前,输出了记录日志的语句。这里面,我们接触了一个比较陌生的名称:切入点表达式,它是做什么的呢?我们往下看。
概念及作用 切入点表达式,也称之为AspectJ切入点表达式,指的是遵循特定语法结构的字符串,其作用是用于对符合语法格式的连接点进行增强。它是AspectJ表达式的一部分。 关于AspectJ AspectJ是一个基于Java语言的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切入点表达式的部分,开始支持AspectJ切入点表达式。 切入点表达式使用示例 全限定方法名 访问修饰符 返回值 包名.包名.包名.类名.方法名(参数列表) 全匹配方式: 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.Account) 包名可以使用..表示当前包及其子包 * *..*.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 类名和方法名,都可以使用.表示任意类,任意方法 * *..*.*(com.lagou.pojo.Account) 参数列表,可以使用具体类型 基本类型直接写类型名称 : int 引用类型必须写全限定类名:java.lang.String 参数列表可以使用*,表示任意参数类型,但是必须有参数 * *..*.*(*) 参数列表可以使用..,表示有无参数均可。有参数可以是任意类型 * *..*.*(..) 全通配方式: * *..*.*(..)
2. xml + 注解 使用注解配置切面:
@Component @Aspect public class LogUtils { @Pointcut("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++) { Object arg = args[i]; System.out.println(arg); } System.out.println("业务逻辑开始执行之前执行......." ); } @After("pt1()") public void afterMethod () { System.out.println("业务逻辑结束时执行,无论异常与否都执行......." ); } @AfterThrowing("pt1()") public void exceptionMethod () { System.out.println("异常时执行......." ); } @AfterReturning(value = "pt1()", returning = "retVal") public void successMethod (Object retVal) { System.out.println("业务逻辑正常时执行......." ); } public Object arroundMethod (ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知中的beforemethod...." ); Object result = null ; try { }catch (Exception e) { System.out.println("环绕通知中的exceptionmethod...." ); }finally { System.out.println("环绕通知中的after method...." ); } return result; } }
开启aop注解驱动:
3. 纯注解模式 只需要在xml + 注解模式的基础下,定义一个配置类或者在已有配置类的基础上加上一个注解@EnableAspectJAutoProxy
即可。基础下,定义一个配置类或者在已有配置类的基础上加上一个注解@EnableAspectJAutoProxy
即可。