《大厂最爱问的Spring》—浅入浅出的IOC启动流程-上

> 关注微信公众号「面向八股文编程」,更多技术文章、面试宝典更新不停

目录

1.铺垫知识

1.1什么是BeanDefinition?

1.2什么是FactoryBean?

1.3什么是Object Factory?

2.ioc启动流程

2.1.1入口1:ClassPathXmlApplicationContext

2.1.1入口2:AnnotationConfigApplicationContext

3.总结


前言

    Spring作为一款最为经典的框架,在工作和面试中出现的频率越来越高,下面我们将按照spring容器的启动过程,带着大家一起过一遍spring源码

    本文中使用的spring版本是基于5.0.7.RELEASE版本

        本文知识点提前预览:

1.铺垫知识

1.1什么是BeanDefinition?

    BeanDefinition是对bean的描述,spring将根据BeanDefinition创建bean对象

    为什么不用class来描述bean呢?因为Class无法完成bean的抽象,比如bean的作用域,bean的注入模型,bean是否是懒加载等等信息,Class是无法抽象出来的,故而需要一个BeanDefinition类来抽象这些信息,以便于spring能够完美的实例化一个bean

     beanDefinition中存放了很多class对象中没有的信息,如:

 
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
      implements BeanDefinition, Cloneable {

    //表示bean的类型,如UserService.class,spring在创建bean的过程中根据此属性通过反射来获取对象
    private volatile Object beanClass;
    //表示bean的作用域,值为singleton代表是单例,为prototype代表是原型,这里SCOPE_DEFAULT    等同于单例
    private String scope = SCOPE_DEFAULT;
    //表示bean是否需要懒加载,默认不需要,原型bean的lazyInit属性无效。

    //1.声明为true时,懒加载的单例bean会在第一次getBean()时生成该bean;

    //2.声明为false时,非懒加载的单例bean会在spring容器创建时直接生成
    private boolean lazyInit = false;
    //声明当前bean依赖于别的bean。所依赖的bean会被容器确保在当前bean实例化之前被实例化
    private String[] dependsOn;
    //为true表示该bean是主bean,spring中一个接口可能有多个bean实现,在进行依赖注入时,    如果找到了多个bean对象,会优先使用主bean进行注入
    private boolean primary = false;
    //指定bean的init-method,即初始化方法
    private String initMethodName;

    ……

}

    由于篇幅原因,这里我们就不贴全部源码了

1.2什么是FactoryBean?

    在spring中,大量的对象都是通过beanFactory工厂生产的。一般情况下,Spring通过反射机制读取配置文件或注解为指定实现类实例化Bean。但在某些情况下,实例化Bean过程比较复杂,如果按照传统方式,则需要在bean中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。

 
public interface FactoryBean<T> {

   /**
    * 实例化bean,将其创建完成后返回
    */
   T getObject() throws Exception;

   /**
    * 获取bean的class类型
    */
   Class<?> getObjectType();

   /**
    * 判断是否为单例模式
    */
   boolean isSingleton();

}

        如果使用FactoryBean创建bean对象,实际上会在ioc容器中创建两个bean对象,一个是我们希望创建的类型为T的bean对象,其id为类型名(首字母小写),另一个对象是FactoryBean对象本身,其beanName为&符加上id。

        如MyFactoryBean extends FactoryBean<MyBean>,会创建myFactoryBean和&myFactoryBean两个bean对象

         只要一个类实现了FactoryBean<T>,并且对其通过注解或者xml等方式生成了FactoryBean<T>实现类的bean(即FactoryBean实现类需要加@Componet注解),那么T就会被创建成为bean对象,并被spring容器管理

1.3什么是Object Factory?

        在我们spring启动过程中还有一个相当重要的接口

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
 }

    这个接口类似于FactoryBean接口。ObjectFactory仅被当作一个普通工厂使用,他的T不会被ioc容器加载为bean对象,想要使用他的T对象就必须通过调用getObject()方法获取,这是和FactoryBean最大的区别。我们在bean生命周期的很多地方都能见到它的身影,比如我们的getSingleton()方法就是通过它去调用createBean()方法的

2.ioc启动流程

2.1ioc的入口

2.1.1入口1:ClassPathXmlApplicationContext

    通过xml配置方式初始化bean的方式,入口为ClassPathXmlApplicationContext

这种方式下Spring 初始化的入口在 ContextLoaderListener,如果项目用了 Spring,通常可以在 web.xml 中找到下面这行代码。

<context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>classpath*:spring/spring-*.xml</param-value>
 </context-param>
<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

    ContextLoaderListener 是 Spring 的入口,而 contextConfigLocation 是 Spring 配置文件的路径

ContextLoaderListener 是实现了 javax.servlet.ServletContextListener 接口的服务器端程序,ContextLoaderListener随 web 应用的启动而启动,只初始化一次,随 web 应用的停止而销毁。在web应用启动的时候会调用 contextInitialized 方法,停止的时候会调用 contextDestroyed 方法

public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
/**
     * 在web应用启动的时候会调用 contextInitialized 方法
     */      
  @Override
    public void contextInitialized(ServletContextEvent event) {
       initWebApplicationContext(event.getServletContext());
    }
 
 
    /**
     * Close the root web application context.
     */
    @Override
    public void contextDestroyed(ServletContextEvent event) {
       closeWebApplicationContext(event.getServletContext());
       ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
 
 }

  进入initWebApplicationContext方法,可以看到如下代码

if (this.context == null) {
    // 创建一个WebApplicationContext并保存到context属性
    this.context = createWebApplicationContext(servletContext);
 }
 if (this.context instanceof ConfigurableWebApplicationContext) {
    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    if (!cwac.isActive()) {
       if (cwac.getParent() == null) {
          ApplicationContext parent = loadParentContext(servletContext);
          cwac.setParent(parent);
       }
//配置并刷新应用上下文,在这个方法里会调用refresh()方法刷新容器
       configureAndRefreshWebApplicationContext(cwac, servletContext);
    }
 }

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
   // 如果应用上下文id是原始默认值,则根据相关信息生成一个更有用的
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 从servletContext中解析初始化参数contextId(可以在web.xml中配置)
       String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
       if (idParam != null) {
          wac.setId(idParam);
       }
       else {
          // 如果idParam为空, 则生成默认的id
          wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                ObjectUtils.getDisplayString(sc.getContextPath()));
       }
    }
 
    wac.setServletContext(sc);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
       wac.setConfigLocation(configLocationParam);
    }
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
       ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }
 
    customizeContext(sc, wac);

  //(重要!)应用上下文的刷新
    wac.refresh();
 }

我们可以看到,xml配置最终调用了refresh()方法

2.1.1入口2:AnnotationConfigApplicationContext

通过注解方式初始化bean,则入口为AnnotationConfigApplicationContext

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
    //主要做了3件事:

  //1.调用父类GenericApplicationContext的无参构造函数,用于创建beanFactory工厂,即DefaultListableBeanFactory

  //2.指定注解配置读取器AnnotatedBeanDefinitionReader

  //3.指定包路径扫描器ClassPathBeanDefinitionScanner

this();

//将传入的【springConfig】配置类注册到容器中,这里只是注册成BeanDefinition,并未实例化
    register(annotatedClasses);

//应用上下文的刷新
    refresh();
  }

}

同样的,注解配置最终也是调用了refresh()

3.总结

  1. BeanDefinition是对bean的详细描述,里面提供了比Class更丰富的bean信息

  2. FactoryBean是spring中用于构造复杂bean的一种方式,其实我们@Bean注解就是使用它来完成bean的创建的

  3. ObjectFactory是一个自定义bean工厂,它在bean的生命周期中频繁出现,它和FactoryBean最大的区别在于:

    (1)FactoryBean的getObject()方法由spring帮助我们调用,不需要我们主动调用就可以注入到beanFactory中

    (2)ObjectFactory必须由我们主动调用getObject()才能获取其中定义的bean,spring不会将其注入到beanFactory中

  4. spring常见的启动入口有两个,分别是xml入口和注解入口,但不论是哪个入口,最终都要调用refresh()方法刷新ioc容器

本来想着这篇文章还能把refresh()方法写完,现在看起来需要等到下一篇文章了

码字不易,如果觉得我写得不错,就给我点个赞

欢迎关注我的公众号,微信搜索

【面向八股文编程】

面经、技术文章更新不停

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值