如果要重用通用功能,可以采用继承或者组合,它们对业务类有侵入性。切面是一种更清晰简洁的方法,它完全实现开闭原则,业务类不知道切面类的存在,完全聚焦业务功能。
AOP
AOP(Aspect-Oriented Programming,面向切面编程)是Springboot 的核心概念。它是面向对象编程的一种延续。
它将公共模块(日志,事务,权限,监控)代码与业务模块代码分开开发,通过动态代理实现两者组合发挥功能。
术语
连接点:应用程序执行过程中能够插入切面的点。点可以是调用方法, 抛出异常时。
切点:通过正则表达式匹配的连接点的集合。在这些连接点均执行通知。
通知:定义切面要完成的工作内容以及工作时机。时机是相对于目标方法而言的。按照时机可以分为前置通知,后置通知,环绕通知,正常返回通知,异常返回通知。环绕通知等于前置通知+后置通知。
目标对象:被代理对象。
引入:不修改目标类,向目标类添加新方法或者属性。
切面:等于切点+通知。
织入:将切面应用到目标对象,创建新的代理对象。按照目标对象的生命周期可以分为:编译期织入,AspectJ就是这种。类加载期织入。运行期织入。
通过动态代理,生成代理对象。就是将通知应用到切点。
切点指示器
Spring 官网定义了支持的 AspectJ 切点指示器。其中 execution 执行匹配,其余指示器限制匹配参数,注解,类型,对象。
execution(* com.example.demo.User.getUser(..))
*
表示不限定返回类型。com.example.demo
是类名,User
是对象名,getUser
是方法名,(..)
表示不限定参数。
多个指示器可以用&&
,||
,!
组合使用。
public class LogAspect {
@Pointcut("execution(* com.example.demo.service.MySweviceImpl.printName(..))")
public void pointCut() {}
@Before("pointCut()")
public void before() {
System.out.println("before ...");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint jp) {
// 执行目标方法前的操作
jp.proceed(); // 执行目标方法
// 执行目标方法后的操作
}
}
execution 表示匹配正则表达式定义的方法。com.example.demo.service.MySweviceImpl.printName
是目标对象的全限定名称,可以是类,可以是方法。(..)
表示任意参数。*
表示任意返回对象。
常用的匹配方法还有:@annotation():匹配带有特定注解的连接点。更多切点匹配定义参考 springboot 教程。
@Before 定义前置通知。在执行目标连接点之前,先执行前置通知。
拦截方法参数
切面可以通过args
指示器获取方法参数并且传递到通知中。
引入:动态添加新方法
切面可以非侵入性地为代理对象添加新方法。
HearIntroducer
是切面。它通过 @DeclareParents 注解将 Hearable 接口引入到 User bean 中。
@AspectJ
public class HearIntroducer {
@DeclareParents(value = "com.example.demo.User+",
defaultImpl=DefaultHear.class)
public static Hearable hear;
}