一、Spring启动入口ContextLoadListener

本文探讨了Spring在Web容器中的启动过程,重点关注ContextLoaderListener如何在web.xml配置中作为监听器启动,并在WEB容器初始化时创建WebApplicationContext。文章详细介绍了ContextLoaderListener的contextInitialized()方法以及ContextLoader类中的initWebApplicationContext()方法,阐述了Spring IOC容器的启动流程,包括创建根上下文、加载父上下文、配置和刷新上下文以及将上下文存储到ServletContext中的过程。
摘要由CSDN通过智能技术生成

一、Spring与WEB容器整合   

web项目中,Spring启动是在web.xml配置监听器,如下所示: 

<!-- 配置Spring上下文监听器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

 
  可以看看ContextLoaderListener类,它实现了Tomcat容器的ServletContextListener接口,所以它与普通的Servlet监听是一样的。同样是重写到两个方法:contextInitialized()方法在web容器初始化时执行,contextDestroyed()方法在容器销毁时执行。

    WEB容器启动时会触发初始化事件,ContextLoaderListener监听到这个事件,其contextInitialized()方法会被调用,在这个方法中Spring会初始化一个根上下文,即WebApplicationContext。这是一个接口,其实际默认实现类是XmlWebApplicationContext。这个就是Spring IOC的容器,其对应bean定义的配置信息由web.xml中的context-param来指定

<!-- 配置Spring配置文件路径 -->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath*:applicationContext.xmlclasspath*:applicationContext-shiro.xml
        </param-value>
</context-param>

 在Spring IOC 容器启动初始化完毕之后,会将其储存到ServletContext中。形式如下:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, WebApplicationContext context);



 
注:以前我们写Servlet程序的时候,如果在Tomcat容器启动的时候需要绑定一些参数或者做一些全局处理,那么就需要实现ServletContextListener接口,在contextInitialized方法中编写必要的代码。然后在web.xml添加配置listener

 在ContextLoaderListener类中,只是实现了ServletContextListener提供的到两个方法,Spring启动主要的逻辑在父类ContextLoader的方法initWebApplicationContext实现。ContextLoaderListener的作用就是启动web容器时自动装配ApplicationContext的配置信息。更细化一点讲,Spring的启动过程其实就是Spring IOC容器的启动过程。

 

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	/**
	 * Initialize the root web application context.
	 */
	@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());
	}
}

 

 二、ContextLoader剖析

  从上一部分ContextLoaderListener类可以得知,ContextLoader实际执行Spring容器的初始化,Spring整个的配置工作都是在ContextLoader完成的,这里参数ServletContextEvent由web容器提供,不做说明。ContextLoaderListener很好理解,所以我们主要看ContextLoader类。用Maven引入Spring的源码,打开ContextLoader类,类注释的第一行就是

/**
 * Performs the actual initialization work for the root application context.
  ......
**/

 用google翻译:实际执行根应用上下文的初始化工作。这里的根应用上下文就是上文所写的WebApplicationContext。我们先看看ContextLoader的时序图

 ContextLoader类initWebApplicationContext()方法

/**
 * Initialize Spring's web application context for the given servlet context,
 * using the application context provided at construction time, or creating a new one
 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
 * @param servletContext current servlet context
 * @return the new WebApplicationContext
 * @see #ContextLoader(WebApplicationContext)
 * @see #CONTEXT_CLASS_PARAM
 * @see #CONFIG_LOCATION_PARAM
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	//判断ServletContext是否已经存在WebApplication,如果存在则抛出异常
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException(
				"Cannot initialize context because there is already a root application context present - " +
				"check whether you have multiple ContextLoader* definitions in your web.xml!");
	}

	Log logger = LogFactory.getLog(ContextLoader.class);
	servletContext.log("Initializing Spring root WebApplicationContext");
	if (logger.isInfoEnabled()) {
		logger.info("Root WebApplicationContext: initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {
		// Store context in local instance variable, to guarantee that
		// it is available on ServletContext shutdown.
		if (this.context == null) {
			//创建WebApplicationContext(下文有说明)
			this.context = createWebApplicationContext(servletContext);
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// 得到根上下文的父上下文,然后设置到根上下文 。一般的web项目parent为空
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				//从web.xml加载参数,初始化跟上下文WebApplicationContext,创建bean工厂和bean对象。
				//这个过程比较麻烦,下一篇文章专门分析
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if (ccl == ContextLoader.class.getClassLoader()) {
			currentContext = this.context;
		}
		else if (ccl != null) {
			currentContextPerThread.put(ccl, this.context);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
		}
		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
		}

		return this.context;
	}
	catch (RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
	catch (Error err) {
		logger.error("Context initialization failed", err);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
		throw err;
	}
}

     

    在这个方法中ServletContext是由web容器监听器(ContextLoaderListener)提供。首先判断servlectContext中是否已经存在根上下文,如果存在,则抛出异常;否则通过createWebApplicationContext方法创建新的根上下文。然后通过loadParentContext()方法为其设置父上下文。再通过configureAndRefreshWebApplicationContext为根上下文构建bean工厂和bean对象。 最后把上下文存入servletContext,并且存入currentContextPerThread。至此初始化过程完毕,接下来可以获取WebApplicationContext,进而用getBean("bean name")得到bean。

 

createWebApplicationContext()方法用来创建根上下文:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
	//从web.xml配置的contextClass参数中获取上下文类名,如果contextClass为空,则使用默认的。
        //下文有说明
	Class<?> contextClass = determineContextClass(sc);
        //根上下文必须是ConfigurableWebApplicationContext的子类,否则抛出异常
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
				"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
	}
	//BeanUtils.instantiateClass工具方法,根据类名创建类
	return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
 

determineContextClass()方法返回根上下文的类名

protected Class<?> determineContextClass(ServletContext servletContext) {
	//从web.xml获得参数contextClass,在一般的web项目中,此参数为null
	String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
	if (contextClassName != null) {
		try {
			return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException(
					"Failed to load custom context class [" + contextClassName + "]", ex);
		}
	}
	else {
		//获得根上下文WebApplicationContext的默认实现类的类名,defaultStrategies是Properties类型,
		//在CotnextLoader类开头static语句块中初始化
		contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
		try {
			return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException(
					"Failed to load default context class [" + contextClassName + "]", ex);
		}
	}
}
 

WebApplicationContext默认实现类的类名获取

static {
	try {
		//获取当前包下面的ContextLoader.properties文件,文件内容是:
		//org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
		ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	}
	catch (IOException ex) {
		throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
	}
}
 
 
 
Spring Boot项目的业务流程可以大致分为以下几个步骤: 1. 启动应用程序:在Spring Boot项目中,通常以一个启动类为入口,通过调用`SpringApplication.run(Application.class, args)`来启动应用程序。这个启动类使用了`@SpringBootApplication`注解,该注解是一个组合注解,包含了`@Configuration`、`@EnableAutoConfiguration`和`@ComponentScan`等注解,用于自动配置Spring和扫描组件。 2. 创建控制器(Controller):控制器负责处理HTTP请求和响应。在Spring Boot项目中,可以使用`@RestController`注解标识一个控制器类,同时可以使用`@RequestMapping`注解来指定请求的URL路径。 3. 创建服务层(Service):服务层是业务逻辑的核心,负责处理各种业务逻辑和事务。在Spring Boot中,可以使用`@Service`注解来标识一个服务层组件。 4. 创建数据访问层(Repository):数据访问层负责与数据库进行交互,包括数据的增删改查等操作。在Spring Boot中,可以使用`@Repository`注解来标识一个数据访问层组件,并通过Spring Data JPA或其他ORM框架来简化数据库操作。 5. 创建实体类(Entity):实体类表示数据库中的一张表或集合,在Spring Boot项目中,可以使用`@Entity`注解来标识一个实体类,并通过注解来定义表名、字段等属性。 6. 创建数据传输对象(DTO):数据传输对象用于在不同层之间传递数据,在Spring Boot项目中,可以使用普通的Java类作为数据传输对象。 7. 进行业务逻辑处理:在服务层中,根据需求实现具体的业务逻辑处理,包括数据的查询、计算、验证等操作。 8. 处理HTTP请求和响应:在控制器中,根据请求的URL路径和参数调用相应的服务层方法,并将处理结果通过HTTP响应返回给客户端。 这样,一个简单的Spring Boot项目的业务流程就完成了。当然,具体的业务流程会根据项目需求的复杂性而有所不同,但以上步骤可以作为一个通用的参考。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [SpringBoot基础流程](https://blog.csdn.net/BASK2311/article/details/130599214)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [SpringBoot 开发业务逻辑基本流程](https://blog.csdn.net/QQQZP/article/details/130402547)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值