工作三年,小胖问我 Spring 是怎么托管 Bean 的?真的菜!

简介: 工作三年,小胖问我 Spring 是怎么托管 Bean 的?真的菜!

Spring 相信 Java 程序员都很熟悉,甚至于有人说 Java 开发就是面向 Spring 开发。由此可见,Spring 在 Java 领域的地位是举足轻重的。


Spring 有很多模块,常用的有 spring-core、spring-beans、spring-aop、spring-context、spring-expression 以及 spring-test 等,模块太多,不可能一次聊完。Bean 的概念在 Spring 中是非常重要的。这篇狗哥先聊聊 Bean 相关的内容。


面试中常问 Bean 的注册方式、作用域、同名 Bean、Bean 的生命周期等等问题。


Bean 的注册方式


Spring 中 Bean 的注册方式有三种:


  • XML 配置文件的注册方式
  • Java 注解的注册方式
  • Java API 的注册方式


XML 方式


这种方式已经不常用了,原因是维护过于繁琐。


<bean id="user" class="com.nasus.spring.beans.User">
 <property name="id" value="1"/>
 <property name="name" value="Spring"/>
</bean>


如上面代码所示,只需要指定注入的类以及类下定义的属性即可。


注解方式


用 Java 注解方式现在很常见,基本都是这种方式。注解又分为两种方式:@Component 和 @Bean 方式。


@Component 方式注册 Bean,代码如下:


@Component
public class User {
    private Integer id;
    private String name
    // 忽略其他方法
}


@Bean 方式注册 Bean,常与 @Configuration 结合使用。**@Configuration 可理解为 XML 配置里的标签,而 @Bean 可理解为用 XML 配置里面的标签。** 代码如下:


@Configuration
public class User {
    @Bean
    public User user() {
        return new User();
    }
    // 忽略其他方法
}


你说到这里面试官肯定会问 @Component 和 @Bean 二者有啥区别。区别就在于:「如果想将第三方的类变成组件,没有源代码,也就没办法使用 @Component 进行自动配置,这时就可以使用 @Bean (当然,也可以用 XML 方式)。」 比如下面的代码:


@Configuration
public class WireThirdLibClass {
    // 假设 ThirdLibClass 是第三方库中的类,我们没源码
    @Bean
    public ThirdLibClass getThirdLibClass() {
        return new ThirdLibClass();
    }
}


API 方式


这种方式用的很少,实现容易出错,代码写起来也繁琐,增加了维护的时间成本。代码如下:


public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        RootBeanDefinition personBean = new RootBeanDefinition(User.class);
        // 新增 Bean
        registry.registerBeanDefinition("user", userBean);
    }
}


Bean 的作用域


一共 5 个:


作用域 描述 用法
singleton Spring 默认的作用域,单例作用域。表示在 Spring 中只会有一个 Bean 实例 默认
prototype 原型作用域,每次调用 Bean 都会新建一个。多线程场景下常用。 类上加 @Scope ("prototype")
request 该作用域将 bean 的定义限制为 HTTP 请求。只在 web-aware Spring ApplicationContext 的上下文中有效。 类上加 @Scope (WebApplicationContext.SCOPE_REQUEST)
session 该作用域将 bean 的定义限制为 HTTP 会话。只在 web-aware Spring ApplicationContext 的上下文中有效。 类上加 @Scope (WebApplicationContext.SCOPE_SESSION)
global-session 该作用域将 bean 的定义限制为全局 HTTP 会话。只在 web-aware Spring ApplicationContext 的上下文中有效。 类上加 @Scope (WebApplicationContext.SCOPE_APPLICATION)


怎么解决同名 Bean 的问题?


Spring 对同名 Bean 的处理分两种情况:


  • 同一个 Spring 配置文件中 Bean 的 id 和 name 是不能够重复的,否则 Spring 容器启动时会报错
  • 要是不同配置文件,id 和 name 允许重复。Spring 处理规则是后引入的覆盖前面引用的。


所以,我们自己定义的时候尽量使用长命名方式,避免重复。


Bean 的生命周期


我们知道 getBean () 是 Bean 对象的入口,它属于 BeanFactory 接口,而它的真正实现是 AbstractAutowireCapableBeanFactory 的 createBean () 方法,最终调用的是 doCreateBean (),源码如下:


@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating instance of bean '" + beanName + "'");
    }
    RootBeanDefinition mbdToUse = mbd;
    // 确定并加载 Bean 的 class
    Class < ? > resolvedClass = resolveBeanClass(mbd, beanName);
    if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
        mbdToUse = new RootBeanDefinition(mbd);
        mbdToUse.setBeanClass(resolvedClass);
    }
    // 验证以及准备需要覆盖的方法
    try {
        mbdToUse.prepareMethodOverrides();
    } catch (BeanDefinitionValidationException ex) {
        throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
            beanName, "Validation of method overrides failed", ex);
    }
    try {
        // 给 BeanPostProcessors 一个机会来返回代理对象来代替真正的 Bean 实例,在这里实现创建代理对象功能
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
            return bean;
        }
    } catch (Throwable ex) {
        throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
            "BeanPostProcessor before instantiation of bean failed", ex);
    }
    try {
        // 创建 Bean
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        if (logger.isTraceEnabled()) {
            logger.trace("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
        throw ex;
    } catch (Throwable ex) {
        throw new BeanCreationException(
            mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
    }
}


doCreateBean 方法的源码如下:


protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
    // 实例化 bean,BeanWrapper 对象提供了设置和获取属性值的功能
    BeanWrapper instanceWrapper = null;
    // 如果 RootBeanDefinition 是单例,则移除未完成的 FactoryBean 实例的缓存
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        // 创建 bean 实例
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    // 获取 BeanWrapper 中封装的 Object 对象,其实就是 bean 对象的实例
    final Object bean = instanceWrapper.getWrappedInstance();
    // 获取 BeanWrapper 中封装 bean 的 Class
    Class < ? > beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }
    // 应用 MergedBeanDefinitionPostProcessor 后处理器,合并 bean 的定义信息
    // Autowire 等注解信息就是在这一步完成预解析,并且将注解需要的信息放入缓存
    synchronized(mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            } catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                    "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
        }
        // 为了避免循环依赖,在 bean 初始化完成前,就将创建 bean 实例的 ObjectFactory 放入工厂缓存(singletonFactories)
        addSingletonFactory(beanName, () - > getEarlyBeanReference(beanName, mbd, bean));
    }
    // 对 bean 属性进行填充
    Object exposedObject = bean;
    try {
        populateBean(beanName, mbd, instanceWrapper);
        // 调用初始化方法,如 init-method 注入 Aware 对象
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        } else {
            throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }
    if (earlySingletonExposure) {
        // 如果存在循环依赖,也就是说该 bean 已经被其他 bean 递归加载过,放入了提早公布的 bean 缓存中
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 如果 exposedObject 没有在 initializeBean 初始化方法中被增强
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                // 依赖检测
                String[] dependentBeans = getDependentBeans(beanName);
                Set < String > actualDependentBeans = new LinkedHashSet < > (dependentBeans.length);
                for (String dependentBean: dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                // 如果 actualDependentBeans 不为空,则表示依赖的 bean 并没有被创建完,即存在循环依赖
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName,
                        "Bean with name '" + beanName + "' has been injected into other beans [" +
                        StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                        "] in its raw version as part of a circular reference, but has eventually been " +
                        "wrapped. This means that said other beans do not use the final version of the " +
                        "bean. This is often the result of over-eager type matching - consider using " +
                        "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }
    try {
        // 注册 DisposableBean 以便在销毁时调用
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    } catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }
    return exposedObject;
}


从上述源码中,doCreateBean 方法,首先调用 createBeanInstance 方法 对 Bean 进行了实例化,该方法返回 BeanWrapper 对象。而 BeanWrapper 对象是 Spring 中一个基础的 Bean 结构接口,说它是基础接口是因为它连基本的属性都没有。


BeanWrapper 接口有一个默认实现类 BeanWrapperImpl,其主要作用是对 Bean 进行填充,比如填充和注入 Bean 的属性等。


完成实例化并设置完属性 & 依赖后,调用 Bean 的 initializeBean 初始化方法。初始化第一步是检查当前 Bean 对象是否实现了 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware 等接口,源码如下:


private void invokeAwareMethods(final String beanName, final Object bean) {
    if (bean instanceof Aware) {
  // 设置 beanName
        if (bean instanceof BeanNameAware) {
            ((BeanNameAware) bean).setBeanName(beanName);
        }
  // 注入当前 Bean 对象相应的 ClassLoader
        if (bean instanceof BeanClassLoaderAware) {
            ClassLoader bcl = getBeanClassLoader();
            if (bcl != null) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
            }
        }
  // 将 BeanFactory 容器注入到当前对象实例,使当前对象拥有 BeanFactory 容器的引用
        if (bean instanceof BeanFactoryAware) {
            ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
        }
    }
}


第二步是 BeanPostProcessor 增强处理,它主要对 Spring 容器中的 Bean 实例对象进行扩展,允许 Spring 在初始化 Bean 阶段对其进行定制化修改,比如为其提供代理实现等等。


前置处理完事之后,检查和执行 InitializingBean 和 init-method 方法。


其中,InitializingBean 是个接口,里面有 afterPropertiesSet 方法。在 Bean 初始化时会判断 bean 是否实现了 InitializingBean,是则调用  afterPropertiesSet 进行初始化;再检查


protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd) throws Throwable {
    // 判断当前 Bean 是否实现了 InitializingBean,如果是的话需要调用 afterPropertiesSet()
    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        // 安全模式
        if (System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged((PrivilegedExceptionAction < Object > )() - > {
                    ((InitializingBean) bean).afterPropertiesSet(); // 属性初始化
                    return null;
                }, getAccessControlContext());
            } catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        } else {
            // 属性初始化
            ((InitializingBean) bean).afterPropertiesSet(); 
        }
    }
    // 判断是否指定了 init-method()
    if (mbd != null && bean.getClass() != NullBean.class) {
        String initMethodName = mbd.getInitMethodName();
        if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
            // 利用反射机制执行指定方法
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}


完成以上工作就可以使用 Bean 对象了,在 Spring 容器关闭时执行销毁方法,但是 Spring 不会自动调用,需要我们主动调用。


「如果是 BeanFactory 容器,我们需要主动调用 destroySingletons () 方法,通知 BeanFactory 容器去执行相应的销毁方法;如果是 ApplicationContext 容器,我们需要主动调用 registerShutdownHook () 方法,告知 ApplicationContext 容器执行相应的销毁方法」


巨人的肩膀



总结


这章聊了 Bean 的三种注册方式、五个作用域、以及同名问题的解决方法,最后还通过源码把 Bean 的生命周期走了一遍。关于生命周期的验证,之前在《Spring Bean 的生命周期》一文中写过,有兴趣的小伙伴可以看下。它的生命周期流程图如下:


640.png

相关文章
|
20天前
|
XML 安全 Java
|
2天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
18 6
|
4天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
32 3
|
4月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
18天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
27 1
|
2月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
80 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
3月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
316 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
2月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
3月前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
248 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
下一篇
DataWorks