likes
comments
collection
share

Spring AOP保姆级超详细解析(下)

作者站长头像
站长
· 阅读数 19

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

前言

上一篇文章我们详细介绍了使用动态代理的方式实现AOP,动态代理的方式对于不了解反射机制的小伙伴们可能比较难以理解,Spring对AOP进行了封装,可以使用面向对象的方式来实现AOP。我们这篇文章就来介绍下使用面向对象的方式来实现AOP。

面向对象实现AOP

实现原理

Spring框架中不需要创建InvocationHandler,只需要创建一个切面对象, 将所有的非业务代码在切面对象中完成即可,Spring 框架底层会自动根据切面类以及目标类生成一个代理对象。就不需要使用动态代理来实现AOP了。

代码实现

接我们上一篇文章的代码。

我们先定位切面的位置。

  • 首先,在方法执行之前,我们需要打印方法的参数。

  • 其次,方法返回返回值之前,打印计算结果。

  • 然后,方法执行完成后,输出日志执行完成。

  • 最后,如果方法有异常,则抛出异常。

定位完切面位置,我们需要创建一个类来统一管理非业务代码。

public class ConsoleLog {

}

根据我们所定位的切面的位置实现日志打印功能。

我们使用注解的方式来实现AOP

在方法执行之前,打印方法的参数

@Aspect
@Component
public class ConsoleLog{
    @Before("execution(public int com.impl.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        //获取方法参数
        String arr = Arrays.toString(joinPoint.getArgs());
        System.out.println(name+"方法的参数是"+arr);
    }
}

代码解析:

@Before表示在方法执行之前去切入。括号中参数表示需要切入类的方法。*是通配符,表示匹配该类中的所有方法。..同样也是通配符,表示匹配该方法的所有参数。

为了获取我们切入的方法的一些信息,我们需要在before方法中传入JoinPoint对象,JoinPoint用于连接我们的目标方法。

然后,我们通过joinPointgetSignature()方法的getName()获取到目标方法的方法名。 通过joinPointgetArgs()方法来获取目标方法的参数,从而实现在目标方法执行前打印出方法的参数。

前面我们说过,Spring框架会根据我们的切面类自动生成一个代理对象。但实际上,Spring并不是通过切面类去解析的,而是通过切面类的对象去解析的,所以我们要在ConsoleLog切面类前面加上@Component注解。这个注解的意思就相当于我们在spring.xml文件中配置了一个ConsoleLog切面类的bean,在加这个注解之前,我们需要在pom.xml文件中配置spring-context依赖。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.11.RELEASE</version>
</dependency>

但这样配置完,ConsoleLog还只是个普通对象,我们需要它成为切面对象,在前面的动态代理的方式中是通过实现InvocationHandler接口来成为切面对象的,我们这里直接加上@Aspect注解即可实现。

最后,我们来配置spring.xml文件,我们需要配置的是spring扫描这个类,然后添加注解。

<!--自动扫描包-->
<context:component-scan base-package="com"></context:component-scan>

<!--使Aspect注解生效,为目标类自动生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

base-package属性值为所需要扫面包的包名。

注意:我们添加<aop:aspectj-autoproxy></aop:aspectj-autoproxy>之前需要在spring.xml文件中引入aop命名空间。

xmlns:aop="http://www.springframework.org/schema/aop"
获取代理对象并调用
import com.Cal;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App2 {
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
        //获取代理对象
        Cal cal = (Cal) applicationContext.getBean("calImpl");
        cal.add(2,3);
    }
}

日志打印结果:

add方法的参数是[2, 3]

这样我们就完成了我们第一个定位的切面的位置。

方法执行完成后,输出日志执行完成

@After("execution(public int com.impl.CalImpl.*(..))")
public void after(JoinPoint joinPoint){
    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法已执行完成");
}

@After表示在方法执行之后去切入。括号中的参数与前面@Before相同。

日志打印结果:

add方法已执行完成

方法返回返回值之前,打印计算结果

@AfterReturning(value = "execution(public int com.impl.CalImpl.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法执行的结果是"+result);
}

@AfterReturning表示在方法返回返回值之前切入,value属性值为需要切入类的方法,returning属性值为需要获取的返回值,该属性值需要与下面方法参数中Object名相同。

日志打印结果:

add方法执行的结果是5

如果方法有异常,则抛出异常

@AfterThrowing(value = "execution(public int com.impl.CalImpl.*(..))",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception){
    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法抛出异常:"+exception);
}

@AfterThrowing表示在方法抛出异常时切入,value属性@AfterReturning相同,throwing属性值为所需要抛出的异常名,需要与下面方法中的Exception名对应。

这样我们的切面类就比较完整了,基本实现了我们所需要的日志打印功能。

总结

经过这些文章的学习,我们的Spring入门的基础教学都已经完成了,我们现在应该已经对Spring的IoC和AOP有所了解并已经掌握了其基本的使用,后续更进阶的教程我也会根据我自身的学习,写成文章继续更新在Spring专栏之中,更新不易,希望各位多多支持。最后感谢大家的阅读,喜欢对你有帮助的话记得加个关注不迷路哦,后续内容逐步更新,请耐心等待!!

转载自:https://juejin.cn/post/7151946571624480804
评论
请登录