注:文章内容有点多,可以先总体大概浏览一遍,心中有个整体类目关系构图,再详细看,不至于迷失在细节中,最好本地也使用开发工具打开源码对着看。
引言:
Spring框架已经成为目前JavaEE企业应用的主流框架,它提供了一个全面的编程和配置模型,适用于任何类型的部署平台。
Spring的一个关键元素是应用程序级别的基础设施支持:Spring专注于企业应用程序的“管道”,这样我们开发人员就可以专注于应用程序的业务逻辑开发,而不用关注于底层对象的管理与环境配置等。
说到Spring,我们基本都能说出它的两大特性:IoC、AOP,但底层的实现原理、启动流程等就不一定有所深入了解。
Spring 特性:
- 核心技术:依赖注入、事件、资源、i18n、验证、数据绑定、类型转换、SpEL、AOP。
- 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
- 数据访问:事务、DAO支持、JDBC、ORM、编组XML。
- Spring MVC和Spring WebFlux web框架。
- 集成:远程调用、JMS、JCA、JMX、电子邮件、任务、调度、缓存。
- 语言:Kotlin、Groovy、动态语言。
Spring架构:
Spring框架是一个分层架构,总共包含20多个模块,包含一系列的功能要素,由 1300 多个不同的文件构成。所有组件被分别整合在核心容器(Core Container)
、 AOP(Aspect Oriented Programming)
、Aspects(切面)、设备支持(Instrmentation)
、数据访问及集成(Data Access/Integeration)
、 Web
、 报文发送(Messaging)
、 Test 这
8个模块集合中,Spring 架构图如下:
注:篇幅有限,这块内容也不是本文重点,每一个架构模块,可参考其他资料。
下面将结合源码分析Spring的启动流程:
Spring的启动是建立在servlet/web容器(Tomcat、JBoss、Jetty等)之上的,一个常规的Spring应用,在web容器启动时,默认会先去加载/WEB-INF/web.xml,它配置了:servletContext上下文、监听器(Listener)、过滤器(Filter)、Servlet等。
常规的web.xml示例:
<!--spring资源上下文定义,在指定路径加载spring的xml配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!--spring的上下文监听器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--编码过滤器,防止请求中文乱码-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Springmvc的核心控制器,DispatcherServlet请求分发器-->
<servlet>
<servlet-name>dispatchServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatchServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
web.xml 加载顺序为: context-param < listener < filter < servlet
当然,也可以自定义自己的监听器、过滤器、拦截器等。这里只是截取主要的常用配置,下面将结合web.xml详细介绍它的启动流程。
一、ServletContext上下文
JavaEE标准规定,servlet容器需要在应用项目启动时,给应用程序初始化一个ServletContext作为公共环境容器存放公共信息,ServletContext中的信息都是由容器提供的。servlet规范当中,使用了Listener监听器机制来进行web容器相关组件的生命周期管理以及Event事件监听器来实现组件之间的交互。
其中一个重要的生命周期监听器是 ServletContextListener 。web容器在创建和初始化 ServletContext 的时候,会产生一个ServletContextEvent 事件,其中 ServletContextEvent 包含该 ServletContext 的引用。然后交给在web.xml中配置的,注册到这个ServletContext 的监听器 ServletContextListener。ServletContextListener 在其 contextInitialized 方法中定义处理逻辑,源码如下:
package javax.servlet;
import java.util.EventListener;
public interface ServletContextListener extends EventListener {
void contextInitialized(ServletContextEvent var1);
void contextDestroyed(ServletContextEvent var1);
}
从 contextInitialized 方法的注释可知:通知所有的ServletContextListeners,当前的web应用正在启动,而且这些ServletContextListeners是在Filters和Servlets创建之前接收到通知的。所以在这个时候,web应用还不能接收请求,故可以在这里完成底层处理请求的组件的加载,这样等之后接收请求的Filters和Servlets创建时,则可以使用这些创建好的组件了。spring相关的bean就是这里所说的底层处理请求的组件,如数据库连接池,数据库事务管理器等。
举例:
通过自定义contextListener获取web.xml中配置的参数,需要实现 ServletContextListener 接口。
1.容器启动时,优先会加载web.xml 配置文件<context-param>标签中的内容,作为键值对放到ServletContext中。
2.然后找到对应的<listener>标签 ,容器调用指定的监听器的 contextInitialized(ServletContextEvent event) 方法,执行其中的操作。
例如:在web.xml中配置
<context-param>
<param-name>key</param-name>
<param-value>value</param-value>
</context-param>
<listener>
<listener-class>com.stwen.spring.listener.MyContextListener</listener-class>
</listener>
其中,MyContextListener 是自定义实现ServletContextListener 接口的类:
package com.stwen.spring.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyContextListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent event) {
System.out.println("===========销毁自定义的监听器:MyContextListener=============");
}
@Override
public void contextInitialized(ServletContextEvent event) {
System.out.println("===========初始化自定义的监听器:MyContextListener===========");
ServletContext servletContext = event.getServletContext();
System.out.println("key:"+servletContext.getInitParameter("key"));
}
}
web.xml中可以定义两种参数:
- 一个是全局参数(ServletContext),通过<context-param></context-param>定义,如上;
- 一个是servlet参数,通过在servlet中声明:
<init-param>
<param-name>param1</param-name>
<param-value>avalible in servlet init()</param-value>
</init-param>
第一种参数在servlet里面可以通过 getServletContext().getInitParameter("context/param") 得到;
第二种参数只能在Servlet的 init() 方法中通过 this.getInitParameter("param1")取得:
package javax.servlet;
import java.io.IOException;
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}