> 关注微信公众号「面向八股文编程」,更多技术文章、面试宝典更新不停
目录
2.1.1入口1:ClassPathXmlApplicationContext
2.1.1入口2:AnnotationConfigApplicationContext
前言
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.总结
BeanDefinition是对bean的详细描述,里面提供了比Class更丰富的bean信息
FactoryBean是spring中用于构造复杂bean的一种方式,其实我们@Bean注解就是使用它来完成bean的创建的
ObjectFactory是一个自定义bean工厂,它在bean的生命周期中频繁出现,它和FactoryBean最大的区别在于:
(1)FactoryBean的getObject()方法由spring帮助我们调用,不需要我们主动调用就可以注入到beanFactory中
(2)ObjectFactory必须由我们主动调用getObject()才能获取其中定义的bean,spring不会将其注入到beanFactory中
spring常见的启动入口有两个,分别是xml入口和注解入口,但不论是哪个入口,最终都要调用refresh()方法刷新ioc容器
本来想着这篇文章还能把refresh()方法写完,现在看起来需要等到下一篇文章了
码字不易,如果觉得我写得不错,就给我点个赞!
欢迎关注我的公众号,微信搜索
【面向八股文编程】
面经、技术文章更新不停