什么是SpringAOP?
AOP(面向方面编程),AOP是对OOP(面向对象编程)的补充和完善。
OOP通过继承父类、实现接口的方式纵向编程,这种方式会使与业务无关的代码散布在各个类中,例如日志功能、权限认证。它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为"Aspect",即切面。
什么是切面?
所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为。那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。
AOP相关概念
- 连接点(Joinpoint)
连接点即需要增强的方法。Spring仅支持方法的连接点,仅能在方法调用前、方法调用后以及方法调用前后的这些程序执行点织入增强。
- 切点(Pointcut)
AOP通过切点定位连接点。通过数据库查询的概念来理解切点和连接点:连接点相当于数据库表中的记录,而切点相当于查询条件。连接点和切点不是一一对应的关系,一个切点可以匹配多个连接点。
- 增强(Advice)
增强是织入到目标类连接点上的一段程序代码。
- 目标对象(Target)
增强逻辑的织入目标类,如果没有AOP,目标业务类需要自己实现所有逻辑,在AOP的帮助下,目标类只实现那些非横切逻辑的程序逻辑,而其他代码则可以使用AOP动态织入到特定的连接点上。
- 引介(Introduction)
引介是一种特殊的增强,它为类添加一些属性和方法,这样即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态的为该业务类添加接口的实现逻辑,让这个业务类成为这个接口的实现类。
- 织入(Weaving)
织入是将增强添加到目标类具体连接点上的过程,AOP就像一台织布机,将目标类、增强或者引介编织到一起。
- 代理(Proxy)
一个类被AOP织入增强后,就产生出了一个结果类,它是融合了原类和增强逻辑的代理类。
- 切面(Aspect)
切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
实现原理
SpringAOP的实现是通过代理模式来实现。
什么是代理模式?
为某一个对象创建一个代理对象,我们不直接引用原本的对象,而是由创建的代理对象来控制对原对象的引用。
代理模式是常用的java设计模式,代理对象内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
按照代理的创建时期,代理类可以分为两种:
- 静态代理
由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
- 动态代理
在程序运行时,运用反射机制动态创建而成,无需手动编写代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。
AOP实现原理
Spring使用动态代理模式来实现,由两种方式:
- JDK动态代理
- CGLIB动态代理
JDK动态代理
- JDK动态代理是面向接口的,必须提供一个委托类和代理类都要实现的接口,只有接口中的方法才能够被代理。
- JDK动态代理的实现主要使用java.lang.reflect包里的Proxy类和InvocationHandler接口。
CGLIB动态代理
CGLIB采用非常底层的字节码技术,可以为一个类创建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势植入横切逻辑。其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然用CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖。
AOP代理总结
CGLIB所创建的动态代理对象的性能比JDK所创建的动态代理对象的性能高很多,但创建动态代理对象时比JDK创建动态代理对象要花费更长的时间。
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,实现接口就用CGLIB代理。
AOP注解的使用
execution表达式
通知
注解 | 通知 |
---|---|
@Before | 通知方法会在目标方法调用之前执行 |
@After | 通知方法会在目标方法返回或异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会将目标方法封装起来 |
定义注解
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationName {
String key() default "value";
}
定义切面
@Component
@Aspect
public class AspectName {
// 使用注解方式定义切入点
@Pointcut("@annotation(com.feng.AnnotationName)")
public void methodPointcut() {
}
// 使用execution表达式定义切入点
@Pointcut("execution(* com.sample.service.impl..*.*(..))")
public void point(){}
@Around("methodPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
}
}
注解失效
在同一个类中,方法a调用方法b,方法b加有注解,注解无效
public class A {
public void a() {
b();
}
public void b() {
}
}
// 生成的代理类
public class A$Proxy {
A serviceA = new A();
public void a() {
serviceA.a();
}
public void b() {
transaction start();
serviceA.b();
transaction end();
}
}
我们调用A这个Bean其实调用的是AProxy,对于a方法的调用其实是调用的A中的a方法,而A中的a调用的是A中的b方法,并没有调用AProxy中的b方法来实施事务。
解决办法
// 2.拿到代理类对象,再调用B。
((ClassName)AopContext.currentProxy()).B()