SpringAOP——概念与简单实现
参考官网5-6+百度翻译...
一、概念
1、OOP与AOP
面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。
OOP:面向对象编程,关键单元是对象,面向的是一个个对象。
AOP:面向切面编程,关键单元是切面,面向的是一个个切面。切面关注点可能是多个不同类型的对象的相同点,也就是说切面可以跨域多个类型和对象。
OOP与AOP两种不同的编程思想,为什么说AOP是OOP的补充呢?将切面视为一个对象,面向切面也就是面向对象。(面向对象就是吊,跟马哲似的。)
2、SpringIOC与SpringAOP
Spring两大核心组件:SpringIOC与SpringAOP,SpringIOC是OOP思想实现的框架组件,SpringAOP则是AOP思想实现框架组件。
经过上面理解先猜想一下:
① SpringAOP的一个切面的关注点可以横跨SpringIOC容器里多个对象。
② SpringAOP的切面本身也是一个对象,会放入到SpringIOC容器中。
3、AspectJ与SpringAOP
AspectJ与SpringAOP都是AOP思想实现的框架。区别在于:
AspectJ:编译期通过字节码技术修改class文件实现切面,运行期内存中仅有一个对象。
SpringAOP:编译器不修改class文件,运行期运用动态代理生成代理类完成切面。内存中会有两个对象(target object & proxy object)
AspectJ的一些切入点、表达式解析简单,SpringAOP吸取了这一优点,使用AspectJ的库来解释与AspectJ 5相同的注释和表达式(例如pointcut的匹配表达式)。因此项目中使用SpringAOP时,会依赖AspectJ框架的jar包——aspectJ.jar,但是这个jar包仅仅是解析匹配作用,AOP运行时仍然是纯Spring AOP(代理实现),并且不依赖于AspectJ编译器或编织器,总结:SpringAOP=AspectJ的注解表达式解析+SpringAOP动态代理实现;SpringAOP与AspectJ框架语法大致相同。
3、术语
AOP术语
AspectJ:切面
Join point:程序执行过程中的一个点,切面关注点包含的一个对象。
Advice:切面在关注点处采取的操作
pointcut:比Join point大的概念,切面关注点包含的所有对象。advice与pointcut表达式关联,并在与该切入点匹配的任何Join point处运行(例如,执行具有特定名称的方法)。
pointcut表达式匹配的join point的概念是AOP的核心,Spring默认使用AspectJ切入点表达语言
introduction:给类型声明其他方法或字段。Spring AOP中允许向任何切入关键点有关的对象引入新的接口(和相应的实现)。例如,您可以使用introduction 使Bean实现 IsModified
接口,以简化缓存。
Target object:一个或多个切面Advice的对象。也称为“advised object”。由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。
AOP proxy:由AOP框架创建的一个代理,用于执行切面的协定(advice方法执行等)。在Spring Framework中,AOP代理是JDK动态代理或CGLIB代
Weaving:将切面与其他类型或对象链接以创建代理对象。这可以在编译时(例如,使用AspectJ编译器),加载时或在运行时完成。Spring AOP在运行时执行编织
SpringAOP拓展术语
before advice:在连接点之前运行的advice,但是它不能阻止执行流程继续进行到join point(除非它引发异常)。
after returning advice:在join point正常完成之后要运行的advice(例如,如果方法返回而没有引发异常)
after throwing advice:如果方法因抛出异常而退出,则执行advice
after (finally) advice:无论join point退出的方式如何(正常或特殊返回),均应执行advice
around advice:围绕jion point的advice,例如方法调用。这是最有力的advice。around advice可以在方法调用之前和之后执行自定义行为。它还负责选择是返回join point还是通过返回其自身的返回值或引发异常来捷径建议的方法执行
生涩难懂的概念:理解起来真是麻烦,自己建模成一个数学几何问题理解一下。方法调用是纵向的,将线程执行视为一条垂线。
4、springAOP实现
Spring AOP默认将标准JDK动态代理用于AOP代理,也可以使用CGLIB代理。
如果要代理的目标对象实现至少一个接口,则使用JDK动态代理。代理了由目标类型实现的所有接口。如果目标对象未实现任何接口,则将创建CGLIB代理(CGLIB继承实现代理所以方法不能被final修饰)
public interface Xxable { void xx(); } @Component public class A implements Xxable{ @Override public void xx() { System.out.println("I am A xx"); } } @Component public class B{ public void xx() { System.out.println("I am b xx"); } } @Component//切面必须放到IOC容器中 @org.aspectj.lang.annotation.Aspect//aspecjweaver.jar SpringAOP的注解是AspectJ解析的 public class Aspect { @Pointcut("execution(* xx())") public void pointCut(){ } @Before("pointCut()")//aspecjweaver.jar SpringAOP的表达式也是AspectJ解析的 public void beforeAdvice(){ System.out.println("before advice"); } @After("pointCut()") public void afterAdvice(){ System.out.println("after advice"); } } @Configuration @EnableAutoConfiguration @EnableAspectJAutoProxy//启用SpringAOP public class Main { public static void main(String[] args){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.scan("com.app.aop"); context.refresh(); A a = (A)context.getBean("a"); B b = (B)context.getBean("b"); a.xx(); b.xx(); System.out.println(a.getClass().getName());//本来只是想随便打印一下具体类型 System.out.println(b.getClass().getName());//结果发现spring5.x默认jdk动态代理,但是springboot2.x默认cglib代理 } }
简单步骤:
① 需要启用SpringAOP支持,@EnableAspectJAutoProxy
② 声明一个aspect切面,必须且切面也是一个对象,需要注入到容器中。@Aspect & @Component
③ 声明关注点@pointcut
④ 声明advice
5、用上面例子验证Advice执行
//无exception around before advice before advice I am A xx around after advice after advice AfterReturning advice //有exception 不会执行aroud after around before advice before advice I am A xx after advice AfterThrowing advice exception
6、pointcut表达式解析
-
execution
:模糊匹配。用于匹配方法执行的join points。这是使用Spring AOP时要使用的主要切入点指示符。 -
within
:包或类型匹配。将匹配限制为某些类型内的join points(使用Spring AOP时,在匹配类型内声明的方法的执行)。 -
this
:指定代理对象(proxy object)。限制匹配到join points(使用Spring AOP时方法的执行)的匹配,其中bean引用(Spring AOP代理)是给定类型的实例。 -
target
:指定目标对象(target object)。在目标对象(代理的应用程序对象)是给定类型的实例的情况下,将匹配限制为join points(使用Spring AOP时方法的执行)。 -
args
:指定方法参数。在参数为给定类型的实例的情况下,将匹配限制为join points(使用Spring AOP时方法的执行)。 -
@target
:在执行对象的类具有给定类型的注释的情况下,将匹配限制为join points(使用Spring AOP时方法的执行)。 -
@args
:限制匹配的join points(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。 -
@within
:将匹配限制为具有给定注释的类型内的join points(使用Spring AOP时,使用给定注释的类型中声明的方法的执行)。 -
@annotation
:匹配注解(自定义注解的良好使用)。将匹配限制在join points的主题(Spring AOP中正在执行的方法)具有给定注释的连接点上。
官方给出的表达式格式及列举的常用pointcut表达式:
//modifiers-pattern? 方法访问修饰符public private等匹配 //ret-type-pattern 方法返回类型匹配 //declaring-type-pattern 方法声明类型匹配 //name-pattern(param-pattern) 方法名称匹配 // thows-pattern 方法声明异常throws匹配 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) //常用表达式 //所有public 方法 execution(public * *(..)) //所有set开头的方法 execution(* set*(..)) //AccountService接口定义的所有方法 execution(* com.xyz.service.AccountService.*(..)) //service包下的所有方法 execution(* com.xyz.service.*.*(..)) //service包或其子包的所有方法 execution(* com.xyz.service..*.*(..)) //service包下的所有方法 within(com.xyz.service.*) //service包或其子包的所有方法 within(com.xyz.service..*) //代理对象实现AccountService接口的所有方法 this(com.xyz.service.AccountService) //目标对象实现AccountSerivce接口的所有方法 target(com.xyz.service.AccountService) //采用单个参数的所有方法 args(java.io.Serializable) //目标对象的声明类型带有@Transactional注解的所有方法 @target(org.springframework.transaction.annotation.Transactional) //目标对象的声明类型带有@Transactional注解的所有方法 @within(org.springframework.transaction.annotation.Transactional) //带有@Transactional注解的所有方法 @annotation(org.springframework.transaction.annotation.Transactional) //任何采用单个参数的方法,且参数类型被@Classified注解 @args(com.xyz.security.Classified) //beanName=tradeService的bean的所有方法 bean(tradeService) //beanName=*Service的bean的所有方法 bean(*Service)