Moralok's Blog

你在幼年时当快乐

如果你正在参与一个共享库的开发,你可能会想为使用方提供自动配置的支持,以帮助对方快速地接入和使用。自动配置机制往往和 starter 联系在一起,本文将介绍如何创建一个自定义的 starter 并从源码角度分析 Spring Boot 自动配置的工作原理。

阅读全文 »

Import 注解是 Spring 基于 Java 注解配置的重要组成部分,处理 Import 注解是处理 Configuration 注解的子过程之一,本文将介绍 Import 注解的 3 种使用方式,然后通过分析源码和处理过程示意图解释它是如何导入(注册) BeanDefinition 的。

阅读全文 »

Nginx 没有提供开箱即用的日志滚动功能,而是将其交给使用者自己实现。你既可以按照官方文档的建议通过编写脚本实现,也可以使用 logrotate 管理日志。但是和在普通场景下不同,在使用 Docker 运行 Nginx 时,你可能需要额外考虑一点细节。本文记录了在为 Docker 中的 Nginx 的日志文件配置滚动功能过程中遇到的一些问题和思考。

阅读全文 »

原先在使用 Cloudflare Tunnel 访问家庭网络中的服务时,是直接将域名解析到相应服务。尽管 Cloudflare 已经提供相关的请求统计和安全防护功能,部分服务自身也有访问日志,但是为了更好地监控和跟踪对外服务的使用情况,采集 Cloudlfare 统计中缺少的新,决定使用 Nginx 反向代理内部服务,统一内部服务的访问入口。简而言之就是,又折腾一些有的没的。以上修改带来的一个附加好处是在局域网内访问服务时,通过在 hosts 文件中添加域名映射,可以用更加容易记忆的域名代替 IP + port 的形式去访问。

阅读全文 »

直接展示一个具体的 Dubbo SPI 自适应拓展是什么样子,是一种非常好的表现其作用的方式。正如官方博客中所说的,它让人对自适应拓展有更加感性的认识,避免读者一开始就陷入复杂的代码生成逻辑。本文在此基础上,从更原始的使用方式上展现“动态加载”技术对“按需加载”的天然倾向,从更普遍的角度解释自适应拓展的本质目的,在介绍 Dubbo 的具体实现是如何约束自身从而规避缺点之后,详细梳理了 Dubbo SPI 自适应拓展的相关源码和工作原理。

阅读全文 »

SPI 作为一种服务发现机制,允许程序在运行时动态地加载具体实现类。因其强大的可拓展性,SPI 被广泛应用于各类技术框架中,例如 JDBC 驱动、SpringDubbo 等等。Dubbo 并未使用原生的 Java SPI,而是重新实现了一套更加强大的 Dubbo SPI。本文将简单介绍 SPI 的设计理念,通过示例带你体会 SPI 的作用,通过 Dubbo 获取拓展的流程图源码分析带你理解 Dubbo SPI 的工作原理。深入了解 Dubbo SPI,你将能更好地利用这一机制为你的程序提供灵活的拓展功能。

阅读全文 »

Configuration 注解是 Spring 中常用的注解,在一般的应用场景中,它用于标识一个类作为配置类,搭配 Bean 注解将创建的 bean 交给 Spring 容器管理。神奇的是,被 Bean 注解标注的方法,只会被真正调用一次。这种方法调用被拦截的情况很容易让人联想到代理,如果你在 Debug 时注意过配置类的实例,你会发现配置类的 Class 名称中携带 EnhancerBySpringCGLIB。本文将从源码角度,分析 Configuration 注解是如何工作的。

阅读全文 »

Spring 中的循环依赖是一个“大名鼎鼎”的问题,本文从原始的问题出发分析应当如何正确地看待和处理循环依赖现象,同时也会回归到源码详细介绍 Spring 的具体处理过程,并在最后给出笔者的个人思考。

阅读全文 »

Spring AOP 是基于代理实现的,它既支持 JDK 动态代理也支持 CGLib。

  • 在什么时候创建代理对象的?
  • 怎么创建代理对象的?

过程简单图解

准备工作

  • 引入依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.12.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.12.RELEASE</version>
    </dependency>
  • 目标对象类
    1
    2
    3
    4
    5
    6
    public class MathCalculator {

    public int div(int i, int j) {
    return i / j;
    }
    }
  • 切面类
    1
    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
    34
    35
    36
    @Aspect
    public class LogAspects {

    @Pointcut("execution(public int com.moralok.aop.MathCalculator.*(..))")
    public void pointCut() {

    }

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
    System.out.println(joinPoint.getSignature().getName() + "除法运行@Before。。。参数列表为 " + Arrays.asList(joinPoint.getArgs()) + "");
    }

    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint) {
    System.out.println(joinPoint.getSignature().getName() + "除法结束@After。。。");
    }

    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
    System.out.println(joinPoint.getSignature().getName() + "除法正常返回@AfterReturning。。。运行结果 " + result);
    }

    @AfterThrowing(value = "pointCut()", throwing = "e")
    public void logException(JoinPoint joinPoint, Exception e) {
    System.out.println(joinPoint.getSignature().getName() + "除法异常@AfterThrowing。。。异常信息 " + e.getMessage());
    }

    @Around(value = "execution(public String com.moralok.bean.Car.getName(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println(joinPoint.getSignature().getName() + " @Around开始");
    Object proceed = joinPoint.proceed();
    System.out.println(joinPoint.getSignature().getName() + " @Around结束");
    return proceed;
    }
    }
  • 配置类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    @EnableAspectJAutoProxy
    public class AopConfig {

    @Bean
    public MathCalculator mathCalculator() {
    return new MathCalculator();
    }

    @Bean
    public LogAspects logAspects() {
    return new LogAspects();
    }
    }
  • 测试类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class AopTest {

    @Test
    public void aopTest() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AopConfig.class);
    MathCalculator mathCalculator = ac.getBean(MathCalculator.class);
    mathCalculator.div(1, 1);
    mathCalculator.div(1, 0);
    ac.close();
    }
    }
  • Debug 断点的判断条件(可选)
    1
    beanName.equals("mathCalculator")

创建代理 Bean 和创建普通 Bean 的区别

其实创建代理 Bean 的过程和创建普通 Bean 的过程直到进行初始化处理(initializeBean)前都是一样的。更具体地说,如很多资料所言,Spring 创建代理对象的工作,是在应用后置处理器阶段完成的。

常规的入口 getBean

mathCalculator 以 getBean 方法为起点,开始创建的过程。

1
2
3
4
5
6
@Override
public void preInstantiateSingletons() throws BeansException {
// ...(mathCalculator)
getBean(beanName);
// ...
}

应用后置处理器

在正常地实例化 Bean 后,初始化 Bean 时,会对 Bean 实例应用后置处理器。

可是,究竟是哪一个后置处理器做的呢

1
2
3
4
5
6
7
8
9
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
// ...
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
// ...
invokeInitMethods(beanName, wrappedBean, mbd);
// ...
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}

AnnotationAwareAspectJAutoProxyCreator

在本示例中,创建代理的后置处理器就是 AnnotationAwareAspectJAutoProxyCreator,它继承自 AbstractAutoProxyCreator,AbstractAutoProxyCreator 实现了 BeanPostProcessor 接口。

那么,它是什么时候,怎么加入到 beanFactory 中呢

PS: 显然,还有其他继承自 AbstractAutoProxyCreator 的后置处理器,暂时不谈。

BeanPostProcessor 的方法

postProcessBeforeInitialization 和 postProcessAfterInitialization 方法,前者什么都没做,后者在必要时对 Bean 进行包装。

  • AbstractAutoProxyCreator#postProcessAfterInitialization 就是创建代理对象的入口。
  • wrapIfNecessary 就是将 Bean 包装成代理 Bean 的入口方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
// ...
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 什么都没做
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 如有必要,将 bean 包装成代理对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
// ...
}

创建代理 Bean 的过程

按需包装成代理 wrapIfNecessary

1
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
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 判断是否直接返回 bean
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

// 如果有适用于当前 bean 的 advise 则为其创建代理
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}

this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

AbstractAutoProxyCreator 视角,创建代理

AbstractAutoProxyCreator#createProxy,创建一个 ProxyFactory,将工作交给它处理。

  1. 创建一个代理工厂 ProxyFactory
  2. 设置相关信息
  3. 通过 ProxyFactory 获取代理
1
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
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);

if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}

Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);

proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}

return proxyFactory.getProxy(getProxyClassLoader());
}

ProxyFactory 视角,获取代理

ProxyFactory#getProxy,创建一个 AopProxy 并委托它实现 getProxy。

AopProxy 的含义与职责从字面上有点不好理解。

1
2
3
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}

ProxyFactor视角,创建 AopProxy

ProxyFactory#createAopProxy,获取一个 AopProxyFactory 创建 AopProxy。

1
2
3
4
5
6
7
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 获取 AopProxy 工厂并创建一个 AopProxy
return getAopProxyFactory().createAopProxy(this);
}

AopProxyFactory视角,创建 AopProxy

AopProxyFactory#createAopProxy。

  • AopProxyFactory 有且仅有一个默认实现 DefaultAopProxyFactory。
  • createAopProxy 方法会根据配置信息,返回具体实现:开箱即用的有 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy。

这里的处理,决定了 Spring AOP 会使用哪一种动态代理实现。比如 Spring AOP 默认使用 JDK 动态代理,如果目标对象实现了接口 Spring 会使用 JDK 动态代理,这些结论的依据就在于此。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
}

获取代理 AopProxy#getProxy

AopProxy 视角,获取代理。

JDK 动态代理

JdkDynamicAopProxy。

1
2
3
4
5
6
@Override
public Object getProxy(ClassLoader classLoader) {
// ...
// JDK 动态代理,已经和 Spring 无关
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
InvocationHandler 的 invoke 方法

根据 Proxy.newProxyInstance(classLoader, proxiedInterfaces, this) 可知,this 也就是 JdkDynamicAopProxy 同时也是一个 InvocationHandler,它必然实现了 invoke 方法,当代理对象调用方法时,就会进入到 invoke 方法中。

1
2
3
4
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ...
}

CGLib 动态代理

ObjenesisCglibAopProxy。

1
2
3
4
5
6
7
8
@Override
public Object getProxy(ClassLoader classLoader) {
// ...
// CGLib 动态代理,已经和 Spring 无关
Enhancer enhancer = createEnhancer();
// ...
return createProxyClassAndInstance(enhancer, callbacks);
}
为什么 Spring 中没有依赖 CGLib

你可能会注意到 Spring 中并没有直接依赖 CGLib,像 Enhancer 所在的包是 org.springframework.cglib.proxy。根据文档:

从 spring 3.2 开始,不再需要将 cglib 添加到类路径中,因为 cglib 类在 org.springframework 下重新打包并分布在 spring-core jar 中。 这样做既是为了方便,也是为了避免与使用不同版本 cglib 的其他项目发生潜在冲突。

创建代理前的准备

在前面预留了一些问题,当初我在看网上的资料时就有这些困惑。

Bean 后置处理器 AspectJAwareAdvisorAutoProxyCreator 在什么时候,怎么加入到 beanFactory 中的?

Debug 停留在 Spring 上下文刷新方法中的 finishBeanFactoryInitialization。

1
2
3
4
5
6
7
8
@Override
public void refresh() throws BeansException, IllegalStateException {
// ...
invokeBeanFactoryPostProcessors(beanFactory);
// ...
finishBeanFactoryInitialization(beanFactory);
// ...
}

从 beanFatory 的 beanDefinitionMap 可以观察到,配置类 AopConfig 中的 MathCalculator 和 LogAspect 的信息已经就位。

从 beanFactory 的 beanProcessor 可以观察到,AnnotationAwareAspectJAutoProxyCreator 已经就位。

@EnableXXX 的魔法

注解 @EnableXXX 往往伴随着注解 @Import,在 invokeBeanFactoryPostProcessors(beanFactory) 中,工厂后置处理器 ConfigurationClassPostProcessor 会处理它。

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}

在 ConfigurationClassPostProcessor 的处理中,因为 AspectJAutoProxyRegistrar 实现了 ImportBeanDefinitionRegistrar,registerBeanDefinitions 方法会被调用,AnnotationAwareAspectJAutoProxyCreator 的 beanDefinition 随之被注册到 beanFactory,因 AnnotationAwareAspectJAutoProxyCreator 实现了 BeanPostProcessor 被提前创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 如有必要注册 AspectJAnnotationAutoProxyCreator
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// 根据配置设置一些属性
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
1
2
3
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

切面类 LogAspect 的解析是在什么时候?

进入创建 Bean 的方法 createBean 后,除了 doCreateBean,应额外留意 resolveBeforeInstantiation 方法。

  1. Object bean = resolveBeforeInstantiation(beanName, mbdToUse),在实例化前进行解析。
  2. Object beanInstance = doCreateBean(beanName, mbdToUse, args),创建 Bean 的具体过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
// ...
try {
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
// ...

Object beanInstance = doCreateBean(beanName, mbdToUse, args);
// ...
return beanInstance;
}

入口方法 resolveBeforeInstantiation

根据注释,该方法给 BeanPostProcessors 一个机会提前返回一个代理对象。在本示例中,返回 null,但是方法在第一次执行后已经提前解析得到 advisors 并缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
// 注意,应用的是实例化前的处理
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
// 注意,应用的是初始化后的处理
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}

InstantiationAwareBeanPostProcessor

应用 InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation。

1
2
3
4
5
6
7
8
9
10
11
12
13
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
// 循环依次处理
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
return result;
}
}
}
return null;
}

AnnotationAwareAspectJAutoProxyCreator 不仅仅是一个 BeanPostProcessor,它还是一个 InstantiationAwareBeanPostProcessor。

1
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
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
Object cacheKey = getCacheKey(beanClass, beanName);

if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}

if (beanName != null) {
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
this.targetSourcedBeans.add(beanName);
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
}

return null;
}

和 wrapIfNecessary 方法对比,容易发现两者有不少相似的处理。

注意:以下方法应注意是否被子类重写

org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#shouldSkip

1
2
3
4
5
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
// 查找并缓存 advisors
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// ...
}

org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean

1
2
3
4
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
// ...
}

org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors

1
2
3
4
5
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 查找并缓存 advisors
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// ...
}

容易注意到两者在创建代理前,都会调用 findCandidateAdvisors 方法查找候选的 advisors,其实这也是我们想要找的对切面类的解析处理所在。

查找并缓存 advisors

org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors

1
2
3
4
5
protected List<Advisor> findCandidateAdvisors() {
List<Advisor> advisors = super.findCandidateAdvisors();
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
return advisors;
}

org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

1
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
// 第一次进入,没有缓存
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new LinkedList<Advisor>();
aspectNames = new LinkedList<String>();
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
for (String beanName : beanNames) {
// ...
// 如果是切面,解析得到 advisors
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
// ...
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}

if (aspectNames.isEmpty()) {
return Collections.emptyList();
}
// 以后进来读缓存
List<Advisor> advisors = new LinkedList<Advisor>();
for (String aspectName : aspectNames) {
List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
if (cachedAdvisors != null) {
advisors.addAll(cachedAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
return advisors;
}

可以通过 beanFactory->beanPostProcessors->aspectJAdvisorsBuilder->advisorsCache 观察 advisors 的查找情况。

介绍

JDK 动态代理

Java 标准库提供了动态代理功能,允许程序在运行期动态创建指定接口的实例。

CGLib 动态代理

使用 ASM 框架,加载代理对象的 Class 文件,通过修改其字节码生成子类。

cglib Github 仓库

适用场景

  • JDK 动态代理适用于实现接口的类,对未实现接口的类无能为力。
  • CGLib 不要求类实现接口,但对 final 方法无能为力。

性能比较

  • 在 JDK 8 以前,CGLib 性能更好
  • 从 JDK 8 开始,JDK 动态代理性能更好

根据 README.md 的提醒,cglib 已经不再维护,且在较新版本的 JDK 尤其是 JDK 17+ 中表现不佳,官方推荐可以考虑迁移到 ByteBuddy。在如今越来越多的项目迁移到 JDK 17 的背景下,值得注意。

使用

代理对象的类和接口

代理对象的类和实现的接口:

  • HelloService.java

    1
    2
    3
    public interface HelloService {
    void sayHello(String name);
    }
  • HelloService.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
    if (name == null) {
    throw new IllegalArgumentException("name can not be null");
    }
    System.out.println("Hello " + name);
    }
    }

JDK 动态代理示例

  • 自定义 InvocationHandler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class UserServiceInvocationHandler implements InvocationHandler {

    private Object target;

    public UserServiceInvocationHandler(Object target) {
    this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    System.out.println("do sth. before invocation");
    Object ret = method.invoke(target, args);
    System.out.println("do sth. after invocation");
    return ret;
    } catch (Exception e) {
    System.out.println("do sth. when exception occurs");
    throw e;
    } finally {
    System.out.println("do sth. finally");
    }
    }
    }
  • 测试类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class JdkProxyTest {

    public static void main(String[] args) {
    HelloService target = new HelloServiceImpl();
    ClassLoader classLoader = target.getClass().getClassLoader();
    Class<?>[] interfaces = target.getClass().getInterfaces();
    UserServiceInvocationHandler invocationHandler = new UserServiceInvocationHandler(target);

    HelloService proxy = (HelloService) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    proxy.sayHello("Tom");
    System.out.println("=================");
    proxy.sayHello(null);
    }
    }
  • 结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    do sth. before invocation
    Hello Tom
    do sth. after invocation
    do sth. finally
    =================
    do sth. before invocation
    do sth. when exception occurs
    do sth. finally
    Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
    at com.sun.proxy.$Proxy0.sayHello(Unknown Source)
    at com.moralok.proxy.jdk.JdkProxyTest.main(JdkProxyTest.java:19)
    Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.moralok.proxy.jdk.UserServiceInvocationHandler.invoke(UserServiceInvocationHandler.java:18)
    ... 2 more
    Caused by: java.lang.IllegalArgumentException: name can not be null
    at com.moralok.proxy.HelloServiceImpl.sayHello(HelloServiceImpl.java:8)

CGLib 动态代理示例

  • 引入依赖
    1
    2
    3
    4
    5
    6
    7
    <dependencies>
    <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
    </dependency>
    </dependencies>
  • 自定义 MethodInterceptor
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class UserServiceMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    try {
    System.out.println("do sth. before invocation");
    Object ret = proxy.invokeSuper(obj, args);
    System.out.println("do sth. after invocation");
    return ret;
    } catch (Exception e) {
    System.out.println("do sth. when exception occurs");
    throw e;
    } finally {
    System.out.println("do sth. finally");
    }
    }
    }
  • 测试类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class CglibTest {

    public static void main(String[] args) {
    UserServiceMethodInterceptor methodInterceptor = new UserServiceMethodInterceptor();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(HelloServiceImpl.class);
    enhancer.setCallback(methodInterceptor);

    HelloService proxy = (HelloService) enhancer.create();
    proxy.sayHello("Tom");
    System.out.println("=================");
    proxy.sayHello(null);
    }
    }
  • 结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    do sth. before invocation
    Hello Tom
    do sth. after invocation
    do sth. finally
    =================
    do sth. before invocation
    do sth. when exception occurs
    do sth. finally
    Exception in thread "main" java.lang.IllegalArgumentException: name can not be null
    at com.moralok.proxy.HelloServiceImpl.sayHello(HelloServiceImpl.java:8)
    at com.moralok.proxy.HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31.CGLIB$sayHello$0(<generated>)
    at com.moralok.proxy.HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31$$FastClassByCGLIB$$c068b511.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at com.moralok.proxy.cglib.UserServiceMethodInterceptor.intercept(UserServiceMethodInterceptor.java:14)
    at com.moralok.proxy.HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31.sayHello(<generated>)
    at com.moralok.proxy.cglib.CglibTest.main(CglibTest.java:18)

查看 JDK 生成的代理类

使用以下语句,将在工作目录下生成代理类的 Class 文件。

1
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
1
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public final class $Proxy0 extends Proxy implements HelloService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
// 将 InvocationHandler 传递给父类 Proxy
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

// 代理方法调用 InvocationHandler 的 invoke 方法
public final void sayHello(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

// 静态代码块,初始化 Method 属性。
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.moralok.proxy.HelloService").getMethod("sayHello", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

查看 CGLib 生成的子类

使用以下语句,将 CGLib 生成的子类的 Class 文件输出到指定目录,会发现出现了 3 个 Class 文件。

1
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\Users\\username\\Class");
  • HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31.class,代理类
  • HelloServiceImpl$$FastClassByCGLIB$$a5654167.class,被代理类的 FastClass
  • HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31$$FastClassByCGLIB$$c068b511.class,代理类的 FastClass

代理类定义

继承了被代理类。

1
2
public class HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31 extends HelloServiceImpl implements Factory {
}

静态代码块

1
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
static {
// 调用静态钩子方法
CGLIB$STATICHOOK1();
}

static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.moralok.proxy.HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31");
Class var1;
// 获取 Object 类的 equals、toString、hashCode、clone 这几个特定方法的 Method 对象
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
// 还生成了相对应的 Method 属性保存(为了减少一次寻址吗?)
CGLIB$equals$1$Method = var10000[0];
// 为每一个 Method 创建一个 MethodProxy
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
// 被代理类的方法也做相同处理
CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "(Ljava/lang/String;)V"}, (var1 = Class.forName("com.moralok.proxy.HelloServiceImpl")).getDeclaredMethods())[0];
CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "sayHello", "CGLIB$sayHello$0");
}

MethodProxy 稍后再做介绍。

构造器方法

构造器方法内,调用了绑定回调(Callbacks)方法。

1
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
public HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31() {
CGLIB$BIND_CALLBACKS(this);
}

// 标识是否已经绑定过回调
private boolean CGLIB$BOUND;

private static final void CGLIB$BIND_CALLBACKS(Object var0) {
HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31 var1 = (HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31)var0;
if (!var1.CGLIB$BOUND) {
// 未绑定过回调则进行绑定,更新标识
var1.CGLIB$BOUND = true;
// 先获取 THREAD_CALLBACKS
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if (var10000 == null) {
// 如果为 null,再获取 STATIC_CALLBACKS
var10000 = CGLIB$STATIC_CALLBACKS;
if (var10000 == null) {
// 如果仍然为 null,直接返回
return;
}
}

// 每一个 Callback (像之前的 Method 一样)都有专门的属性保存
var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
}

}

生成的代理方法

CGLib 会为每一个代理方法生成两个对应的方法,一个直接调用父类方法,一个则调用回调(拦截器)的 intercept 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final void CGLIB$sayHello$0(String var1) {
super.sayHello(var1);
}

public final void sayHello(String var1) {
// 获取回调(拦截器)
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
// 如果为 null,先进行回调绑定
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
// 如果回调(拦截器)不为 null,则调用 intercept 方法
var10000.intercept(this, CGLIB$sayHello$0$Method, new Object[]{var1}, CGLIB$sayHello$0$Proxy);
} else {
// 否则直接调用父类方法
super.sayHello(var1);
}
}

CGLib 通过继承实现动态代理的过程,在查看生成的子类的 Class 后,是非常容易理解的。拦截器的参数有代理对象、Method、方法参数和 MethodProxy 对象。

分析 MethodProxy

如何在拦截器中调用被代理的方法呢?就是通过 MethodProxy 实现的。

创建 MethodProxy

MethodProxy 是 CGLib 为每一个代理方法创建的方法代理,当调用拦截的方法时,它被传递给 MethodInterceptor 对象的 intercept 方法。它可以用于调用原始方法,或对同一类型的不同对象调用相同方法。

1
2
3
4
5
6
7
8
9
10
11
12
CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "sayHello", "CGLIB$sayHello$0");

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
// sayHello 方法签名
proxy.sig1 = new Signature(name1, desc);
// CGLIB$sayHello$0 方法签名
proxy.sig2 = new Signature(name2, desc);
// 被代理类和代理类
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}

CreateInfo 静态内部类,保存被代理类和代理类以及其他一些信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static class CreateInfo
{
// 被代理类
Class c1;
// 代理类
Class c2;
NamingPolicy namingPolicy;
GeneratorStrategy strategy;
boolean attemptLoad;

public CreateInfo(Class c1, Class c2)
{
this.c1 = c1;
this.c2 = c2;
AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
if (fromEnhancer != null) {
namingPolicy = fromEnhancer.getNamingPolicy();
strategy = fromEnhancer.getStrategy();
attemptLoad = fromEnhancer.getAttemptLoad();
}
}
}

FastClass 和方法索引对

调用原始方法 invokeSuper

MethodProxy 通过 invokeSuper 调用原始方法(父类方法)。

1
2
3
4
5
6
7
8
9
10
11
12
// invoke 方法的代码相似
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
// 初始化,生成 FastClassInfo
init();
FastClassInfo fci = fastClassInfo;
// 调用原始(父类)方法
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}

生成 FastClass 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void init()
{
// 双重校验锁,生成 FastClass 和方法索引对
if (fastClassInfo == null)
{
synchronized (initLock)
{
if (fastClassInfo == null)
{
CreateInfo ci = createInfo;

FastClassInfo fci = new FastClassInfo();
// 生成 FastClass
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
// 获取方法索引
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}

FastClass 信息

  • f1 是被代理类的 FastClass 对象,i1 是 CGLIB$sayHello$0 方法在生成的 FastClass 中的索引。
  • f2 是代理类的 FastClass 对象,i2 是 sayHello 方法在生成的 FastClass 中的索引。

invoke 方法根据传入的方法索引,快速定位要调用对象 obj 的哪个方法。

CGLib 完全有能力获得 CGLIB$sayHello$0 的 Method 对象,通过反射实现调用,这样处理逻辑更加清楚。但是早期 Java 反射的性能并不好,通过 FastClass 机制避免使用反射从而提升了性能。

1
2
3
4
5
6
7
private static class FastClassInfo
{
FastClass f1;
FastClass f2;
int i1;
int i2;
}

FastClass 的 invoke 方法

以代理类的 FastClass HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31$$FastClassByCGLIB$$c068b511 为例,当传入的方法索引为 16 时,就会调用 CGLIB$sayHello$0 方法。

  1. 获取代理对象
  2. 根据传入的方法索引,调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
HelloServiceImpl..EnhancerByCGLIB..c51b2c31 var10000 = (HelloServiceImpl..EnhancerByCGLIB..c51b2c31)var2;
int var10001 = var1;

try {
switch (var10001) {
case 0:
return new Boolean(var10000.equals(var3[0]));
// ...
case 16:
var10000.CGLIB$sayHello$0((String)var3[0]);
return null;
// ...
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}

throw new IllegalArgumentException("Cannot find matching method/constructor");
}

获取方法索引

怎么知道方法的索引呢?在初始化 FastClass 信息时,不仅生成了 FastClass,还通过 getIndex 获取方法的索引。

在 JDK 7 之后,switch 不仅可以支持 int、enum,还能支持 String,CGLib 这样实现是出于兼容性的考虑还是说有什么性能提升?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch (var10000.hashCode()) {
// ...
case -1721191351:
if (var10000.equals("CGLIB$sayHello$0(Ljava/lang/String;)V")) {
return 16;
}
break;
// ...
}

return -1;
}

总结和思考

两者在使用上是相仿的。

  • 对于两者的源码,读得不多。有时候会感慨,看这么多年前的代码,还是感觉吃力。有时候想,如果不好好看源码,心里不踏实;如果花很多时间理清楚了,但是发现更多只是知道了一些细节,于整体理解的提升不大,又会感觉不值得。
  • 但也提醒自己,不要太在意,用得本就不多,涉及源码的机会更是没有,如果方方面面都要细究,人生太短,智商不够,等涉足相关问题再回头研究。
  • 基础的用法和概念应该了解,不然看到 Spring AOP 源码时,分不清 Spring 封装的边界在哪里。

借着梳理 Spring 的机会回头再看,又感觉轻松不少。

0%