Spring AOP 应用

文章目录

  • Spring AOP 应用
    • AOP 相关术语
      • 业务主线
      • AOP 术语
    • Spring中AOP的代理选择
    • Spring中AOP的配置方式
    • Spring中AOP的实现
      • XML模式
      • XML+注解模式
      • 纯注解模式

Spring AOP 应用

AOP本质:在不改变原有业务代码逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、日志代码、事务控制代码、性能监控代码。

AOP 相关术语

业务主线

在讲解AOP术语之前,我们先来看一下下面这两张图
Spring AOP 应用_第1张图片

上图描述的就是未采用AOP思想设计的程序,当我们红色框中圈定的方法时,会带来大量的重复劳动,程序中充斥着大量的重复代码,使我们的程序独立性很差。
而下图中是采用了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运用动态代理技术,在运行期对需要使用的业务逻辑方法进行增强。

Spring AOP 应用_第2张图片

AOP 术语

名称 解释
Joinpoint(连接点) 它指的是那些可以用于把增强代码加入到业务主线中的点,那么由上图我们可以看出,这些点指的就是方法。在方法执行的前后通过动态代理技术加入增强的代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。
Pointcut(切入点) 它指的是那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我们看出表现层transfer 方法就只是连接点,因为判断访问权限的功能并没有对其增强。
Advice(通知/增强) 它指的是切面类中用于提供增强功能的方法,并且不同的方法增强的时机是不一样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法正常执行之后执行;而回滚事务要在要在业务方法执行产生异常后执行等等。那么这就是通知的类型,其分类有:前置通知、后置通知。异常通知、最终通知、环绕通知
Target(目标对象) 它指的是代理的目标对象,即被代理对象。
Proxy(代理) 它指的是一个类被AOP织入增强后产生的代理类,即代理对象。
Weaving(织入) 它指的是把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Aspect(切面) 它指的是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是个切面类。例如事务切面,它里面定义的方法就是和事务相关的,像开启事务、提交事务、回滚事务等,不会定义其他与事务无关的方法。

重点:

  • 连接点:方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点是一种候选点

  • 切入点:指定AOP思想想要影响的具体方法是哪些,描述那些需要增强的方法

  • Advice 增强:

    • 第一个层次:指的是横切逻辑
    • 第二个层次:方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫方位点,描述的是具体的特殊时机)
  • Aspect切面:

    • 切面概念是对上述概念的一个综合

    • Aspect切面 = 切入点 + 增强

      ​ = 切入点(锁定方法)+ 方位点(锁定方法中的特殊时机)+ 横切逻辑

      众多的概念,目的就是为了锁定要在哪个地方插入什么横切逻辑代码

Spring中AOP的代理选择

Spring 实现AOP思想使用的是动态代理技术

默认情况下,Spring会根据被代理对象是否实现接口来选择JDK还是CGLIB。当代理对象没有实现任何接口时,Spring会选择CGLIB。当代理对象实现了接口,Spring会选择JDK动态代理。不过我们也可以通过配置的方式,让Spring强制使用CGLIB

xml方式:

<aop:aspectj-autoproxy proxy-target-class="true"/>

注解方式:

@EnableAspectJAutoProxy(proxyTargetClass = true)

一般情况下,使用默认让spring自动选择即可。

Spring中AOP的配置方式

在Spring的AOP配置中,也和IoC配置一样,支持三种配置方式

  • 使用XMl配置
  • 使用XML+注解配置
  • 使用纯注解配置

Spring中AOP的实现

打印日志需求:把打印日志的代码逻辑织入service层中

XML模式

  • 引入AOP依赖

    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-aopartifactId>
        <version>5.2.5.RELEASEversion>
    dependency>
    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjweaverartifactId>
        <version>1.9.5version>
    dependency>
    
  • 切面类LogUtils:

    package com.test.utils;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    public class LogUtil {
    
        public void beforeLog() {
            System.out.println("前置通知...");
        }
    
        public void afterReturningLog(){
            System.out.println("正常执行通知...");
        }
    
        public void afterLog() {
            System.out.println("后置通知...");
        }
    
        public void afterThrowingLog() {
            System.out.println("抛出异常通知...");
        }
    
        public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕通知1...");
            // 获取参数
            Object[] args = pjp.getArgs();
    
            System.out.println("环绕通知2...");
    
            // 调用方法
            Object result = pjp.proceed(args);
    
            System.out.println("环绕通知3...");
            return result;
    
    
        }
    
    }
    
  • applicationContext.xml配置

    在xml配置文件约束加上aop的约束

    
    <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">
    
        ......
    
        
        <bean id="logUtil" class="com.test.utils.LogUtil">bean>
        
        <aop:config>
            
            <aop:aspect id="logAdvice" ref="logUtil">
                
                <aop:pointcut id="pt1" expression="execution(public void com.test.service.impl.TransferServiceImpl.transfer(java.lang.String, String, int))"/>
                
                <aop:before method="beforeLog" pointcut-ref="pt1"/>
                
                <aop:after-returning method="afterReturningLog" pointcut-ref="pt1"/>
                
                <aop:after-throwing method="afterThrowingLog" pointcut="execution(public void com.test.service.impl.TransferServiceImpl.transfer(java.lang.String, String, int))"/>
                
                <aop:after method="afterLog" pointcut-ref="pt1"/>
                
                <aop:around method="aroundLog" pointcut-ref="pt1"/>
            aop:aspect>
        aop:config>
    
        <aop:aspectj-autoproxy/>
    beans>
    

    执行输出:

    前置通知...
    环绕通知1...
    环绕通知2...
    业务核心代码
    环绕通知3...
    最终通知...
    正常执行通知...
    

    上述配置实现了对 TransferServiceImpl#transfer 方法进行增强。

    • 关于切入点表达式:

      • 概念及作用

        切入点表达式,也称之为AspectJ切入点表达式,指的是遵循特定语法结构的字符串,其作用是对于符合语法格式的连接点进行增强,它是AspectJ表达式的一部分。

      • 关于AspectJ

        AspectJ是一个机遇Java语言的AOP框架,Spring框架从2.0版本之后就集成了AspectJ框架中切入点表达式的部分,开始支持AspectJ的切入点表达式。

      • 表达式使用示例

        public void com.test.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String, int)
        访问修饰符 返回值 全限定方法名(参数类型列表)
        
        • 访问修饰符可以省略
        • 返回值可以用 * ,表示任意返回值 `*
        • 包名可以使用 . ,但是有几级包,必须写几个 void ...impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String, int)
        • 包名可以使用 .. 表示当前包及其子包 * ..TransferServiceImpl.transfer(java.lang.String, java.lang.String, int)
        • 参数:基本类型直接写类型名称,引用类型需要写全限定类名
        • * 可以表示参数列表,但至少要有一个参数;.. 可以表示任意参数个数
    • 五种通知类型:

      • before前置通知

        • 执行时机是在切入点方法(业务核心方法)执行之前执行
        • 前置通知可以获取切入点方法的参数,并对其进行增强
      • after-returning正常执行通知

        • 执行时机是在切入点方法(业务核心方法)正常执行后执行
      • after-throwing异常通知

        • 执行时机是在切入点方法(业务核心方法)产生异常后执行,如果没有异常不会执行
      • after最终通知

        • 执行时机是在切入点方法(业务核心方法)执行完成之后,返回之前执行,无论是否产生异常,都会执行。
      • around环绕通知

        环绕通知有别于以上四种通知类型。前面四种通知它们都是指定何时增强的通知类型;而环绕通知,它是Spring框架为我们提供一种可以通过编码的方式,控制增强代码何时执行的通知类型。

        它里面借助的ProceedingJoinPoint接口及其实现类,实现手动自定义除服切入点方法的调用。

        • Object result = pjp.proceed(args);
          

        需要调用该方法,原代码才会执行,并且需要返回返回值

XML+注解模式

  • 在xml文件中配置

    
    <aop:aspectj-autoproxy/>
    
  • 修改切面类LogUtils

    @Component
    @Aspect
    public class LogUtil {
    
        /**
         * 提供切入点表达式
         */
        @Pointcut("execution(public void com.test.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String, int))")
        public void pointcut(){}
    
        @Before("pointcut()")
        public void beforeLog() {
            System.out.println("前置通知...");
        }
    
        @AfterReturning("pointcut()")
        public void afterReturningLog(){
            System.out.println("正常执行通知...");
        }
    
        @AfterThrowing("pointcut()")
        public void afterThrowingLog() {
            System.out.println("抛出异常通知...");
        }
    
        @After("pointcut()")
        public void afterLog() {
            System.out.println("最终通知...");
        }
    
        @Around("pointcut()")
        public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕通知1...");
            // 获取参数
            Object[] args = pjp.getArgs();
    
            System.out.println("环绕通知2...");
    
            // 调用方法
            Object result = pjp.proceed(args);
    
            System.out.println("环绕通知3...");
            return result;
    
        }
    
    }
    

    首先要添加一个方法,使用**@Pointcut** 注解给后续的通知方法提供切入点表达式,然后只需把对应方法上添加对应的注解即可

纯注解模式

在上面xml+注解的基础上,只需用注解替换掉配置文件中的这行配置即可:


<aop:aspectj-autoproxy/>

只需在配置类用注解替换即可

@Configuration
@ComponentScan("com.test")
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class SpringConfiguration {
    
}

你可能感兴趣的:(#,SpringFramework,Spring)