对 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. 测试
创建一个接口
Animal
public interface Animal {
//接口里的方法不能被final修饰
void sayHello();
}
创建
Animal
接口的实现类Dog
public 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 {
@Test
public 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`
```java
public 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 {
@Test
public 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());
}
@Test
public 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 human
I am a human
after human
before return
Yes, 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();
}
@Component
public class Cat implements Animal {
@Override
public void sayHello() {
System.out.println("mow mow mow");
}
}
创建切面类AnimalAspect
@Component
@Aspect
public 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);
@Test
public void test() {
Animal animal = applicationContext.getBean(Animal.class);
animal.sayHello();
}
}
运行结果如下:
before animal
mow mow mow
after 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**