【Spring】面向切面编程 AOP

IoC 使软件组件松耦合。AOP 让你能够捕捉系统中经常使用的功能,把它转化成组件

AOP(Aspect Oriented Programming):面向切面编程(AOP是一种编程技术)

AOP 是对 OOP 的补充延伸

AOP 底层使用的就是动态代理来实现的

Spring 的 AOP 使用的动态代理是:JDK 动态代理 + CGLIB 动态代理技术。

Spring 在这两种动态代理中灵活切换,如果是代理接口,会默认使用 JDK 动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用 CGLIB。当然,你也可以强制通过一些配置让 Spring 只使用CGLIB

一、AOP 简介 

一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务

这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的

如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

  • 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处

  • 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务

使用AOP可以很轻松的解决以上问题

请看下图,可以帮助你快速理解AOP的思想:

用一句话总结AOP:

将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP

AOP的优点:

  • 第一:代码复用性增强

  • 第二:代码易维护

  • 第三:使开发者更关注业务逻辑

 

二、AOP 的七大术语 

public class UserService{
    public void do1(){
        System.out.println("do 1");
    }
    public void do2(){
        System.out.println("do 2");
    }
    public void do3(){
        System.out.println("do 3");
    }
    public void do4(){
        System.out.println("do 4");
    }
    public void do5(){
        System.out.println("do 5");
    }
    // 核心业务方法
    public void service(){
        do1();
        do2();
        do3();
        do5();
    }
}
  • 连接点 Joinpoint

    在程序的整个执行流程中,可以织入切面的位置(方法的执行前后,异常抛出之后等位置)

  • 切点 Pointcut

    在程序执行流程中,真正织入切面的方法(一个切点对应多个连接点)

  • 通知 Advice

    通知又叫增强,就是具体你要织入的代码

    • 通知包括:

      • 前置通知

      • 后置通知

      • 环绕通知

      • 异常通知

      • 最终通知

  • 切面 Aspect

    切点 + 通知就是切面

  • 织入 Weaving

    把通知应用到目标对象上的过程

  • 代理对象 Proxy

    一个目标对象被织入通知后产生的新对象

  • 目标对象 Target

    被织入通知的对象

 

通过下图,大家可以很好的理解AOP的相关术语:  

 

三、切点表达式 

切点表达式用来定义通知(Advice)往哪些方法上切入

切入点表达式语法格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

访问控制权限修饰符:

  • 可选项

  • 没写,就是4个权限都包括

  • 写public就表示只包括公开的方法

返回值类型:

  • 必填项

  • * 表示返回值类型任意

全限定类名:

  • 可选项

  • 两个点“..”代表当前包以及子包下的所有类

  • 省略时表示所有的类

方法名:

  • 必填项

  • *表示所有方法

  • set* 表示所有的 set 方法

形式参数列表:

  • 必填项

  • () 表示没有参数的方法

  • (..) 参数类型和个数随意的方法

  • (*) 只有一个参数的方法

  • (*, String) 第一个参数类型随意,第二个参数是String的

异常:

  • 可选项

  • 省略时表示任意异常类型

理解以下的切点表达式:

service 包下所有的类中以 delete 开始的所有方法

execution(public * org.qiu.mall.service.*.delete*(..))

mall包下所有的类的所有的方法 

execution(* org.qiu.mall..*(..))

所有类的所有方法  

execution(* *(..))

 

四、使用 Spring 的 AOP

Spring 对 AOP 的实现包括以下 3 种方式:

  • 第一种方式:Spring 框架结合 AspectJ 框架实现的 AOP,基于注解方式

  • 第二种方式:Spring 框架结合 AspectJ 框架实现的 AOP,基于 XML 方式

  • 第三种方式:Spring 框架自己实现的 AOP,基于XML配置方式

实际开发中,都是 Spring + AspectJ 来实现 AOP

什么是 AspectJ?

Eclipse 组织的一个支持AOP的框架。AspectJ 框架是独立于 Spring 框架之外的一个框架,Spring 框架用了AspectJ

AspectJ 项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由 Xerox 集团资助,Gregor Kiczales 领导,从1997年开始致力于 AspectJ 的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动 AspectJ 技术和社团的发展,PARC 在2003年3月正式将 AspectJ 项目移交给了 Eclipse 组织,因为 AspectJ 的发展和受关注程度大大超出了 PARC 的预期,他们已经无力继续维持它的发展。

1、准备工作

使用 Spring + AspectJ 的 AOP 需要引入的依赖如下:  

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!--spring aop依赖 : 这个可以不用,已经包含在 spring-context 中-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.2.13.RELEASE</version>
    </dependency>
    <!--spring aspects依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.13.RELEASE</version>
    </dependency>
</dependencies>

Spring 配置文件中添加 context 命名空间和 aop 命名空间  

<?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:context="http://www.springframework.org/schema/context"
       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/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

 

2、基于AspectJ的AOP注解式开发

第一步:定义目标类以及目标方法

package org.qiu.spring.service;

import org.springframework.stereotype.Service;

/**
 * 目标类
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-16:53
 * @since 1.0
 */
@Service
public class UserService {
    // 目标方法
    public void login(){
        System.out.println("系统正在进行身份认证......");
    }
}

第二步:定义切面类  

package org.qiu.spring.service;

import org.springframework.stereotype.Component;

/**
 * 切面 = 通知 + 切点
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-16:54
 * @since 1.0
 */
@Aspect
@Component
public class LogAspect {
}

第三步:目标类和切面类都纳入spring bean管理

在目标类OrderService上添加@Component注解。

在切面类MyAspect类上添加@Component注解。

第四步:在spring配置文件中添加组建扫描

<?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:context="http://www.springframework.org/schema/context"
       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/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 组件扫描 -->
    <context:component-scan base-package="org.qiu.spring.service"/>

</beans>

第五步:在切面类中添加通知  

package org.qiu.spring.service;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 切面 = 通知 + 切点
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-16:54
 * @since 1.0
 */
@Aspect
@Component
public class LogAspect {

    // 前置通知
    public void beforeAdvice(){
        System.out.println("我是增强的功能......");
    }
}

第六步:在通知上添加切点表达式  

package org.qiu.spring.service;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 切面 = 通知 + 切点
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-16:54
 * @since 1.0
 */
@Aspect
@Component
public class LogAspect {

    // 前置通知
    @Before("execution(* org.qiu.spring.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("我是增强的功能......");
    }
}

注解@Before表示前置通知。

第七步:在spring配置文件中启用自动代理

<?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:context="http://www.springframework.org/schema/context"
       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/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 组件扫描 -->
    <context:component-scan base-package="org.qiu.spring.service"/>
    <!--开启自动代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>

<aop:aspectj-autoproxy proxy-target-class="true"/> 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。

proxy-target-class="true" 表示采用cglib动态代理。

proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。

package org.qiu.spring.test;

import org.qiu.spring.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.test
 * @date 2022-11-20-17:05
 * @since 1.0
 */
public class Test {
    @org.junit.Test
    public void testAOP(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }
}

运行效果:  

 

通知类型

通知类型包括:

  • 前置通知:@Before 目标方法执行之前的通知

  • 后置通知:@AfterReturning 目标方法执行之后的通知

  • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。

  • 异常通知:@AfterThrowing 发生异常之后执行的通知

  • 最终通知:@After 放在finally语句块中的通知

编写程序来测试这几个通知的执行顺序:

package org.qiu.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 切面 = 通知 + 切点
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-16:54
 * @since 1.0
 */
@Aspect
@Component
public class LogAspect {

    // 前置通知
    @Before("execution(* org.qiu.spring.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("beforeAdvice");
    }

    // 后置通知
    @AfterReturning("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("afterReturningAdvice");
    }

    // 环绕通知(范围最大的通知)
    @Around("execution(* org.qiu.spring.service.UserService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("aroundAdvice -- 前置");
        joinPoint.proceed();    // 执行目标
        System.out.println("aroundAdvice -- 后置");
    }

    // 异常通知
    @AfterThrowing("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterThrowingAdvice(){
        System.out.println("afterThrowingAdvice");
    }

    // 最终通知
    @After("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterAdvice(){
        System.out.println("afterAdvice");
    }

}

运行结果:  

通过上面的执行结果就可以判断他们的执行顺序了

结果中没有异常通知,这是因为目标程序执行过程中没有发生异常。我们尝试让目标方法发生异常:

package org.qiu.spring.service;

import org.springframework.stereotype.Service;

/**
 * 目标类
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-16:53
 * @since 1.0
 */
@Service
public class UserService {
    // 目标方法
    public void login(){
        System.out.println("系统正在进行身份认证......");
        if (1 == 1) {
            throw new RuntimeException("模拟异常发生");
        }
    }
}

运行效果:  

通过测试得知,当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。

出现异常之后,后置通知环绕通知的结束部分不会执行。

切面的先后顺序

我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高

package org.qiu.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-19:15
 * @since 1.0
 */
@Aspect
@Component
@Order(1)
public class MyAspect {
    // 前置通知
    @Before("execution(* org.qiu.spring.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("MyAspect - beforeAdvice");
    }

    // 后置通知
    @AfterReturning("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("MyAspect - afterReturningAdvice");
    }

    // 环绕通知(范围最大的通知)
    @Around("execution(* org.qiu.spring.service.UserService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("MyAspect - aroundAdvice -- 前置");
        joinPoint.proceed();    // 执行目标
        System.out.println("MyAspect - aroundAdvice -- 后置");
    }

    // 异常通知
    @AfterThrowing("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterThrowingAdvice(){
        System.out.println("MyAspect - afterThrowingAdvice");
    }

    // 最终通知
    @After("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterAdvice(){
        System.out.println("MyAspect - afterAdvice");
    }
}
package org.qiu.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-19:16
 * @since 1.0
 */
@Aspect
@Component
@Order(2)
public class YourAspect {
    // 前置通知
    @Before("execution(* org.qiu.spring.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("YourAspect - beforeAdvice");
    }

    // 后置通知
    @AfterReturning("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("YourAspect - afterReturningAdvice");
    }

    // 环绕通知(范围最大的通知)
    @Around("execution(* org.qiu.spring.service.UserService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("YourAspect - aroundAdvice -- 前置");
        joinPoint.proceed();    // 执行目标
        System.out.println("YourAspect - aroundAdvice -- 后置");
    }

    // 异常通知
    @AfterThrowing("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterThrowingAdvice(){
        System.out.println("YourAspect - afterThrowingAdvice");
    }

    // 最终通知
    @After("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterAdvice(){
        System.out.println("YourAspect - afterAdvice");
    }
}

运行效果:  

通过修改@Order注解的整数值来切换顺序,执行测试程序:  

 

优化使用切点表达式

观看以下代码中的切点表达式:

package org.qiu.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 切面 = 通知 + 切点
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-16:54
 * @since 1.0
 */
@Aspect
@Component
public class LogAspect {

    // 前置通知
    @Before("execution(* org.qiu.spring.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("beforeAdvice");
    }

    // 后置通知
    @AfterReturning("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("afterReturningAdvice");
    }

    // 环绕通知(范围最大的通知)
    @Around("execution(* org.qiu.spring.service.UserService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("aroundAdvice -- 前置");
        joinPoint.proceed();    // 执行目标
        System.out.println("aroundAdvice -- 后置");
    }

    // 异常通知
    @AfterThrowing("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterThrowingAdvice(){
        System.out.println("afterThrowingAdvice");
    }

    // 最终通知
    @After("execution(* org.qiu.spring.service.UserService.*(..))")
    public void afterAdvice(){
        System.out.println("afterAdvice");
    }

}

缺点是:

  • 第一:切点表达式重复写了多次,没有得到复用。

  • 第二:如果要修改切点表达式,需要修改多处,难维护。

可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可

package org.qiu.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 切面 = 通知 + 切点
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-16:54
 * @since 1.0
 */
@Aspect
@Component
public class LogAspect {

    @Pointcut("execution(* org.qiu.spring.service.UserService.*(..))")
    public void pointcut(){}

    // 前置通知
    @Before("pointcut()")
    public void beforeAdvice(){
        System.out.println("beforeAdvice");
    }

    // 后置通知
    @AfterReturning("pointcut()")
    public void afterReturningAdvice(){
        System.out.println("afterReturningAdvice");
    }

    // 环绕通知(范围最大的通知)
    @Around("pointcut()")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("aroundAdvice -- 前置");
        joinPoint.proceed();    // 执行目标
        System.out.println("aroundAdvice -- 后置");
    }

    // 异常通知
    @AfterThrowing("pointcut()")
    public void afterThrowingAdvice(){
        System.out.println("afterThrowingAdvice");
    }

    // 最终通知
    @After("pointcut()")
    public void afterAdvice(){
        System.out.println("afterAdvice");
    }

}

使用@Pointcut注解来定义独立的切点表达式。

注意这个@Pointcut注解标注的方法随意,只是起到一个能够让@Pointcut注解编写的位置。

全注解式开发AOP

就是编写一个类,在这个类上面使用大量注解来代替spring的配置文件,spring配置文件消失了,如下:

package org.qiu.spring.service;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-20-19:26
 * @since 1.0
 */
@Configuration
@ComponentScan("org.qiu.spring.service")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Spring6Configuration {
}

测试程序:  

package org.qiu.spring.test;

import org.qiu.spring.service.SpringConfiguration;
import org.qiu.spring.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.test
 * @date 2022-11-20-17:05
 * @since 1.0
 */
public class Test {
    @org.junit.Test
    public void testAOP(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }
}

运行效果:  

 

3、基于 XML 配置方式的AOP(了解)

第一步:编写目标类(省略导包)

// 目标类
public class VipService {
    public void add(){
        System.out.println("保存vip信息。");
    }
}

第二步:编写切面类,并且编写通知  

// 负责计时的切面类
public class TimerAspect {
    
    public void time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        //执行目标
        proceedingJoinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }
}

第三步:编写spring配置文件  

<?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:context="http://www.springframework.org/schema/context"
       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/context 
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop 
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--纳入spring bean管理-->
    <bean id="vipService" class="org.qiu.spring.service.VipService"/>
    <bean id="timerAspect" class="org.qiu.spring.service.TimerAspect"/>

    <!--aop配置-->
    <aop:config>
        <!--切点表达式-->
        <aop:pointcut id="p" expression="execution(* org.qiu.spring.service.VipService.*(..))"/>
        <!--切面-->
        <aop:aspect ref="timerAspect">
            <!--切面=通知 + 切点-->
            <aop:around method="time" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
</beans>
public class AOPTest3 {

    @Test
    public void testAOPXml(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop-xml.xml");
        VipService vipService = applicationContext.getBean("vipService", VipService.class);
        vipService.add();
    }
}

 

五、AOP 的实际案例:事务管理 

项目中的事务控制是在所难免的。在一个业务流程当中,可能需要多条DML语句共同完成,为了保证数据的安全,这多条DML语句要么同时成功,要么同时失败。这就需要添加事务控制的代码。例如以下伪代码:  

class 业务类1{
    public void 业务方法1(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
    public void 业务方法2(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
    public void 业务方法3(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
}

class 业务类2{
    public void 业务方法1(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
    public void 业务方法2(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
    public void 业务方法3(){
        try{
            // 开启事务
            startTransaction();
            
            // 执行核心业务逻辑
            step1();
            step2();
            step3();
            ....
            
            // 提交事务
            commitTransaction();
        }catch(Exception e){
            // 回滚事务
            rollbackTransaction();
        }
    }
}
//......

可以看到,这些业务类中的每一个业务方法都是需要控制事务的,而控制事务的代码又是固定的格式,都是:

try{
    // 开启事务
    startTransaction();

    // 执行核心业务逻辑
    //......

    // 提交事务
    commitTransaction();
}catch(Exception e){
    // 回滚事务
    rollbackTransaction();
}

这个控制事务的代码就是和业务逻辑没有关系的“交叉业务”。以上伪代码当中可以看到这些交叉业务的代码没有得到复用,并且如果这些交叉业务代码需要修改,那必然需要修改多处,难维护,怎么解决?可以采用AOP思想解决。可以把以上控制事务的代码作为环绕通知,切入到目标类的方法当中。接下来我们做一下这件事,有两个业务类,如下:  

package org.qiu.spring.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-25-17:01
 * @since 1.0
 */
@Service
public class AccountService {
    public void transfer(){
        System.out.println("银行账户正在完成转账操作......");
    }

    public void withdraw(){
        System.out.println("银行账户正在取款......");
    }
}
package org.qiu.spring.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-25-17:01
 * @since 1.0
 */
@Service
public class OrderService {
    public void generate(){
        System.out.println("正在生成订单......");
    }

    public void cancel(){
        System.out.println("正在取消订单......");
    }
}

注意,以上两个业务类已经纳入spring bean的管理,因为都添加了@Component注解。

接下来我们给以上两个业务类的4个方法添加事务控制代码,使用AOP来完成:

package org.qiu.spring.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.service
 * @date 2022-11-25-17:10
 * @since 1.0
 */
@Aspect
@Component
public class TransactionAspect {

    @Around("execution(* org.qiu.spring.service..*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint){
        try {
            // 前环绕
            System.out.println("开启事务");
            // 执行目标
            joinPoint.proceed();
            // 后环绕
            System.out.println("提交事务");
        } catch (Throwable e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

}

这个事务控制代码是不是只需要写一次就行了,并且修改起来也没有成本。编写测试程序:  

package org.qiu.spring.test;

import org.junit.Test;
import org.qiu.spring.service.AccountService;
import org.qiu.spring.service.OrderService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.annotation.Order;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.spring.test
 * @date 2022-11-25-17:18
 * @since 1.0
 */
public class AopTest {
    @Test
    public void testTransaction(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        orderService.generate();
        orderService.cancel();
        accountService.transfer();
        accountService.withdraw();
    }
}

运行结果: 

通过测试可以看到,所有的业务方法都添加了事务控制的代码。  

 

六、AOP 的实际案例:安全日志 

需求是这样的:项目开发结束了,已经上线了。运行正常。客户提出了新的需求:凡是在系统中进行修改操作的,删除操作的,新增操作的,都要把这个人记录下来。因为这几个操作是属于危险行为。例如有业务类和业务方法:

package com.qiu.spring.biz;

import org.springframework.stereotype.Component;

@Component
//用户业务
public class UserService {
    public void getUser(){
        System.out.println("获取用户信息");
    }
    public void saveUser(){
        System.out.println("保存用户");
    }
    public void deleteUser(){
        System.out.println("删除用户");
    }
    public void modifyUser(){
        System.out.println("修改用户");
    }
}
package com.qiu.spring.biz;

import org.springframework.stereotype.Component;

// 商品业务类
@Component
public class ProductService {
    public void getProduct(){
        System.out.println("获取商品信息");
    }
    public void saveProduct(){
        System.out.println("保存商品");
    }
    public void deleteProduct(){
        System.out.println("删除商品");
    }
    public void modifyProduct(){
        System.out.println("修改商品");
    }
}

注意:已经添加了@Component注解。

接下来我们使用aop来解决上面的需求:编写一个负责安全的切面类

package com.qiu.spring.biz;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class SecurityAspect {

    @Pointcut("execution(* com.powernode.spring6.biz..save*(..))")
    public void savePointcut(){}

    @Pointcut("execution(* com.powernode.spring6.biz..delete*(..))")
    public void deletePointcut(){}

    @Pointcut("execution(* com.powernode.spring6.biz..modify*(..))")
    public void modifyPointcut(){}

    @Before("savePointcut() || deletePointcut() || modifyPointcut()")
    public void beforeAdivce(JoinPoint joinpoint){
        System.out.println("XXX操作员正在操作"+joinpoint.getSignature().getName()+"方法");
    }
}
@Test
public void testSecurity(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
    UserService userService = applicationContext.getBean("userService", UserService.class);
    ProductService productService = applicationContext.getBean("productService", ProductService.class);
    userService.getUser();
    userService.saveUser();
    userService.deleteUser();
    userService.modifyUser();
    productService.getProduct();
    productService.saveProduct();
    productService.deleteProduct();
    productService.modifyProduct();
}

运行效果:  

 

一  叶  知  秋,奥  妙  玄  心 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qx_java_1024

祝老板生意兴隆,财源广进!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值