因为不明白Spring-的@Configuration-配置类,我被面试官绝地反杀了

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
//什么是Full类,简单来说就是加了 @Configuration 的配置类
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
…忽略日志打印…
如果是Full模式,才会放进来
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// 没有什么可增强的->立即返回
return;
}
//配置类增强器
// ConfigurationClassEnhancer就是对配置类做增强操作的核心类
//初始化会初始化两个chlib拦截类 BeanFactoryAwareMethodInterceptor 和 BeanMethodInterceptor
//这个是重点 这个类里面的方法会产生最终的代理类
//这个方法里面有个
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
//对每个Full模式的配置类,一个个做enhance()增强处理
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// 如果@Configuration类被代理,请始终代理目标类
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
try {
// 设置用户指定的bean类的增强子类
//CGLIB是给父类生成子类对象的方式实现代理,所以这里指定“父类”类型
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader); if (configClass != null) { //做增强处理,返回enhancedClass就是一个增强过的子类 //这个是重点,这个会构建一个cglib的增强器,最终返回被代理完成的类对象! Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
//不相等,证明代理成功,那就把实际类型设置进去
if (configClass != enhancedClass) {
… 忽略日志打印 …
//这样后面实例化配置类的实例时,实际实例化的就是增强子类喽
//这里就是替换 config类的beanClass对象的!
beanDef.setBeanClass(enhancedClass);
}
}
}
catch (Throwable ex) {
。。。。。忽略异常处理。。。。。。。
}
}
}

这个类至关重要,总共做了这样几件事:

  1. 筛选配置类,只有加了 @Configuration的配置类才会被增强!
  2. 使用enhancer.enhance构建一个增强器,返回增强后的代理类对象!
  3. 替换配置类原始的beanClass,为代理后的class!

那么,我们最关心的是如何实现的,肯定要看enhancer.enhance里面的逻辑~

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
// 如果已经实现了该接口,证明已经被代理过了,直接返回
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
。。。。忽略日志打印。。。。
return configClass;
}
//没被代理过。就先调用newEnhancer()方法创建一个增强器Enhancer
//然后在使用这个增强器,生成代理类字节码Class对象
//创建一个新的CGLIB Enhancer实例,并且做好相应配置
//createClass是设置一组回调(也就是cglib的方法拦截器)
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
if (logger.isTraceEnabled()) {
。。。。忽略日志打印。。。。
}
return enhancedClass;
}

这是一个过度方法,真正去构建一个代理增强器的是newEnhancer方法,我们似乎接近了我们要的答案!

/**

  • 创建一个新的CGLIB {@link Enhancer}实例。
    */
    private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) { Enhancer enhancer = new Enhancer(); // 目标类型:会以这个作为父类型来生成字节码子类 enhancer.setSuperclass(configSuperClass); //代理类实现EnhancedConfiguration接口,这个接口继承了BeanFactoryAware接口 //这一步很有必要,使得配置类强制实现 EnhancedConfiguration即BeanFactoryAware 这样就可以轻松的获取到beanFactory 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;
    }

如果你熟悉cglib的话,肯定对这几行代码熟悉无比,主要做了这样几件事!

  1. 设置需要代理的类
  2. 设置生成的代理类需要实现的接口,这里设置实现了EnhancedConfiguration,注意这个是一个很骚的操作,他是能够保证最终类能够从beanFactory返回的一个重要逻辑,为什么?因为EnhancedConfigurationBeanFactoryAware的子类,Spring会回调他,给他设置一个 beanFactory ,如果你看不懂不妨先把和这个记下来,等看完在回来仔细品味一下!
  3. 设置过滤器,过滤器里面其实是一组回调方法,这个回调方法是最终方法被拦截后执行的真正逻辑,我们一会要分析的也是过滤器里面这一组回调实例!
  4. 返回最终的增强器!

刚刚也说了,我们需要重点关注的是这一组拦截方法,我们进入到拦截器里面,找到对应的回调实例!

CALLBACK_FILTER:常量对应的是一个过滤器,我们看它如何实现的:

private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

那么此时 CALLBACKS 就是我么要找的回调方法,点进去可以看到:

// 要使用的回调。请注意,这些回调必须是无状态的。
private static final Callback[] CALLBACKS = new Callback[] {
//这个是真正能够Bean方法多次调用返回的是一个bean实例的实际拦截方法,这个拦截器就是完全能够说明,为什么多次调用只返回
//一个实例的问题
new BeanMethodInterceptor(),
//拦截 BeanFactoryAware 为里面的 setBeanFactory 赋值
//刚刚也说了,增强类会最终实现 BeanFactoryAware 接口,这里就是拦截他的回调方法 setBeanFactory方法,获取bean工厂!
new BeanFactoryAwareMethodInterceptor(),
//这个说实话 真魔幻 我自己实现cglib的时候一直在报错 报一个自己抛出的异常,异常原因是没有处理object里面的eques等
//方法,这个就是为了处理那些没有被拦截的方法的实例 这个些方法直接放行
//这个实例里面没有实现任何的东西,空的,代表着不处理!
NoOp.INSTANCE
};

具体里面每一个拦截器究竟是干嘛的,注释说的很明白,我们从第二个说起!为什么不从第一个呢?第一个比较麻烦,我们由浅入深,逐步的说!

BeanFactoryAwareMethodInterceptor

/**

  • 拦截对任何{@link BeanFactoryAware#setBeanFactory(BeanFactory)}的调用 {@code @Configuration}类实例,用于记录{@link BeanFactory}。
  • @see EnhancedConfiguration
    */
    private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {

@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]);

// 实际的(非CGLIB)超类是否实现BeanFactoryAware?
// 如果是这样,请调用其setBeanFactory()方法。如果没有,请退出。
//如果用户类(也就是你自己定义的类)自己实现了该接口,那么别担心,也会给你赋值上
if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
return proxy.invokeSuper(obj, args);
}
return null;
}

/**

  • 执行到setBeanFactory(xxx)方法时匹配成功
  • @param candidateMethod 当前执行的方法
  • @return
    */
    @Override
    public boolean isMatch(Method candidateMethod) {
    //判断方法是不是 setBeanFactory 方法
    return isSetBeanFactory(candidateMethod);
    }

…忽略不必要逻辑…
}

不知道你注意没有,在最终生成的代理配置类里面有一个 $$beanFactory属性,这个属性就是在这里被赋值的!再把图片放出来,看最后一个属性!

这个拦截器的主要作用:

  1. 拦截 setBeanFactory方法,为 $$beanFactory赋值!

好了,这个拦截器介绍完了,功能大家也记住了,那么,我们分析下一个拦截器,这个是重点!

BeanMethodInterceptor

/**

  • 增强{@link Bean @Bean}方法以检查提供的BeanFactory中的 这个bean对象的存在。
  • @throws Throwable 作为所有在调用时可能引发的异常的统筹 代理方法的超级实现,即实际的{@code @Bean}方法
  • 当该方法经过匹配成功后 会进入到这个拦截方法 这个是解决bean方法只被创建一次的重要逻辑
    */
    @Override
    @Nullable
    public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
    MethodProxy cglibMethodProxy) throws Throwable {
    //通过反射,获取到Bean工厂。也就是 $$beanFactory 这个属性的值
    //也就是上一个拦截器被注入的值
    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
    //拿到Bean的名称
    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

// 确定此bean是否为作用域代理
//方法头上是否标注有@Scoped注解
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
。。。。。。忽略与本题无关的代码。。。。。。。。。。

// 检查给定的方法是否与当前调用的容器相对应工厂方法。
// 比较方法名称和参数列表来确定是否是同一个方法
// 怎么理解这句话,参照下面详解吧
//在整个方法里面,我认为这个判断是核心,为什么说他是核心,因为只有这个判断返回的是false的时候他才会真正的走增强的逻辑
//什么时候会是false呢?
//首先 spring会获取到当前使用的方法 其次会获取当前调用的方法,当两个方法不一致的时候会返回false
//什么情况下胡不一致呢?
//当在bean方法里面调用了另一个方法,此时当前方法和调用方法不一致,导致返回课false然后去执行的增强逻辑
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 这是个小细节:若你@Bean返回的是BeanFactoryPostProcessor类型
// 请你使用static静态方法,否则会打印这句日志的~~~~
// 因为如果是非静态方法,部分后置处理失效处理不到你,可能对你程序有影像
// 当然也可能没影响,所以官方也只是建议而已~~~
if (logger.isInfoEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
… 忽略日志打印…
}
// 这表示:当前方法,就是这个被拦截的方法,那就没啥好说的
// 相当于在代理代理类里执行了super(xxx);
// 但是,但是,但是,此时的this依旧是代理类
//这个事实上上调用的是本身的方法 最终会再次被调用到下面的 resolveBeanReference 方法
//这里的设计很奇妙 为什么这么说呢?
//了解这个方法首先要对cglib有一个基础的认识 为什么这么说呗?
//首先要明白 cglib是基于子类集成的方式去增强的目标方法的
//所以在不进行增强的时候就可以以很轻松的调用父类的原始方法去执行实现
//当前调用的方法和调用的方法是一个方法的时候 就直接调用cglib父类 也就是原始类的创建方法直接创建
//当不一样的时候 会进入到下面的方法 直接由beanFactory返回 精妙!!
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//方法里调用的实例化方法会交给这里来执行
//这一步的执行是真正的执行方式,当发现该方法需要代理的时候不调用父类的原始方法
//而是调用我需要代理的逻辑去返回一个对象,从而完成对对象的代理
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

总结

无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。

最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。

面试了阿里,滴滴,网易,蚂蚁,最终有幸去了网易【面试题分享】

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
转存中…(img-3keXxey2-1713110513310)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-bVqO8elj-1713110513310)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值