# AOP
如何正确使用Spring进行面向切面编程,请参考官方文档 (opens new window), 本篇主要讲它的实现原理。
面向切面编程(Aspect-oriented Programming AOP)是面向对象编程(Object-oriented Programming OOP)的补充,它提供了另一种关于程序结构的思考方式。
# OOP 和 AOP 分别解决的问题
下面我们先看一个OOP的例子:
例如:现有三个类,Horse、Pig、Dog,这三个类中都有 eat() 和 run() 两个方法。
通过 OOP 思想中的继承,我们可以提取出一个 Animal 的父类,然后将 eat 和 run 方法放入父类中,Horse、Pig、Dog通过继承Animal类即可自动获得eat()和run()方法,这样将会少些很多重复的代码。
图示:

OOP编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类Animal中的多个方法的相同位置出现了重复的代码,OOP 就解决不了,示例代码如下:
/**
* 动物父类
*/
public class Animal {
/** 身高 */
private String height;
/** 体重 */
private double weight;
public void eat() {
// 性能监控代码
long start = System.currentTimeMillis();
// 业务逻辑代码
System.out.println("I can eat...");
// 性能监控代码
System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
}
public void run() {
// 性能监控代码
long start = System.currentTimeMillis();
// 业务逻辑代码
System.out.println("I can run...");
// 性能监控代码
System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
这部分重复的代码,一般统称为横切逻辑代码。
图示:

横切逻辑代码存在的问题:
- 代码重复问题
- 横切逻辑代码和业务代码混杂在一起,代码臃肿,不变维护
通过上面的分析可以发现,AOP主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
# Spring AOP
AOP在Spring Framework中用于:
- 提供声明式服务。此类服务最重要的是声明式事务管理。
- 用户可以实现自定义切面,以AOP补充其对OOP的使用。例如ControllerLogAspect。
Spring AOP默认将标准JDK动态代理用于AOP代理。所以它可以代理任何接口(或一组接口)。
Spring AOP也可以使用CGLIB代理,默认情况下,如果业务对象未实现接口,则使用CGLIB。由于对接口而不是对类进行编程是一种好习惯(As it is good practice to program to interfaces rather than classes),因此业务类通常实现一个或多个业务接口。
实际项目中如何使用代理?
按照我们目前的编程习惯,Mapper接口使用的JDK动态代理,Service、Facade、Controller都是使用CGLIB代理。
可以通过在项目启动时,debug AutowiredFieldElement类来查看注入属性时,使用的是哪种方式实现的代理类。
如下图所示:

参考源码如下:
// org.springframework.aop.framework.AopProxyFactory.java
public interface AopProxyFactory {
AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}
2
3
4
5
6
// org.springframework.aop.framework.DefaultAopProxyFactory.java
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 如果是接口,使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则使用 cglib
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
除了以上两种代理方式外,当然你也可以使用AspectJ ,Spring AOP已经集成了AspectJ ,AspectJ应该算的上是Java生态系统中最完整的AOP框架了。
# @EnableAspectJAutoProxy
开启注解版的AOP功能
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
// 是否强制指定使用CGLIB代理
boolean proxyTargetClass() default false;
/**
* @since 4.3.1 代理的暴露方式:解决内部调用不能使用代理的场景 默认为false表示不处理
* true:这个代理就可以通过AopContext.currentProxy()获得这个代理对象的一个副本(ThreadLocal里面),从而我们可以很方便得在Spring框架上下文中拿到当前代理对象(处理事务时很方便)
* 必须为true才能调用AopContext得方法,否则报错:Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
*/
boolean exposeProxy() default false;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
// org.springframework.context.annotation.AspectJAutoProxyRegistrar.java
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
// 是否强制指定使用CGLIB代理
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
// 代理的暴露方式:解决内部调用不能使用代理的场景
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# AspectJAutoProxyRegistrar 🔨
AspectJAutoProxyRegistrar 是一个 ImportBeanDefinitionRegistrar
AnnotationAwareAspectJAutoProxyCreator这个类定义为BeanDefinition注册到Spring容器中,这是通过实现ImportBeanDefinitionRegistrar接口来装载的.
AnnotationAwareAspectJAutoProxyCreator继承了SmartInstantiationAwareBeanPostProcessor, 也就是BeanPostProcessor,这个后置处理器可以拦截实例创建, 在创建之前调用postProcessBeforeInstantiation
# Spring AOP 和 AspectJ AOP 的区别
Spring AOP属于运行时增强,而AspectJ是编译时增强。 Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ ,它比Spring AOP快很多。
# Spring AOP final 方法
由于在项目里面用到模板方法设计模式,在抽象类里面有一个final方法。这个final方法调用@Autowired注解的属性字段时候报空指针异常。
我们知道在默认情况下,如果业务对象未实现接口,则使用CGLIB(例如:controller类,抽象类的子类)。我们称代理的对象为proxy,
被代理的对象是target。而对于final方法,子类是不能覆盖的,走的代码流程依然是target里面的。
@Autowired AutowiredFieldElement 实现属性依赖注入核心源码如下:
// 根据容器中`Bean`定义,解析指定的依赖关系,获取依赖对象
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
2
其实最核心还是在Bean工厂里,也就是它的唯一内建实现类org.springframework.beans.factory.support.DefaultListableBeanFactory。
# 解决内部方法调用导致AOP失效的问题
# 方法一:通过 SpringUtil.getBean 手动获取对象来获取代理对象
@Service
@Slf4j
public class DemoService {
public void add(){
DemoService bean = SpringUtil.getBean(DemoService.class);
bean.sendToKafka();
}
@Async
public void sendToKafka() {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方法二:通过 AopContext 获取当前代理
- 第一步:需要在启动类中增加以下注解
@EnableAspectJAutoProxy(exposeProxy = true)
- 第二步:使用
AopContext.currentProxy()获取当前代理,调用内部方法
CurrentImpl currentProxy = (CurrentImpl) AopContext.currentProxy();
// 使用代理调用
currentProxy.method();
2
3