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>
相关推荐
小筱在线8 分钟前
SpringCloud微服务实现服务熔断的实践指南
java·spring cloud·微服务
luoluoal13 分钟前
java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)
java·vue.js·spring boot
ChinaRainbowSea18 分钟前
十三,Spring Boot 中注入 Servlet,Filter,Listener
java·spring boot·spring·servlet·web
小游鱼KF22 分钟前
Spring学习前置知识
java·学习·spring
扎克begod25 分钟前
JAVA并发编程系列(9)CyclicBarrier循环屏障原理分析
java·开发语言·python
青灯文案127 分钟前
SpringBoot 项目统一 API 响应结果封装示例
java·spring boot·后端
我就是程序猿37 分钟前
tomcat的配置
java·tomcat
阳光阿盖尔43 分钟前
EasyExcel的基本使用——Java导入Excel数据
java·开发语言·excel
二十雨辰44 分钟前
[苍穹外卖]-12Apache POI入门与实战
java·spring boot·mybatis
程序员皮皮林44 分钟前
开源PDF工具 Apache PDFBox 认识及使用(知识点+案例)
java·pdf·开源·apache