Spring 注解开发 2 IOC容器(AOP)

AOP

1、环境搭建

AOP是指在程序的运行期间动态地将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。
在讲解AOP原理之前,我们先来搭建一个模拟AOP的开发环境

1.1、导入AOP支持的依赖

  <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.12.RELEASE</version>
   </dependency>

在这里插入图片描述

1.2、创建一个业务逻辑类(目标类)

public class MathCalculator {
    public int div(int i, int j) {
        System.out.println("MathCalculator...div...正在进行除法运算....");
        return i / j;
    }
}

现在,我们希望在以上这个业务逻辑类中的除法运算之前,记录一下日志,例如记录一下哪个方法运行了,用的参数是什么,运行结束之后它的返回值又是什么,顺便可以将其打印出来,还有如果运行出异常了,那么就捕获一下异常信息。

或者,你会有这样一个需求,即希望在业务逻辑运行的时候将日志进行打印,而且是在方法运行之前、方法运行结束、方法出现异常等等位置,都希望会有日志打印出来。

1.3、创建一个业务切面类

AOP中的通知方法及其对应的注解与含义如下:

前置通知(对应的注解是@Before):在目标方法运行之前运行
后置通知(对应的注解是@After):在目标方法运行结束之后运行,无论目标方法是正常结束还是异常结束都会执行
返回通知(对应的注解是@AfterReturning):在目标方法正常返回之后运行
异常通知(对应的注解是@AfterThrowing):在目标方法运行出现异常之后运行
环绕通知(对应的注解是@Around):动态代理,我们可以直接手动推进目标方法运行(joinPoint.procced())

切面类代码

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Before;
/**
 * 切面类
 *
 */
public class LogAspects {
    // @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
    @Before("public int lsp.mdy.aop.MathCalculator.*(..)")
    public void logStart() {
        System.out.println("除法运行之前......@Before,参数列表是:{}");
    }
    // 在目标方法(即div方法)结束时被调用
    @After("public int lsp.mdy.aop.MathCalculator.*(..)")
    public void logEnd() {
        System.out.println("除法结束......@After");
    }
    // 在目标方法(即div方法)正常返回了,有返回值,被调用
    @AfterReturning("public int lsp.mdy.aop.MathCalculator.*(..)")
    public void logReturn() {
        System.out.println("除法正常返回......@AfterReturning,运行结果是:{}");
    }
    // 在目标方法(即div方法)出现异常,被调用
    @AfterThrowing("public int lsp.mdy.aop.MathCalculator.*(..)")
    public void logException() {
        System.out.println("除法出现异常......异常信息:{}");
    }
}

在该切面类中定义几个打印日志的方法,以这些方法来动态地感知MathCalculator类中的div()方法的运行情况。如果需要切面类来动态地感知目标类方法的运行情况,那么就需要使用Spring AOP中的一系列通知方法了。

​ 可以看到每一个通知方法上都写上(“public int mdy.lsp.aop.MathCalculator.*(…)”) ,太麻烦了。 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式,就像下面这样。

// 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式
	@Pointcut("execution(public int lsp.mdy.aop.MathCalculator.*(..))")
	public void pointCut() {} 

若果是在本切面类引用该公共切入点表达式的话,可以用如下形式

 @Before("pointCut()")

如果是外部类(即其他的切面类)引用,那么就得在通知注解中写方法的全名了

@After("lsp.mdy.aop.LogAspects.pointCut()")

由于我们只是在本切面类中引用该切入点表达式,所以最后我们的切面类代码如下


import org.aspectj.lang.annotation.*;

/**
 * 切面类
 * @Aspect:告诉Spring当前类是一个切面类,而不是一些其他普通的类
 */
@Aspect
public class LogAspects {

    // 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式
    @Pointcut("execution(public int lsp.mdy.aop.MathCalculator.*(..))")
    public void pointCut() {}

    // @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
    // @Before("public int com.meimeixia.aop.MathCalculator.*(..)")
    @Before("pointCut()")
    public void logStart() {
        System.out.println("除法运行......@Before,参数列表是:{}");
    }

    // 在目标方法(即div方法)结束时被调用
    // @After("pointCut()")
    @After("lsp.mdy.aop.LogAspects.pointCut()")
    public void logEnd() {
        System.out.println("除法结束......@After");
    }

    // 在目标方法(即div方法)正常返回了,有返回值,被调用
    @AfterReturning("pointCut()")
    public void logReturn() {
        System.out.println("除法正常返回......@AfterReturning,运行结果是:{}");
    }

    // 在目标方法(即div方法)出现异常,被调用
    @AfterThrowing("pointCut()")
    public void logException() {
        System.out.println("除法出现异常......异常信息:{}");
    }

}

1.4、将目标类与切面类加入ioc容器

写一个配置类来将这个目标类与切面类加入ioc容器

@Configuration
public class MathCalculatorConfig {
    @Bean
    public MathCalculator mathCalculator(){
        return new MathCalculator();
    }
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

1.5、使用 @Aspect注解

告诉Spring当前类是一个切面类,而不是一些其他普通的类。也就是在切面类LogAspects上加一个@Aspect注解。在这里插入图片描述

1.6、使用 EnableAspectJAutoProxy

在配置类中使用 EnableAspectJAutoProxy 注解开启AOP切面自动代理功能
在这里插入图片描述

1.7、测试

注意,在进行测试的时候不要自己去new一个MathCalculator对象去进行测试,而是从容器中去获取我们放进去的MathCalculator对象,要不然不在容器中不能使用spring提供的功能。

   @Test
    public void test01(){
        AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MathCalculatorConfig.class);
        MathCalculator mathCalculator = (MathCalculator) ioc.getBean("mathCalculator");
        int div = mathCalculator.div(5, 5);
        System.out.println("结果是: "+div);
    }

在这里插入图片描述
基本的环境搭建已经完成了,而如果我们想要获取运行方法的某些参数,比如方法名,方法参数等,我们可以在通知方法的参数列表加上一个 JoinPoint 类型的形参。 这里,需要注意的是,JoinPoint参数一定要放在参数列表的第一位,否则Spring是无法识别的,那自然就会报错了。

切面类改进如下


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
/**
 * 切面类
 * @Aspect:告诉Spring当前类是一个切面类,而不是一些其他普通的类
 * @author liayun
 */
@Aspect
public class LogAspects {
    // 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式
    // 1. 本类引用
    // 2. 如果是外部类,即其他的切面类引用,那就在这@After("...")写的是方法的全名,而我们就要把切入点写在这儿@Pointcut("...")
    @Pointcut("execution(public int lsp.mdy.aop.MathCalculator.*(..))")
    public void pointCut() {}
    // @Before:在目标方法(即div方法)运行之前切入,public int mdy.lsp.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
    // @Before("public int mdy.lsp.aop.MathCalculator.*(..)")
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
        // System.out.println("除法运行......@Before,参数列表是:{}");
        Object[] args = joinPoint.getArgs(); // 拿到参数列表,即目标方法运行需要的参数列表
        System.out.println(joinPoint.getSignature().getName() + "运行......@Before,参数列表是:{" + Arrays.asList(args) + "}");
    }
    // 在目标方法(即div方法)结束时被调用
    // @After("pointCut()")
    @After("lsp.mdy.aop.LogAspects.pointCut()")
    public void logEnd(JoinPoint joinPoint) {
        // System.out.println("除法结束......@After");
        System.out.println(joinPoint.getSignature().getName() + "结束......@After");
    }
    // 在目标方法(即div方法)正常返回了,有返回值,被调用
    // @AfterReturning("pointCut()")
    @AfterReturning(value="pointCut()", returning="result") // returning来指定我们这个方法的参数谁来封装返回值
    /*
     * 如果方法正常返回,我们还想拿返回值,那么返回值又应该怎么拿呢?
     */
    public void logReturn(JoinPoint joinPoint, Object result) { // 一定要注意:JoinPoint这个参数要写,一定不能写到后面,它必须出现在参数列表的第一位,否则Spring也是无法识别的,就会报错
        // System.out.println("除法正常返回......@AfterReturning,运行结果是:{}");
        System.out.println(joinPoint.getSignature().getName() + "正常返回......@AfterReturning,运行结果是:{" + result + "}");
    }
    // 在目标方法(即div方法)出现异常,被调用
    @AfterThrowing("pointCut()")
    public void logException() {
        System.out.println("除法出现异常......异常信息:{}");
    }
}

测试
在这里插入图片描述
如果目标方法运行时出现了异常,而我们又想拿到这个异常信息,那么该怎么办呢?只须对LogAspects切面类中的logException()方法进行优化即可,优化后的结果如下所示。

// 在目标方法(即div方法)出现异常,被调用
// @AfterThrowing("pointCut()")
@AfterThrowing(value="pointCut()", throwing="exception")
public void logException(JoinPoint joinPoint, Exception exception) {
    // System.out.println("除法出现异常......异常信息:{}");
    
    System.out.println(joinPoint.getSignature().getName() + "出现异常......异常信息:{" + exception + "}");
} 

接下来我们来模拟一个除0异常:

   @Test
    public void test01(){
        AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MathCalculatorConfig.class);
        MathCalculator mathCalculator = (MathCalculator) ioc.getBean("mathCalculator");
        int div = mathCalculator.div(5, 0);
        System.out.println("结果是: "+div);
    }

在这里插入图片描述
小结:

搭建AOP测试环境时 ,有三步是最关键的:
1. 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)。
2. 在切面类上的每个通知方法上标注通知注解,告诉Spring何时何地运行,当然最主要的是要写好切入点表达式,这个切入点表达式可以参照官方文档来写。
3. 开启基于注解的AOP模式,即加上@EnableAspectJAutoProxy注解,这是最关键的一点。 

2、AOP原理

2.1、EnableAspectJAutoProxy注解

2.2、AnnotationAwareAspectJAutoProxyCreator

2.3、registerBeanPostProcessors方法注册后置处理器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值