聊聊Spring的Bean

1、Bean的由来

在JAVA中,Bean是一种组件,一种比较有名的说法是JAVA是咖啡,Bean是咖啡豆,也就是说咖啡豆Bean是JAVA的基础。

JavaBean定义了一种规范,只有满足规范的可称之为Bean,规范具体包括:

1、所有属性必须是private;

2、必须具有一个公共的(public)无参构造函数;

3、private属性必须提供public的getter和setter来给外部访问,并且方法的命名也必须遵循一定的命名规范;

4.是可序列化的,即要实现serializable接口。

JavaBean是一个完整可复用的组件,可以包括属性,方法,和事件,在早期界面编程GUI时是比较常用的。

2、Spring中Bean的概念和作用

在最早的版本中,Spring是被设计用来管理JavaBean的,所以Spring管理的对象会被称为“bean”。当然,现在Spring已经可以管理 任何 对象,即使它不具备默认构造器和设置方法(getter和setter)这些JavaBean的特性。不过“Spring bean”这个术语仍然被保存了下来。

其和普通的Bean最大的区别是其拥有完全不同的生命周期管理。 在传统的Java应用中,bean 的生命周期很简单。使用Java 关键字 new 进行bean 实例化,然后该bean 就可以使用了。一旦该bean 不再被使用,则由Java自动进行垃圾回收。 相比之下,Spring 容器中的 bean 的生命周期就显得相对复杂多了。

Spring的bean都会被封装成一个BeanDefinition,该接口提供了统一的Bean信息管理,比如Bean的作用域,是否懒加载,依赖了哪些对象。

3、Spring Bean的生命周期

在Spring中,Bean是交给IoC容器管理的(单例,prototype是交给客户端使用方管理的,即通过JVM垃圾回收处理),通过容器,在使用时直接通过DI就可引入并使用,这就是常说的控制反转的思想(将对象创建的控制权交给容器,使用者通过依赖注入引入Bean)。

Spring管理Bean有一个完整的生命周期。

1.容器启动后,会对scope为singleton且非懒加载的bean进行实例化,

2.按照Bean定义信息配置信息,注入所有的属性(有一些依赖的属性,同样被加载);

3.如果Bean实现了BeanNameAware接口,会回调该接口的setBeanName()方法,传入该Bean的id,此时该Bean就获得了自己在配置文件中的id,

4.如果Bean实现了BeanFactoryAware接口,会回调该接口的setBeanFactory()方法,传入该Bean的BeanFactory,这样该Bean就获得了自己所在的BeanFactory,

5.如果Bean实现了ApplicationContextAware接口,会回调该接口的setApplicationContext()方法,传入该Bean的ApplicationContext,这样该Bean就获得了自己所在的ApplicationContext,

6.如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation()方法,

7.如果Bean实现了InitializingBean接口,则会回调该接口的afterPropertiesSet()方法,

8.如果Bean配置了init-method方法,则会执行init-method配置的方法,

9.如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessAfterInitialization()方法,

10.经过流程9之后,就可以正式使用该Bean了,对于scope为singleton的Bean,Spring的ioc容器中会缓存一份该bean的实例,而对于scope为prototype的Bean,每次被调用都会new一个新的对象,期生命周期就交给调用方管理了,不再是Spring容器进行管理了。

11.容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法,

12.如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个Bean的生命周期结束。

这里写一个Bean的例子:

public class BeanTest implements ApplicationContextAware, InitializingBean, BeanNameAware, BeanFactoryPostProcessor {

    private ApplicationContext applicationContext;

    private String beanName;

   //BeanNameAware
    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }


    //InitializingBean

    @Override
    public void afterPropertiesSet() throws Exception {
            System.out.println("设置一些初始化操作");
    }

    //BeanFactoryPostProcessor 进行一些额外的修改操作
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            //可以对一些bean进行一些额外操作
            String[] beanNames = beanFactory.getBeanNamesForAnnotation(InvoiceLog.class);
            for (String name:beanNames){
                BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
                Class clz = null;
                try {
                    clz = Thread.currentThread().getContextClassLoader().loadClass(beanDefinition.getBeanClassName());
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
                InvoiceLog invoiceLog = AnnotationUtils.findAnnotation(clz,InvoiceLog.class);
                beanFactory.registerAlias(name,name+"hehe");
            }
    }


   //ApplicationContextAware,可获得应用上下文。

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

上面提到了Bean的单例和prototype,实际上Spring的Bean有不同的作用域:

  1. singleton : bean在每个Spring ioc 容器中只有一个实例。
  2. prototype:一个bean的定义可以有多个实例。
  3. request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
  4. session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
  5. global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

4、BeanFactory的介绍;

上面提到了Bean的生命周期和作用域,那么Bean是如何注入到容器中的,使用时又是如何获得到已经创建好的Bean的呢。

这要借助于BeanFactory以及BeanRegistry。

BeanFactory的继承关系:

目前常用的就是DefaultListableBeanFactory,其也实现了BeanDefinitionRegistry。在SpringBoot中,ApplicationContext继承了BeanFactory,实际上其也使用了DefaultListableBeanFactory来管理Bean。ApplicationContext还提供其他特性,比如事件监听、本地消息处理、资源加载等。

下面是一个简单的注册和使用的例子:

// 默认容器实现
 DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
 // 根据业务对象构造相应的BeanDefinition
 AbstractBeanDefinition definition = new RootBeanDefinition(MyCustomBean.class,true);
 // 将bean定义注册到容器中
 beanFactory .registerBeanDefinition("beanName",definition);


  // 然后可以从容器中获取这个bean的实例
 // 注意:这里的beanRegistry其实实现了BeanFactory接口,所以可以强转,
 // 单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory类型的
 BeanFactory container = (BeanFactory)beanFactory ;
 MyCustomBean customBean = (MyCustomBean)container.getBean("beanName");

上面的代码虽然简单,但完整了说明了Spring的Bean是如何加载到IoC容器的。在Springboot中,框架会扫描我们的packages(invokeBeanPostProcessor内部实现),将所有注解的bean封装成BeanDefinition,并注册到注册器中(map,beanDefinitionNames),注解就包括常用的Controller、Service、Component,Import,Bean等等。

SpringBoot将所有Bean注册后,会进行实例化操作(ApplicationContext会预先实例化所有单例的Bean),Application的refresh最后会调用preInstantiateSingletons进行实例化操作。从源码可以看到,其是根据beanName获取到BeanDefinition(RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);),然后根据BeanDefinition的信息进行实例化操作。实际上在getBean的过程,如果没有实例化,同样是需要根据BeanDefinition的信息去创建Bean。实例化的Bean会放到一个特殊的Map,singleObjects中,这还涉及到另外一个概念,循环依赖。

private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

这里还要提一个概念,FactoryBean,这个是用来创建Bean的Bean,通过该类的getObject可以获得实际的对象。通常用于对实际类做一些代理。下面是一个典型例子:

public class HbnnReferenceFactoryBean implements InitializingBean, ApplicationContextAware, FactoryBean<Object> {

    private ApplicationContext applicationContext;

    private String name;

    private Class<?> type;



    @Override
    public Object getObject() throws Exception {
        return Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return new Object();
            }
        });
    }

    @Override
    public Class<?> getObjectType() {
        return type;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.isTrue(!StringUtil.isEmpty(name),"name不可为空");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext =applicationContext;
    }
}

上面实际上是一个代理,用于对于某些注解的bean进行代理增强,如果想要将FactoryBean注册到容器,当我们使用HbnnReference注解时,自动走代理。就需要扫描所有HbnnReference注解,并重新定义BeanDefinition,将其注册到容器中。

注册逻辑:

    private void registerBean(BeanDefinitionRegistry registry, AnnotationMetadata metadata, Map<String,Object> attributes){
        String className = metadata.getClassName();
        BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(HbnnReferenceFactoryBean.class);
        definitionBuilder.addPropertyValue("type",className);
        definitionBuilder.addPropertyValue("name",(String) attributes.get("name"));
        definitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,className);
        beanDefinition.setPrimary(true);
        String alias = "HbnnReference_" + (String) attributes.get("name");
        BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition,className,new String[]{alias});
        BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder,registry);
    }
}

实际上我们也可以按照上面的原理自定义一些Bean,并将其注册到IoC容器中:

1、扫描包,拿到所有带有对应注解的类;

2、封装成BeanDefinition;

3、注册;

Bean的循环依赖的问题,这是Spring面试都会考的一道题:

Spring只能解决setter注入的单例的循环依赖,对于使用构造器或者prototype类型的Bean,是无法解决循环依赖的。

通过三级缓存。第一级存储的是对外使用的bean对象。第二级缓存存储的是半成品的bean,可能还未初始化。第三级别缓存存储的是objectFactory。

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);


/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

A依赖B,B依赖A,实例创建过程(doCreateBean):

  • A调用doCreateBean开始进行实例化,此时还没有进行属性填充和实例化;
  • 创建A的objectFactory,并将其添加到三级缓存singletonFactories中;
  • 此时需要注入对象B,发现B在一级,二级,三级缓存中都不存在,就会执行doCreateBean;
  • B进行实例化操作,此时也没有进行属性填充和实例化;
  • 创建B的objectFactory,并将其添加到三级缓存singletonFactories中;
  • B开始注入A,发现A在三级缓存中存在,并将其放进二级缓存中,同时删除三级缓存;
  • 将A注入到对象B中,此时A还未进行属性填充和实例化;
  • B对象完成属性填充和实例化,并将B放入一级缓存中;
  • 对象A继续完成属性填充,从一级缓存中得到完成品对象B,完成属性填充和初始化操作;

Bean获取的调用链路:

AbstractBeanFactory.doGetBean ->
DefaultSingletonBeanRegistry.getSingleton ->
AbstractAutowireCapableBeanFactory.createBean ->
AbstractAutowireCapableBeanFactory.doCreateBean ->
DefaultSingletonBeanRegistry.addSingleton

为什么要设计成三级缓存呢?二级缓存能否解决循环依赖的问题?

实际上通过二级缓存同样可以解决循环依赖,三级缓存只是为了延迟代理对象的创建,这样符合spring的设计原则。正常来讲,创建对象Bean时,完成属性填充和初始化之后,才去创建代理对象。但出现循环依赖的情况下,就需要提前创建代理对象。刚开始是创建对象的objectFactory,只有有其他对象注入该Bean的时候,才真正执行objectFactory.get()对象获取对应的代理对象。如果没有循环依赖就不需要提前创建代理对象。

上面提到Spring只解决了setter注入的单例循环依赖,对于构造器注入的无法解决,主要是因为属性注入和构造器注入的时机不同,使用构造器注入时,此时还没有实例化,这也就意味着无法在缓存中获取到Bean,导致一直循环下去。不过我们可以使用Lazy注解延迟加载,从而解决循环依赖的问题。

--------EOF---------
微信分享/微信扫码阅读