Spring AOP保姆级超详细解析(下)
前言
上一篇文章我们详细介绍了使用动态代理的方式实现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
用于连接我们的目标方法。
然后,我们通过joinPoint
的getSignature()
方法的getName()
获取到目标方法的方法名。
通过joinPoint
的getArgs()
方法来获取目标方法的参数,从而实现在目标方法执行前打印出方法的参数。
前面我们说过,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