Spring--AOP使用

2022-05-20 09:08:34 浏览数 (1)

Spring的AOP也是基于AspectJ,和安卓中(之前文章:Android--AOP架构设计之使用AspectJ监测方法耗时)使用相比,Spring集成了它,所以配置起来方便很多

一、概念
  • 重新整理下AOP的概念:

术语

描述

连接点 Joint point

增强的方法,也就是我们想要处理的方法。包括:方法,成员变量访问,异常处理程序执行等

切入点 Pointcut

表示一组连接点,连接点使用表达式集中起来,切入点也是我们AOP编程中需要着重处理的地方

通知 Advice

用于区别对切入点调用之前,之后,还是返回值,异常处理等的逻辑处理。通知也是AOP编程中需要着重处理的地方

目标对象 Target

被代理的对象

切面 Aspect

表示AOP编程的入口

织入 Weaving

创建代理对象并实现功能增强的声明并运行过程

  • 表达式: 我们需要通过表达式来指定连接点,规则如下
代码语言:javascript复制
execution([注解(可省略)] [权限修饰符(可省略)] [返回值类型] [类的全路径名].[方法名](参数 列表) )

如:

代码语言:javascript复制
execution(* com.aruba.mapper.*.add(..))

表示com.aruba.mapper包下,所有类的add方法

代码语言:javascript复制
execution(* com.aruba.mapper.*.add*(..))

表示com.aruba.mapper包下,所有类的,方法名为add前缀的方法

二、项目准备
1. 导入依赖:
代码语言:javascript复制
        <!--spring切面包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.5</version>
        </dependency>
        <!--aop联盟包-->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <!--Apache Commons日志包-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
2. Spring配置文件:

开启包扫描和aop生成代理对象

代码语言:javascript复制
<?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:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--包扫描-->
    <context:component-scan base-package="com.aruba"></context:component-scan>
    <!--aop自动生成代理对象-->
    <aop:aspectj-autoproxy/>

</beans>
3. 类和接口定义:
代码语言:javascript复制
public interface DeptMapper {
    int add();
}
代码语言:javascript复制
@Repository
public class DeptMapperImpl implements DeptMapper {
    @Override
    public int add() {
        System.out.println("DeptMapperImpl add ..");
        return 0;
    }
}

加上@Repository注解,让Spring容器自动实例化

三、AOP编程
1. 准备切面
代码语言:javascript复制
@Aspect
@Component
public class MyAspect {
}

使用@Aspect注解表示处理入口,@Component注解让Spring容器自动实例化

2. 定义切入点

在切面MyAspect中定义切入点,使用Pointcut注解配合连接点表达式注解方法,方法名随意:

代码语言:javascript复制
    @Pointcut("execution(* com.aruba.mapper.*.add*(..))")
    public void addPointcut(){}

注解后,com.aruba.mapper包下的所有类的add前缀的方法都成为了一个切入点,切入点名称为方法名addPointcut()

3. 通过通知注解实现方法增强

先使用@Before注解配合切入点名称:

代码语言:javascript复制
    @Before("addPointcut()")
    public void methodBefore() {
        System.out.println("MyAspect methodBefore..");
    }
4. 测试方法
代码语言:javascript复制
    @Test
    public void test1() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        DeptMapper deptMapper = applicationContext.getBean(DeptMapper.class);
        deptMapper.add();
    }

结果:

四、通知注解
1. 前置通知

上面我们使用了@Before注解,表示在目标方法调用之前,进行增强,称为前置通知,我们把被通知注解的方法称为增强方法

前置通知中还能获取目标方法的传参

接口中新增一个带参数的方法并实现:

代码语言:javascript复制
public interface DeptMapper {
    int add();
    int add(int id,String name);
}
代码语言:javascript复制
@Repository
public class DeptMapperImpl implements DeptMapper {
    @Override
    public int add() {
        System.out.println("DeptMapperImpl add ..");
        return 0;
    }

    @Override
    public int add(int id, String name) {
        System.out.println("DeptMapperImpl add ..");
        return 0;
    }
}

在增强方法中,新增一个JoinPoint入参:

代码语言:javascript复制
    @Before("addPointcut()")
    public void methodBefore(JoinPoint joinPoint) {
        System.out.println("MyAspect methodBefore..");
        System.out.println(Arrays.toString(joinPoint.getArgs()));
    }

测试方法:

代码语言:javascript复制
    @Test
    public void test2() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        DeptMapper deptMapper = applicationContext.getBean(DeptMapper.class);
        deptMapper.add(1,"zhangsan");
    }

结果:

2. 后置通知

在目标方法调用后执行增强方法,使用@After注解

在切面中定义增强方法:

代码语言:javascript复制
    @After("addPointcut()")
    public void methodAfter(JoinPoint joinPoint) {
        System.out.println("MyAspect methodAfter..");
    }

重新运行测试方法的结果:

3. 返回通知

在目标方法正常运行后执行增强方法,和后置不同的是,出现异常,则不会调用增强方法,并且可以获取目标方法返回值,使用AfterReturning注解

在切面中定义增强方法:

代码语言:javascript复制
    //returning 指定返回值对应增强方法的参数名
    @AfterReturning(pointcut = "addPointcut()", returning = "ret")
    public void methodAfterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("MyAspect methodAfterReturning..  :"   ret);
    }

结果:

4. 异常通知

在目标方法执行异常时调用增强方法,使用@AfterThrowing注解

在切面中定义增强方法:

代码语言:javascript复制
    @AfterThrowing(pointcut = "addPointcut()", throwing = "ex")
    public void methodAfterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println("MyAspect methodAfterThrowing..  ex:"   ex);
    }

实现类写个异常:

代码语言:javascript复制
    @Override
    public int add(int id, String name) {
        System.out.println("DeptMapperImpl add ..");
        int a = 1 / 0;
        return 0;
    }

结果:

5. 环绕通知

在目标方法执行前和执行后,实现功能增强,需要手动调用目标方法执行,并将返回值返回,风格为责任链模式,使用@Around注解

在切面中定义增强方法:

代码语言:javascript复制
    @Around("addPointcut()")
    public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("MyAspect methodAround..  start");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("MyAspect methodAround..  end");

        return proceed;
    }

ProceedingJoinPoint为连接点,通过proceed方法执行目标方法,也可以传入新的参数

结果:

五、其他配置

除了xml中定义外,还可以在切面上使用注解表示包扫描和开启生成代理:

代码语言:javascript复制
@Aspect
@Component
@ComponentScan("com.aruba")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class MyAspect {

当有多个切面时,可以使用Order注解指定执行顺序,数字越小,优先级越高,数字越小,其代理位置越靠近注入位置:

代码语言:javascript复制
@Aspect
@Component
@ComponentScan("com.aruba")
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Order(1)
public class MyAspect {
项目地址:

https://gitee.com/aruba/spring-study.git

0 人点赞