你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?

栏目: IT技术 · 发布时间: 4年前

内容简介:各位小伙伴大家好,我是A哥。本专栏/系列讲解到这里,关于Spring的建议刚“翻开”本专栏的同学去我公众号往前翻翻,前几篇文章能助你投入精力较少,收获大不一样虽然你已经可以搞定95%的问题,但还剩5%呢?不要了麽?然而残酷的现实却是这样的,能解决那5%问题的才是真正的王者,他们的薪资往往能高出你一个甚至多个Level,并且在你眼中还好像还“不怎么干活”,不信你品,你细品......

各位小伙伴大家好,我是A哥。本专栏/系列讲解到这里,关于Spring的 @Configuration 配置类,应该是可以完成95%以上工作上的使用以及问题的解决。你也绝对算得上是一个“懂它”的Java Coder了,面试自然也就不在话下,基本可以实现“吊打面试官”。

建议刚“翻开”本专栏的同学去我公众号往前翻翻,前几篇文章能助你投入精力较少,收获大不一样

虽然你已经可以搞定95%的问题,但还剩5%呢?不要了麽?然而残酷的现实却是这样的,能解决那5%问题的才是真正的王者,他们的薪资往往能高出你一个甚至多个Level,并且在你眼中还好像还“不怎么干活”,不信你品,你细品...... 这就是不可替代性/稀缺性的价值......

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?

如何提高自己的不可替代性?对于三无的我们,没有办法只能冲着那5%出发呗。对于钟爱于 面向工资编程 的我们,一般还是有更高追求的嘛,毕竟在趋同的 程序员 视界里,要的就是不一样,所以需要继续打怪升级。

接下来的两篇内容会比较深入,可能会让一些“初学者”感到不适(若感觉不适赶紧往前翻翻补课),希望坚持,毕竟这最终都会反应到你每个月的工资上, 做难事必有所得嘛

我粗浅的认为,对于大多数人来说,工资是衡量个人 市场价值 的唯一/最重要标准。工资20k和22k的两人可认为是差不多的,但40k的人肯定比前者价值高出一截

版本约定

本文内容若没做特殊说明,均基于以下版本:

JDK: 1.8 Spring Framework: 5.2.2.RELEASE

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?

正文

如果说前面是些武功招式,那么接下来就到了内功修炼阶段了。 走得多块得看你手脚是否能灵活会用,而走得多远是由你的体力(内功)决定的 。下面我先以一个示例(可当面试题)开始本文的内容。

配置类在Full模式下的“能力”展示

配置类(标注有@Configuration注解,属于Full模式):

@Configuration

public class AppConfig {


}

case1:

先来个简单点的。

public static void main(String[] args) {

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);


AppConfig appConfig = context.getBean(AppConfig.class);


System.out.println(appConfig.getClass());

System.out.println(appConfig.getClass().getSuperclass() == AppConfig.class);

System.out.println(AopUtils.isCglibProxy(appConfig));

}

结果输出:

class com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$d38ead10

true

false

结果解释:

1. Full模式的配置类被CGLIB增强了,所以最终放进容器内的实际是代理对象 2. 代理类是由CGLIB生成的子类,所以父类必然就是 目标类 3. 这个为何是false???其实这个和 AopUtils.isCglibProxy() 的实现有关(建议你源码点进去瞄一眼一切都明白了),这个配置类仅仅是被CGLIB代理了,和AOP没毛关系

case2:

这个case会进阶一些。

public static void main(String[] args) throws IllegalAccessException {

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);


AppConfig appConfig = context.getBean(AppConfig.class);


Field $$beanFactoryField = ReflectionUtils.findField(appConfig.getClass(), "$$beanFactory");

BeanFactory beanFactory = (BeanFactory) $$beanFactoryField.get(appConfig);


System.out.println(beanFactory == context.getAutowireCapableBeanFactory());

System.out.println(beanFactory == context);

System.out.println(appConfig instanceof BeanFactoryAware);

System.out.println(appConfig.getClass().getInterfaces()[0]);

}

结果输出:

true

false

true

interface org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration

结果解释:

1. CGLIB字节码提升时,会自动给代理类新增一个名为 $$beanFactory 的字段/属性,在运行期间给其赋值。所以通过反射可以从 代理实例 里拿到这个属性值,并且值就是 当前BeanFactory 1. 小细节:一定只能写成 (appConfig.getClass(), "$$beanFactory") 而不能是 (AppConfig.class, "$$beanFactory") 哦,因为这个Field属于代理类而非目标类 2. 这个结果是false,和配置类本身并无关系,考察的知识点是Spring上下文Bean工厂和内建Bean工程的区别,这里先混个脸熟,下个专栏会详解的 3. 结果为true。你是否想动粗:“劳资”的AppConfig配置类明明就没有实现 BeanFactoryAware 接口,为毛你给我返回true呢? 4. 解释同上

如果面试官通过这样的题目来考你(其实目的是想让你“降薪”),你是否招架得住,成为那5%呢?本文将带你一起继续深挖Spring @Configuration 配置里面的“玄机”,看完后你再回来看这几个题目就会感叹了:so easy。

何时创建代理?

我们已然知道Full模式的配置类最终会被CGLIB字节码提升,从而最终放一个代理类对象到Spring容器里。那么我们先来弄清楚创建代理的时机在哪儿~

Spring容器在 refresh() 启动步骤的 AbstractApplicationContext#invokeBeanFactoryPostProcessors 这一步会执行所有的 BeanFactoryPostProcessor 处理器,而此时 BeanFactory 才刚刚准备好,容器内除了 ConfigurationClassPostProcessor 之外,并无任何其它 BeanFactoryPostProcessor ,截图示例如下:

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?

可能你会问:既然这么早期,那这个处理器是什么时候放进去的呢?我只能回答:在Bean容器“开山阶段”同几个开山鼻祖一起放进去的。如果你继续追问很多为什么的话,那我只能回答:这不是本专栏讲解的重点所在,放在下个专栏详解,请关注我公众号即可

既然这样,那么接下来就会会 ConfigurationClassPostProcessor 这个后置处理器喽。

ConfigurationClassPostProcessor

用于引导处理 @Configuration 配置类。该后置处理器的优先级是较高的,属于 PriorityOrdered 分类。

说明: PriorityOrdered 的优先级肯定比 Order 接口的高

// @since 3.0

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,

PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

...

}

它是一个 BeanDefinitionRegistryPostProcessor 处理器,所以在容器启动过程中会先后执行如下两个方法:

postProcessBeanDefinitionRegistry()

从注册进来的配置类(可能是Full模式,可能是Lite模式)里进一步派生bean定义。简而言之:收集到所有的 BeanDefinition (后简称为bd)存储起来,包括 @Import、@Component 等等组件。 并且做出标注:是Full模式的还是Lite模式的配置类 (若非配置组件就不标注哦)。

ConfigurationClassPostProcessor


@Override

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

// 生成一个id,放置后面再重复执行

int registryId = System.identityHashCode(registry);

// 若重复执行 就抛出异常

if (this.registriesPostProcessed.contains(registryId)) {

throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);

}

if (this.factoriesPostProcessed.contains(registryId)) {

throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);

}


// 表示此registry里的bd收集动作,已经做了 避免再重复收集此registry

this.registriesPostProcessed.add(registryId);


// 根据配置类,收集到所有的bd信息

// 并且做出mark标注:是Full模式还是Lite模式,和很重要很重要

processConfigBeanDefinitions(registry);

}

执行完此方法,已经完成了bd的收集和标记,那接下来就是本文的主菜了: 帮你解答上面case的结果

postProcessBeanFactory()

此方法的作用用一句话可概括为: 为Full模式的Bean使用CGLIB做字节码提升,确保最终生成的是代理类实例放进容器内

ConfigurationClassPostProcessor


@Override

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

int factoryId = System.identityHashCode(beanFactory);

// 防止重复处理

if (this.factoriesPostProcessed.contains(factoryId)) {

throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + beanFactory);

}

this.factoriesPostProcessed.add(factoryId);


// 在执行postProcessBeanDefinitionRegistry方法的时就已经将

// 这个id添加到registriesPostProcessed集合中了

// 所以到这里就不会再重复执行配置类的解析了(解析@Import、@Bean等)

if (!this.registriesPostProcessed.contains(factoryId)) {

processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);

}


// 从名字上看,这个方法应该就是为配置类创建代理用的喽

enhanceConfigurationClasses(beanFactory);


// 添加了一个后置处理器 它是个SmartInstantiationAwareBeanPostProcessor

// 它不是本文重点,略

beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));

}

达到这一步之前,已经完成了bd的收集和标记(见上一步)。对bd进行实例化之前,针对于 Full模式的配置类 这步骤里会做增强处理,那就是 enhanceConfigurationClasses(beanFactory) 这个方法。

enhanceConfigurationClasses(beanFactory)

对一个 BeanFactory 进行增强,先查找配置类 BeanDefinition ,再根据Bean定义信息(元数据信息)来决定配置类是否应该被 ConfigurationClassEnhancer 增强。具体处理代码如下:

ConfigurationClassPostProcessor


public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {

// 最终需要做增强的Bean定义们

Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();


for (String beanName : beanFactory.getBeanDefinitionNames()) {

BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);

Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);


... // 省略其它情况以及异常情况的处理代码


// 如果是Full模式,才会放进来

if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {

configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);

}

}

if (configBeanDefs.isEmpty()) {

// nothing to enhance -> return immediately

return;

}


// ConfigurationClassEnhancer就是对配置类做增强操作的核心类,下面详解

ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();

// 对每个Full模式的配置类,一个个做enhance()增强处理

for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {

AbstractBeanDefinition beanDef = entry.getValue();


// 如果代理了@Configuration类,则始终代理目标类

// 该属性和自动代理时是相关的,具体参见Spring的自动代理章节描述

beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);


// CGLIB是给父类生成子类对象的方式实现代理,所以这里指定“父类”类型

Class<?> configClass = beanDef.getBeanClass();

// 做增强处理,返回enhancedClass就是一个增强过的子类

Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

// 不相等,证明代理成功,那就把实际类型设置进去

// 这样后面实例化配置类的实例时,实际实例化的就是增强子类喽

if (configClass != enhancedClass) {

beanDef.setBeanClass(enhancedClass);

}

}

}

值得注意的是,虽然此方法被设计为public的,但是只被一处使用到。Spring这么做是为了给提供钩子,方便容器开发者做扩展时使用

步骤总结:

1. 从BeanFactory拿出所有的bd信息,一个个判断 2. 如果是配置类 并且是Full模式 ,就先存储起来,后面会对它做字节码提升。最终如果一个Full模式的配置类都木有,那直接return,此方法结束。否则继续 3. 对收集到的 每一个  Full模式的配置类,使用 ConfigurationClassEnhancer 增强器进行字节码提升,生成一个CGLIB子类型 1. 小细节:此处显示标注了AOP自动代理为: 始终代理目标类 4. 把CGLIB生成的子类型设置到元数据里去: beanDef.setBeanClass(enhancedClass) 。这样Spring在最后实例化Bean时, 实际生成的是该代理类型的实例 ,从而达到代理/增强的目的

该方法执行完成后,执行“结果”我截了张图,供以参考:

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?

这段代码是不难的,理解起来十分简单。但是,我们仍旧还只知道结果,并不清楚原因。凭它还无法解释上文中两个case的现象,所以我们应该端正态度继续深入,看看 ConfigurationClassEnhancer 增强器到底做了什么事。

在介绍 ConfigurationClassEnhancer 之前,希望你对CGLIB的使用有那么一定的了解,这样会轻松很多。当然不必过于深究(否则容易怀疑人生),但应该能知道如何使用 Enhancer 增强器去增强/代理目标类,如何写拦截器等。

因为之前文章介绍过了CGLIB的基本使用,限于篇幅,此处就不再啰嗦。

ConfigurationClassEnhancer源码分析

得打起精神了,因为接下来才是本文之精华,让你出彩的地方。

@since 3.0。通过生成一个CGLIB子类来增强 @Configuration 类与Spring容器进行交互,每个这样的 @Bean 方法都会被生成的子类所复写。这样子当遇到方法调用时,才有可能通过拦截从而把方法调用引回容器,通过名称获得相应的Bean。

建立在对CGLIB的使用有一定了解的基础上,再来阅读本文会变得轻松许多。该类 有且仅有一个 public方法,如下所示:

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?

ConfigurationClassEnhancer:


public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {

// 如果已经实现了该接口,证明已经被代理过了,直接返回呗~

if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {

return configClass;

}


// 若没被代理过。就先调用newEnhancer()方法创建一个增强器Enhancer

// 然后在使用这个增强器,生成代理类字节码Class对象

Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));

return enhancedClass;

}

巨简单有没有。

为何Spring的源码是开源软件的范本?因为它各种封装、 设计模式 用得都非常好,甚至对初学者都是友好的,所以说Spring易学难精。

该public方法的核心,在下面这两个个私有方法上。

newEnhancer()和createClass()

创建一个新的CGLIB Enhancer 实例,并且做好相应配置。

ConfigurationClassEnhancer:


private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {

Enhancer enhancer = new Enhancer();

// 目标类型:会以这个作为父类型来生成字节码子类

enhancer.setSuperclass(configSuperClass);

// 让代理类实现EnhancedConfiguration接口,这个接口继承了BeanFactoryAware接口

enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});


// 设置生成的代理类不实现org.springframework.cglib.proxy.Factory接口

enhancer.setUseFactory(false);

// 设置代理类名称的生成策略:Spring定义的一个生成策略

// 你名称中会有“BySpringCGLIB”字样

enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);

enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));


// 设置拦截器/过滤器

enhancer.setCallbackFilter(CALLBACK_FILTER);

enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());

return enhancer;

}



// 使用增强器生,成代理类的字节码对象

private Class<?> createClass(Enhancer enhancer) {

Class<?> subclass = enhancer.createClass();

Enhancer.registerStaticCallbacks(subclass, CALLBACKS);

return subclass;

}

Enhancer 是CGLIB的最核心API,通过方法名应该基本清楚了每一步都有什么作用吧。

Enhancer 属于CGLIB的核心API,但你发现它的包名是 xxx.springframework.xxx 。这是因为CGLIB在Spring内太常用了(强依赖),因此Spring索性就自己fork了一份代码过来~

本方法我们需要关注对 Enhancer 实例的 配置 ,有如下关注点:

通过它增强的每个类 都实现了 EnhancedConfiguration 接口 ,并且它还是 BeanFactoryAware 的子接口 统一实现接口,这和Spring AOP创建代理是不是如出一辙?想一想 实现了 BeanFactoryAware 接口,这样Spring在创建代理类实例的时候会给注入 BeanFactory 使用 SpringNamingPolicy 策略来生成类名称。这就是解释了为何代理类的名你都能看到 BySpringCGLIB 字样 对于代理最为重要的当属过滤器/拦截器 org.springframework.cglib.proxy.Callback ,它们是实现功能的核心。配置此增强器时设置了 CALLBACK_FILTER 共三个拦截器

关于 CALLBACK_FILTER ,我们发现在类 ConfigurationClassEnhancer 最开始处就申明了三个拦截器放进去了:

ConfigurationClassEnhancer


private static final Callback[] CALLBACKS = new Callback[] {

new BeanMethodInterceptor(),

new BeanFactoryAwareMethodInterceptor(),

NoOp.INSTANCE

};

如果说前面都是做准备工作,那么拦截器才是运行期真正干活的“人”了。 它能够解答我们今天的疑问~

拦截器分析

什么是动态代理?用通俗的话理解就是: 代理的核心逻辑就是依赖于拦截器实现的 ,可见拦截器(也叫增强)之于代理类是何等重要。

上面的三个拦截器中, NoOp.INSTANCE 代表什么都没做,因此我们只需要关注前两个。他俩均是 MethodInterceptor 接口的实现类,均实现了 intercept() 方法来做具体的拦截操作(他俩均是私有静态内部类哟)。

说明:本文的两个case用第一个拦截器即可解释,鉴于第二个拦截器 非常的复杂 ,所以我把它放在下篇文章详解(已写得差不多了,因为太复杂,篇幅比本文还长)

BeanFactoryAwareMethodInterceptor

顾名思义,它表示的是 BeanFactoryAware 方法的拦截器,所以靠猜应该能猜到它拦截的是 setBeanFactory(beanFactory) 方法。

说明:Spring所有的拦截器实现的拦截都是方法级别的。虽然也支持构造器的拦截,但并没有内置实现,需要使用者自行扩展(比较复杂,一般并无使用场景)

相较于下文要讲的第二个拦截器,这个拦截器比较简单。但是它实现的功能可不简约哦,因为它能够解释文首提出的两个case, 让你谈薪更有底气

既然是拦截器,就应该按如下两步去了解它: 执行时机 + 做了何事

执行时机

执行时机决定了增强逻辑何时执行,毕竟一般来说都不可能是增强所有的嘛。

BeanFactoryAwareMethodInterceptor


// 当执行到setBeanFactory(xxx)方法时匹配成功

@Override

public boolean isMatch(Method candidateMethod) {

return isSetBeanFactory(candidateMethod);

}

// 此方法标记为public static 是因为下面这个拦截器也会用到

public static boolean isSetBeanFactory(Method candidateMethod) {

return (candidateMethod.getName().equals("setBeanFactory") &&

candidateMethod.getParameterCount() == 1 &&

BeanFactory.class == candidateMethod.getParameterTypes()[0] &&

BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));

}

我们知道 setBeanFactory() 方法是由Spring容器在初始化Bean时回调调用的,而代理类实现了 EnhancedConfiguration 接口(间接实现了 BeanFactoryAware 接口),所以该拦截器的执行时机为: 在Spring初始化代理类实例时执行拦截

说明:isSetBeanFactory()判断方法做这么“复杂”主要是为了容错,“担心”你自己定义了一个名为 setBeanFactory 的方法而“搞错了”。

做了何事

作为一个拦截器,增强逻辑才是它的核心。

BeanFactoryAwareMethodInterceptor


@Override

@Nullable

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {


// 找到本类(代理类)里名为`$$beanFactory`的字段

// 若没找到直接报错。若找到了此字段,就给此字段赋值

Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);

Assert.state(field != null, "Unable to find generated BeanFactory field");

field.set(obj, args[0]);


// 如果用户类(也就是你自己定义的类)自己实现了该接口,那么别担心,也会给你赋值上

if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {

return proxy.invokeSuper(obj, args);

}

return null;

}

从执行时机知道了,它拦截的是 setBeanFactory() 方法的执行。所以这里的Method就代表的是 setBeanFactory() 方法, Object[] args 的值是 当前容器的BeanFactory工厂 (注意理解这句话)实例。

此拦截器增强完成后,结果截图如下:

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?

好了,介绍到这里本文就先告一段落。如果你是认真的看完了本文的分析,那么现在你再“回到顶部”理解那两个case的结果,你就豁然开朗了。

建议一定弄懂,我觉得已经很明朗了,所以就不再废话。若有不清楚,可以下方扫码加我微信私聊我吧(或者文末留言)

总结

又是一篇关于Spring配置类的长文,只希望对你有帮助才有意义。最为核心的两个增强/拦截器,迫于篇幅和读者的脑力(毕竟理解起来还是比较费劲的),今天只讲一个。我把另外一个 更为重要、更为复杂、 更为多金 的部分放在了下文专文阐述 ,你可关注我公众号保持“收看”。

下篇码字已经码得差不多了,80%吧。手累了,今天先休息,明天再搞,内容很精彩:smile:

关于A哥

专栏式学习 咱们小众聊,知识星球诚邀您扫码入驻(提示: 请务必先关注公众号 ,后台回复 “知识星球” 领取优惠券后再轻装入驻)

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?
  • 私人微信,扫码加A哥好友,邀你进入  Java高工、架构师 系列纯技术群(或关注公众号,后台回复 “加群” 亦可直接加入)

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?
  • 文章在公众号首发,其它平台慢1-2天。你也可关注我的个人博客:https://www.yourbatman.cn

你自我介绍说很懂 Spring 配置类,那你怎么解释这个现象?

码字非常不易, 不可以白嫖 ,点个在看/关注就是对A哥的支持喽。

点个在看,养成习惯


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Growth Hacker Marketing

Growth Hacker Marketing

Ryan Holiday / Portfolio / 2013-9-3 / USD 10.31

Dropbox, Facebook, AirBnb, Twitter. A new generation of multibillion dollar brands built without spending a dime on “traditional marketing.” No press releases, no PR firms, and no billboards in Times ......一起来看看 《Growth Hacker Marketing》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具