对 AOP 的理解
AOP 即为面向切面编程,它将与业务模块无关却又为业务模块所共用的逻辑封装起来,形成一个切面,使原来的业务功能更加强大(增强),减少重复代码,降低模块间的耦合度,有利于未来的可拓展性和可维护性
目标:通过允许横切关注点的分离,提高模块化,通过在代码前后的一些增强处理,实现对业务逻辑的隔离
Spring AOP 是怎么实现的
Spring AOP 是通过 动态代理 实现的,有两种方式:JDK 动态代理、CGLIB 动态代理,除了这两种方式,也可以采用 AspectJ 来实现,因为 Spring 已经集成了 AspectJ 框架
JDK 动态代理和 CGLIB 动态代理的使用场景
- 如果要代理的类实现了接口,默认采用 JDK 动态代理,但也可以强制使用 CGLIB 动态代理(在 Spring 配置中加入
<aop:aspectj-autoproxy proxyt-target-class="true"/>) - 如果没有实现接口,那么就必须采用 CGLIB 动态代理
JDK 动态代理
1. 实现原理
JDK 动态代理是基于 反射 实现的。JDK 通过反射机制生成一个代理类,这个代理类实现了类的全部接口,并且对接口中定义的方法进行了代理。当通过代理对象调用方法时,代理类底层会通过反射机制回调InvocationHandler接口的invoke方法
JDK 动态代理中最重要的两个组件,一个是InvocationHandler接口,一个是Proxy类。通过编写一个类实现InvocationHandler接口,然后重写invoke方法,而这个方法就是提供的代理方法;然后通过Proxy的newProxyInstance方法返回一个代理对象2. 优点
- 不需要安装依赖,是 JDK 原生的
-
3. 缺点
要代理的类必须实现一个接口才能使用 JDK 动态代理
- 对于接口中没有定义的方法无法实现代理,即使为其配置了切面也是如此
由于使用了反射机制,执行代理方法的速度相比 CGLIB 来说较慢
4. 测试
创建一个接口
Animalpublic interface Animal {//接口里的方法不能被final修饰void sayHello();}
创建
Animal接口的实现类Dogpublic class Dog implements Animal {public void sayHello() {System.out.println("wang wang wang");}}
创建
AnimalInvocationHandler实现InvocationHandler接口,重写invoke方法,并进行一些增强处理public class AnimalInvocationHandler implements InvocationHandler {private Animal animal;public AnimalInvocationHandler(Animal animal) {this.animal = animal;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res = null;try {before();res = method.invoke(animal, args);after();} catch (Exception e) {exception();}return res;}private void before() {System.out.println("before animal");}private void after() {System.out.println("after animal");}private void exception() {System.out.println("throw exception");}}
编写测试方法,进行测试
public class JDKTest {@Testpublic void test() {Dog dog = new Dog();Animal animal = (Animal) Proxy.newProxyInstance(dog.getClass().getClassLoader(), dog.getClass().getInterfaces(), new AnimalInvocationHandler(dog));animal.sayHello();System.out.println("-------------------------------");System.out.println(animal.getClass().getSuperclass());}}
运行结果如下: ```java before animal wang wang wang after animal
class java.lang.reflect.Proxy
可以看出来通过 JDK 动态代理生成的代理类继承了`Proxy`类,并且代理对象是`Animal`类型的,也就是接口的类型,所以说在接口中没有定义的方法都无法被代理<a name="lpyMm"></a>### CGLIB 动态代理<a name="C8iGU"></a>#### 1. 实现原理底层通过 **继承 **实现。采用`ASM`字节码生成框架,直接对需要代理的类的字节码进行操作,生成这个类的一个子类,并重写类中所有可以重写的方法,在重写的过程中,将我们定义的额外的逻辑(切面代码)织入到方法中,对方法进行增强处理<a name="biPDr"></a>#### 2. 优点- 不需要实现接口就可以使用 CGLIB 动态代理- 类中实现的可以被重写的方法都可以被代理- 通过拦截父类的方法,织入横切逻辑,执行代理方法的速度比 JDK 动态代理快<a name="Iovo1"></a>#### 3. 缺点- 无法为不能被重写的方法生成代理方法,比如:被`final`修饰的方法、被`private`修饰的方法- 如果类被`final`修饰,那么就无法继承,无法使用 CGLIB 代理- CGLIB 通过操作字节码生成代理类,所以生成代理类的速度被 JDK 动态代理慢<a name="cILzr"></a>#### 4. 测试创建一个类`Human````javapublic class Human {public String say() {System.out.println("I am a human");return "Yes, you are";}//被final修饰的方法只能引用,不能重写public final void goodbye() {System.out.println("goodbye");}}
创建自定义方法拦截器HumanMethodInterceptor,实现MethodInterceptor接口
//MethodInterceptor:方法拦截器public class HumanMethodInterceptor implements MethodInterceptor {//methodProxy参数一般用来调用原来的对应方法的。比如可以methodProxy.invokeSuper(obj, args)public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object res = null;try {before();res = methodProxy.invokeSuper(o, objects);after();} catch (Exception e) {exception();} finally {beforeReturning();}return res;}//前置通知private void before() {System.out.println("before human");}//后置通知private void after() {System.out.println("after human");}//返回前通知private void beforeReturning() {System.out.println("before return");}//异常通知private void exception() {System.out.println("find exception");}}
编写测试方法,进行测试
public class CglibTest {@Testpublic void testSay() {//Enhancer:字节码增强器,类似于Proxy类,用来创建代理对象Enhancer enhancer = new Enhancer();//通过create方法创建代理类Human humanProxy = (Human) enhancer.create(Human.class, new HumanMethodInterceptor());String res = humanProxy.say();System.out.println(res);System.out.println("-----------------------------------");System.out.println(humanProxy.getClass().getSuperclass());}@Testpublic void testGoodbye() {//Enhancer:字节码增强器,类似于Proxy类,用来创建代理对象Enhancer enhancer = new Enhancer();//通过create方法创建代理类Human humanProxy = (Human) enhancer.create(Human.class, new HumanMethodInterceptor());humanProxy.goodbye();System.out.println("-----------------------------------");System.out.println(humanProxy.getClass().getSuperclass());}}
运行结果如下:
//运行testSay的结果before humanI am a humanafter humanbefore returnYes, you are-----------------------------------class com.lorraine.springaop.cglib.Human //父类是Human类//运行testGoodbye的结果goodbye-----------------------------------class com.lorraine.springaop.cglib.Human //父类仍然是Human类
可以看出 CGLIB 代理生成的代理类是Human的子类,即被代理类的子类。并且从testGoodbye运行的结果可以看出,配置的通知全都没有执行,说明生成的代理类并没有为goodbye()进行代理,因为被final修饰的方法不能被重写
AspectJ 是如何实现的
AspectJ 是通过静态代理实现的,主要采用编译期织入,在编译期间将切面代码(增强代码)织入目标代码。除了编译期织入外,还可以编译后织入、类加载织入
编译期织入的过程:
ajc 编译器是通过 java 代码编写的,由于 javac 编译器无法识别 aspect 语法,所以编写了 ajc 编译器来识别 aspect 语法,并且 ajc 编译器是可以编译 java 文件的
基于 AspectJ 的 Spring AOP 简单实践
导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
创建接口和具体实现类
public interface Animal {void sayHello();}@Componentpublic class Cat implements Animal {@Overridepublic void sayHello() {System.out.println("mow mow mow");}}
创建切面类AnimalAspect
@Component@Aspectpublic class AnimalAspect {//为Cat类的所有方法都配置前置通知@Before("execution(* com.lorraine.springaop.aspectj.Cat.*(..))")public void before() {System.out.println("before animal");}//为Cat类的所有方法都配置后置通知@After("execution(* com.lorraine.springaop.aspectj.Cat.*(..))")public void after() {System.out.println("after animal");}}
创建配置类AopConfig,开启 AspectJ 自动代理支持
@Configuration@EnableAspectJAutoProxy@ComponentScan("com.lorraine.springaop")public class AopConfig {}
进行测试
@ComponentScan("com.lorraine.springaop")public class AspectjTest {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopConfig.class);@Testpublic void test() {Animal animal = applicationContext.getBean(Animal.class);animal.sayHello();}}
运行结果如下:
before animalmow mow mowafter animal
Spring AOP 和 AspectJ 的区别
- Spring AOP 是运行时增强,而 AspectJ 是编译时增强
- Spring AOP 是在运行期间通过代理生成目标类,属于动态代理
- AspectJ 是在编译期间将切面代码织入目标代码中,属于静态代理
- Spring AOP 仅支持
方法级的织入,而AspectJ 功能强大,支持编织字段、方法、构造函数、静态初始值设定项、最终类 / 方法等 - Spring AOP 的性能远不及 AspectJ,AspectJ 快很多
- Spring AOP 比较简单,AspectJ 比较复杂
注意
AspectJ 并不是 Spring AOP 的一部分,AspectJ 和 Spring AOP 是两种框架,只是 Spring 集成了 AspectJ 框架,可以使用 AspectJ 的注解,使用了aspect 来定义切面,使用 advice 来定义增强处理,但是 Spring AOP 并没有使用 AspectJ 的 ajc 编译器,底层仍然是采用动态代理实现
AOP 的基本概念
- 关注点
**Concern**:系统中一个模块的行为(系统基于功能划分后的一个模块,整个系统的一部分) - 横切关注点
**Crosscutting Concern**:关注点的一种,系统中的多个模块(整个系统的各个模块)都会用到的功能,例如事务管理、日志管理、数据传输、安全等 - 目标对象
**Target**:指将要被增强的对象,即包含业务逻辑的类或者对象。目标对象可能被一个或多个切面所通知 - 连接点
**Joincut**:被拦截的程序执行点。由于 Spring AOP 只支持方法类型的连接点,所以“连接点”相当于被拦截的方法 - 切入点(切点)
**Pointcut**:对连接点进行拦截的条件的定义(拦截哪些方法,确定哪些连接点要被拦截) - 通知
**Advice**:拦截到连接点之后要执行的代码,Spring AOP 以拦截器来实现通知模型,并形成一个以连接点为中心的拦截链 - 织入
**Weaving**:将切面与业务代码(业务逻辑对象)连接起来,生成代理类。可以在编译时、类加载时、运行时进行织入,在编译期进行织入则为静态代理,在运行期进行织入则为动态代理 - 切面
**Aspect**:切面是一个横切关注点的模块化,包含了同一类型的不同增强方法。由切入点和通知组成,既包含了要拦截哪些连接点,又包含了拦截之后要进行的操作(横切逻辑),Spring AOP 就是执行切面的框架,它将切面所定义的横切逻辑织入切面所定义的连接点 增强器
**Advisor**:Advisor 是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。Advisor 由切入点和通知组成。 Advisor 这个概念来自于 Spring 对 AOP的支撑,在 AspectJ 中是没有等价的概念的。Advisor 就像是一个小的自包含的切面,这个切面只有一个通知。切面自身通过一个 Bean 表示,并且必须实现一个默认接口通知有哪些类型
@Before:前置通知,在方法执行前调用@After:后置通知,无论方法是否执行成功,都会调用@AfterThrowing:方法执行抛出异常后通知,只有在抛出异常之后才会调用@AfterReturning:方法执行成功后通知,只有方法成功返回后才会调用@Round:环绕通知,在方法执行前和执行后都会通知AOP 的执行顺序
执行成功时:@Round->@before-> 业务 ->@AfterReturning->@After->@Round
执行失败时:@Round->@before-> 业务 ->@AfterThrowing->@After
注意:执行失败时最后没有执行**@Round**
