Spring之AOP

文章目录

初步实现

先导入Spring和Junit4的依赖

java 复制代码
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

导入AOP的依赖

java 复制代码
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>

在Spring的配置文件中开启包扫描和AOP注解

xml 复制代码
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="com.iflytek"></context:component-scan>
    <!-- 开启基于注解的AOP功能 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

也可以通过配置类开启

java 复制代码
@Configuration
@ComponentScan(basePackages = "com.iflytek")
@EnableAspectJAutoProxy
public class MyConfig {
}

创建接口

java 复制代码
public interface Calculator {

    int add(int i, int j);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);

}

创建实现类实现这个接口:

java 复制代码
@Component
public class CalculatorPureImpl implements Calculator{
    @Override
    public int add(int i, int j) {

        int result = i + j;

        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int sub(int i, int j) {

        int result = i - j;

        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int mul(int i, int j) {

        int result = i * j;

        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int div(int i, int j) {

        int result = i / j;

        System.out.println("方法内部 result = " + result);

        return result;
    }
}

定义切面类

java 复制代码
/**
 * 切面类
 */
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {

    // @Before注解:声明当前方法是前置通知方法
    // value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
    @Before(value = "execution(public int com.iflytek.Calculator.add(int,int))")
    public void printLogBeforeCore() {
        System.out.println("[AOP前置通知] 方法开始了");
    }

    @AfterReturning(value = "execution(public int com.iflytek.Calculator.add(int,int))")
    public void printLogAfterSuccess() {
        System.out.println("[AOP返回通知] 方法成功返回了");
    }

    @AfterThrowing(value = "execution(public int com.iflytek.Calculator.add(int,int))")
    public void printLogAfterException() {
        System.out.println("[AOP异常通知] 方法抛异常了");
    }

    @After(value = "execution(public int com.iflytek.Calculator.add(int,int))")
    public void printLogFinallyEnd() {
        System.out.println("[AOP后置通知] 方法最终结束了");
    }

}

测试:

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:ApplicationContext.xml")
public class testClass {
    @Autowired
    private Calculator calculator;//这里一定要导入接口,而非实现类

    @Test
    public void test01(){
        calculator.add(1,2);
    }
}

运行结果:

[AOP前置通知] 方法开始了

方法内部 result = 3

[AOP返回通知] 方法成功返回了

[AOP后置通知] 方法最终结束了

通知执行顺序

  • Spring版本5.3.x以前:
    • 前置通知(@Before)
    • 目标操作
    • 后置通知(@After)
    • 返回通知或异常通知(@AfterReturing/@AfterThrowing)
  • Spring版本5.3.x以后:
    • 前置通知(@Before)
    • 目标操作
    • 返回通知或异常通知(@AfterReturing/@AfterThrowing)
    • 后置通知(@After)

各个通知获取细节信息

JoinPoint接口

org.aspectj.lang.JoinPoint

  • 要点1:JoinPoint接口通过getSignature()方法获取目标方法的签名
  • 要点2:通过目标方法签名对象获取方法名
  • 要点3:通过JoinPoint对象获取外界调用目标方法时传入的实参列表组成的数组

在切面类中:

java 复制代码
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {

    // @Before注解:声明当前方法是前置通知方法
    // value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
    @Before(value = "execution(public int com.iflytek.Calculator.add(int,int))")
    public void printLogBeforeCore(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        //方法名add
        System.out.println("methodName:"+signature.getName());

        //获取目标方法声明类型(public、private、protected)   1025
        System.out.println("modifiers:"+signature.getModifiers());

        // 获取目标方法所属类的类名  com.iflytek.Calculator
        System.out.println("declaringTypeName:"+signature.getDeclaringTypeName());

        Object[] args = joinPoint.getArgs();
        List<Object> list = Arrays.asList(args);
        System.out.println("参数:");//1,2
        list.forEach(item->{
            System.out.println(item);
        });

        System.out.println("[AOP前置通知] 方法开始了");
    }

    @AfterReturning(value = "execution(public int com.iflytek.Calculator.add(int,int))"
                    ,returning = "result")
                    //returning  获取方法返回值
    public void printLogAfterSuccess(Integer result) {
        System.out.println("[AOP返回通知] 方法成功返回了,返回值为"+result);
    }

    @AfterThrowing(value = "execution(public int com.iflytek.Calculator.add(int,int))",
                  throwing = "throwable")
                  //throwing 获取异常信息
    public void printLogAfterException(Throwable throwable) {
        System.out.println("[AOP异常通知] 方法抛异常了"+throwable.getClass().getName());
    }

    @After(value = "execution(public int com.iflytek.Calculator.add(int,int))")
    public void printLogFinallyEnd() {
        System.out.println("[AOP后置通知] 方法最终结束了");
    }

}

重用切点表达式

1、在切面类中声明

java 复制代码
    //定义切点
    @Pointcut(value = "execution(public int com.iflytek.Calculator.add(int,int))")
    public void pointCut(){
    }

2、同一个类内部引用

java 复制代码
    @Before(value = "pointCut()")
    public void printLogBeforeCoreOperation(JoinPoint joinPoint) {

3、在不同类中引用

该方法在LogAspect2类中

java 复制代码
    @Before(value = "com.iflytek.LogAspect.pointCut()")
    public Object roundAdvice(ProceedingJoinPoint joinPoint) {}

4、集中管理

而作为存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于统一管理:

java 复制代码
@Component
public class PointCuts {
    
    @Pointcut(value = "execution(public int *..Calculator.sub(int,int))")
    public void globalPointCut(){}
    
    @Pointcut(value = "execution(public int *..Calculator.add(int,int))")
    public void secondPointCut(){}
    
    @Pointcut(value = "execution(* *..*Service.*(..))")
    public void transactionPointCut(){}
}

切点表达式语法细节

  • 用开头的*号代替"权限修饰符"和"返回值"部分表示"权限修饰符"和"返回值"不限
  • 在包名的部分,一个"*"号只能代表包的层次结构中的一层,表示这一层是任意的。
    • 例如:*.Hello匹配com.Hello,不匹配com.iflytek.Hello
  • 在包名的部分,使用"*..."表示包名任意、包的层次深度任意
  • 在类名的部分,类名部分整体用*号代替,表示类名任意
  • 在类名的部分,可以使用*号代替类名的一部分
java 复制代码
*Service

上面例子表示匹配所有名称以Service结尾的类或接口

  • 在方法名部分,可以使用*号表示方法名任意
  • 在方法名部分,可以使用*号代替方法名的一部分
java 复制代码
*Operation

上面例子表示匹配所有方法名以Operation结尾的方法:

  • 在方法参数列表部分,使用(...)表示参数列表任意
  • 在方法参数列表部分,使用(int,...)表示参数列表以一个int类型的参数开头
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
    • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
java 复制代码
execution(public int *..*Service.*(.., int))

上面例子是对的,下面例子是错的:

java 复制代码
execution(* int *..*Service.*(.., int))

但是public *表示权限修饰符明确,返回值任意是可以的。

* void 就是错误的
  • 对于execution()表达式整体可以使用三个逻辑运算符号
    • execution() || execution()表示满足两个execution()中的任何一个即可
    • execution() && execution()表示两个execution()表达式必须都满足
    • !execution()表示不满足表达式的其他方法

环绕增强

环绕通知对应整个try...catch...finally结构,包括前面四种通知的所有功能。

java 复制代码
    //定义切点
    @Pointcut(value = "execution(public int com.iflytek.Calculator.add(int,int))")
    public void pointCut(){
    }



    @Around(value = "pointCut()")
    public void printAround(ProceedingJoinPoint joinPoint){



        try {
            //此处相当于前置增强
            System.out.println("before...");
            joinPoint.proceed(joinPoint.getArgs());
            System.out.println("afterReturning...");

        } catch (Throwable e) {
//            throw new RuntimeException(e);
            e.printStackTrace();
            System.out.println("afterThrowing");

        }
        finally {
            //此处相当于后置增强
            System.out.println("after...");
        }


    }

切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  • 优先级高的切面:外面
  • 优先级低的切面:里面

eg:如果 是@Before前置增强,则优先级高的先执行

如果是@After后置增强,则优先级低的先执行

使用@Order注解可以控制切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

没有接口的情况

在目标类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理

基于XML的AOP[了解]

将之前用到的AOP的注解都删除

使用xml配置

java 复制代码
    <bean id="aspect" class="com.iflytek.LogAspect"></bean>
    <bean id="calculatorPure" class="com.iflytek.CalculatorPureImpl"></bean>

    <aop:config>
<!-- 定义切入点和规则-->
        <aop:pointcut id="pc" expression="execution(public Integer com.iflytek.Calculator.add(int ,int))"/>
<!--切面-->
        <aop:aspect ref="aspect">
            <aop:before method="printLogBeforeCore" pointcut-ref="pc"></aop:before>
            <aop:after-returning method="printLogAfterSuccess" pointcut-ref="pc" returning="result"></aop:after-returning>
            <aop:after-throwing method="printLogAfterException" pointcut-ref="pc" throwing="throwable"></aop:after-throwing>
            <aop:after method="printLogFinallyEnd" pointcut-ref="pc"></aop:after>
        </aop:aspect>

    </aop:config>
相关推荐
WaaTong2 分钟前
《重学Java设计模式》之 单例模式
java·单例模式·设计模式
面试鸭4 分钟前
离谱!买个人信息买到网安公司头上???
java·开发语言·职场和发展
小白冲鸭34 分钟前
【报错解决】使用@SpringJunitConfig时报空指针异常
spring·java后端开发
沈询-阿里1 小时前
java-智能识别车牌号_基于spring ai和开源国产大模型_qwen vl
java·开发语言
AaVictory.1 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
LuckyLay1 小时前
Spring学习笔记_27——@EnableLoadTimeWeaving
java·spring boot·spring
Stringzhua1 小时前
【SpringCloud】Kafka消息中间件
spring·spring cloud·kafka
向阳12182 小时前
Dubbo负载均衡
java·运维·负载均衡·dubbo
Gu Gu Study2 小时前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言