文章目录
零、前言
Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。那么,在Web应用开发过程中,我们是如何享受到Spring框架带给我们的各种便利的呢?
一、入口
在Servlet开发中,有两个常用入口可以对Servlet框架进行扩展。
- 自定义Listener,用于在Web应用启动时进行业务逻辑处理,可定义多个
- 自定义Servlet,用于在处理请求时整合其他框架,可定义多个
如
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>servletDemoDate</param-name>
<param-value>20200801</param-value>
</context-param>
<!-- 注册自定义listener,可多个 -->
<listener>
<listener-class>com.dfx.servletdemo.listener.DemoListener</listener-class>
</listener>
<!-- 注册自定义servlet,可多个 -->
<servlet>
<servlet-name>demoServlet</servlet-name>
<servlet-class>com.dfx.servletdemo.servlet.DemoServlet</servlet-class>
<init-param>
<param-name>initParam</param-name>
<param-value>demo-111</param-value>
</init-param>
<load-on-startup>4</load-on-startup>
</servlet>
<servlet>
<servlet-name>demo2Servlet</servlet-name>
<servlet-class>com.dfx.servletdemo.servlet.Demo2Servlet</servlet-class>
<init-param>
<param-name>initParam</param-name>
<param-value>demo2-222</param-value>
</init-param>
<load-on-startup>4</load-on-startup>
</servlet>
<!-- 使用tomcat默认servlet default来处理html静态资源 -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!-- 使用tomcat默认servlet 来处理jsp静态资源 -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
<!-- 自定义demoServlet -->
<servlet-mapping>
<servlet-name>demoServlet</servlet-name>
<url-pattern>*.demo</url-pattern>
</servlet-mapping>
<!-- 自定义demoServlet2 -->
<servlet-mapping>
<servlet-name>demo2Servlet</servlet-name>
<url-pattern>*.demo2</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
二、Spring结合Servlet
2.1 结合点
1. Listener、Servlet
web.xml是Java Web项目的常见配置文件,Spring与Servlet的结合点也在Web.xml中。
两个结合点:
- listener
- servlet
Spring框架通过实现Listener、Servlet使得我们可以在基于Servlet框架的Web开发中享受到Spring框架的各种特性。
常见web.xml样例如下:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 配置上下文载入器 -->
<!-- 上下文载入器载入除DispatcherServlet载入的配置文件之外的其他上下文配置文件 -->
<!-- 最常用的上下文载入器是一个Servlet监听器,其名称为ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--配置dispatcher.xml作为mvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<!--load-on-startup 配置一般是大于0的数值,表示启动顺序。 如果相同,则表明这两个Servlet启动顺序随意。 -->
<!-- 不配置该参数时,默认懒加载,只有收到请求时才会实例化DispatcherServlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
说明:
标签声明的是ServletContextListener的实现类ContextLoaderListener,用于监听ServletContext的生命周期变化。在ServletContext启动(即应用启动)时,ContextLoaderListener负责加载Spring容器;在ServletContext关闭(即应用销毁)时,ContextLoaderListener将销毁Spring容器。
标签声明的是Servlet的实现类DispatcherServlet。在Servlet加载时,DispatcherServlet#init方法将加载启动Spring容器,使得我们可以使用Spring中的Bean来处理请求。当然,是否生效也与当前Servlet配置的路径映射有关。
ContextLoaderListener、DispatcherServlet类将Spring容器与Servlet框架相结合,使我们可以按照Spring规范来方便的开发Web应用。
2. Filter
Filter也是Servlet框架中常用的类,用于在请求到达Servlet前,对请求进行一些预处理操作;或者对Servlet的响应做一些后处理操作。
A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.
在Servlet框架中,对请求的处理是一个栈型结构,如下:
在Filter中,也可以使用Spring容器的bean管理功能。
配置样例如下:
<!-- 使用Springbean的filter-->
<!-- DelegatingFilterProxy类遵循filter-name-bean的原则,会根据web.xml中filter-name的值查找到spring配置文件中id与filter-name相同的值,然后把接受到的处理信息传递给相对应的类处理。-->
<filter>
<filter-name>springFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 如果要保留Filter原有的init,destroy方法的调用,还需要配置初始化参数targetFilterLifecycle为true,该参数默认为false. -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>springFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
三、ContextLoaderListener
ContextLoaderListener的类层次结构如下。
Spring通过自定义ContextLoaderListener类实现了Servlet框架中的ServletContextListener接口。
在将ContextLoaderListener配置到web.xml中后。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener对应的方法就可以在ServletContext启动、关闭时被调用到。
3.1 #contextInitialized
/**
*
* 初始化web应用上下文
*
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
作用:在ServletContext初始化(即Web应用启动时)后,初始化Spring的WebApplicationContext。
3.2 #contextDestroyed
/**
*
* 关闭web应用上下文
*
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
作用:在销毁ServletContext(关闭Web应用)时,关闭Spring的WebApplicationContext。
3.3 #contextInitialized时序图
这里重点关注下,Spring容器是如何引入到基于Servlet的Web应用开发中的,ContextLoaderListener#contextInitialized方法的时序图如下:
3.4 分析
ContextLoaderListener实现了ServletContextListener接口。作为一个ServletContextListener,受Servlet容器的管理,使得我们可以在Web应用启动时,初始化Spring容器,便于后期使用。
四、DispatcherServlet
DispatcherServlet的类层次结构简图如下:
Spring通过DispatcherServlet类扩展了Servlet框架中的HttpServlet抽象类。
4.1 DispatcherServlet初始化
Servlet的初始化入口为 Servlet#init方法,让我们来看下DispatcherServlet的初始化过程。
4.1.1 #init(ServletConfig)
实际代码位置:DispatcherServlet父类GenericServlet#init(ServletConfig)
/**
* Called by the servlet container to indicate to a servlet that the
* servlet is being placed into service. See {@link Servlet#init}.
*
* <p>This implementation stores the {@link ServletConfig}
* object it receives from the servlet container for later use.
* When overriding this form of the method, call
* <code>super.init(config)</code>.
*
* @param config the <code>ServletConfig</code> object
* that contains configuration
* information for this servlet
*
* @exception ServletException if an exception occurs that
* interrupts the servlet's normal
* operation
*
* @see UnavailableException
*/
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
作用:通过属性config保存了#init方法中传入的ServletConfig实例。提供了无参#init方法,供子类扩展。
4.1.2 #init()
实际代码位置:DispatcherServlet父类HttpServletBean#init()
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
作用:
- 将DispatcherServlet的配置参数,以属性方式存储到DispatcherServlet实例中;
- 提供#initServletBean方法,留给子类扩展;
4.1.3 #initServletBean
实际代码位置:DispatcherServlet父类FrameworkServlet#initServletBean()
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
作用:
- initWebApplicationContext:初始化WebApplicationContext实例;
- 提供initFrameworkServlet方法,供子类扩展;
4.2 DispatcherServlet#init(ServletConfig)时序图
在DispatcherServlet#init中创建Spring容器的详细图如下:
4.3 分析
在Servlet框架中,Servlet是用于处理Web客户端请求,并给出响应的。
Spring通过DispatcherServlet实现了Servlet接口,并在#init(ServletConfig)方法中创建了Spring WebApplicationContext,使得我们可以在处理Web请求时,也能使用Spring容器管理的bean。
五、这俩Spring WebApplicationContext啥关系?
标签启动的Spring容器是标签启动的Spring容器的父容器,通过DispatcherServlet#init(ServletConfig)时序图中的11.setParent可知,可对照源码来查看。
子容器能够使用父容器中的Bean,反之不行。
六、DelegatingFilterProxy
最后,来看下在Filter中,如何使用Spring容器。
这里主要使用org.springframework.web.filter.DelegatingFilterProxy构造了代理。
6.1 类结构
1. DelegatingFilterProxy类
DelegatingFilterProxy类中包含:
- delegate:Filter类型的Spring bean,对请求执行实际的预处理、后处理。
- webApplicationContext:用于存储在ContextLoaderListener#contextInitialized中创建的WebApplicationContext。
2. 类结构
从DelegatingFilterProxy类图可知,DelegatingFilterProxy实现了Filter接口,而#init方法是Filter实例的初始化入口。
6.2 执行逻辑
1. DelegatingFilterProxy#init
void init(FilterConfig filterConfig)
init方法是Filter类的初始化方法,DelegatingFilterProxy继承于GenericFilterBean,而GenericFilterBean#init是个final方法,不允许子类重写,源码如下:
/**
* Standard way of initializing this filter.
* Map config parameters onto bean properties of this filter, and
* invoke subclass initialization.
* @param filterConfig the configuration for this filter
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
* @see #initFilterBean
*/
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
}
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
Environment env = this.environment;
if (env == null) {
env = new StandardServletEnvironment();
}
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
String msg = "Failed to set bean properties on filter '" +
filterConfig.getFilterName() + "': " + ex.getMessage();
logger.error(msg, ex);
throw new NestedServletException(msg, ex);
}
}
// Let subclasses do whatever initialization they like.
// 提供了待扩展方法,用于子类重写
initFilterBean();
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
}
}
DelegatingFilterProxy#initFilterBean方法:
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
// 若没指定属性targetBeanName,那么把filterName赋值给targetBeanName
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
// 获取Spring容器
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
DelegatingFilterProxy#initDelegate方法:
/**
* Initialize the Filter delegate, defined as bean the given Spring
* application context.
* <p>The default implementation fetches the bean from the application context
* and calls the standard {@code Filter.init} method on it, passing
* in the FilterConfig of this Filter proxy.
* @param wac the root application context
* @return the initialized delegate Filter
* @throws ServletException if thrown by the Filter
* @see #getTargetBeanName()
* @see #isTargetFilterLifecycle()
* @see #getFilterConfig()
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
// 获取Spring容器中name为targetBeanName的Filter bean
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
// 如果期望在启动时,指定被代理类的init方法,必须将targetFilterLifecycle属性设置为true
// 调用被代理类的init方法,并传入filterConfig对象
delegate.init(getFilterConfig());
}
return delegate;
}
至此,DelegatingFilterProxy及其代理类delegate初始化完成。
2. DelegatingFilterProxy#doFilter
#doFilter方法是Filter实例的实际请求处理方法。
DelegatingFilterProxy#doFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
// 延迟加载逻辑
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
// 通过被代理类来处理请求
invokeDelegate(delegateToUse, request, response, filterChain);
}
DelegatingFilterProxy#invokeDelegate
/**
* Actually invoke the delegate Filter with the given request and response.
* @param delegate the delegate Filter
* @param request the current HTTP request
* @param response the current HTTP response
* @param filterChain the current FilterChain
* @throws ServletException if thrown by the Filter
* @throws IOException if thrown by the Filter
*/
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 通过代理类的doFilter方法来处理请求
delegate.doFilter(request, response, filterChain);
}
在实际处理请求时,DelegatingFilterProxy将请求转发给被代理对象delegate处理。
3. DelegatingFilterProxy#destory
#destroy方法负责Filter实例的销毁。
DelegatingFilterProxy#destory
@Override
public void destroy() {
Filter delegateToUse = this.delegate;
if (delegateToUse != null) {
// 销毁被代理类
destroyDelegate(delegateToUse);
}
}
DelegatingFilterProxy#destroyDelegate
/**
* Destroy the Filter delegate.
* Default implementation simply calls {@code Filter.destroy} on it.
* @param delegate the Filter delegate (never {@code null})
* @see #isTargetFilterLifecycle()
* @see javax.servlet.Filter#destroy()
*/
protected void destroyDelegate(Filter delegate) {
if (isTargetFilterLifecycle()) {
// 如果期望在销毁时,指定被代理类的destory方法,必须将targetFilterLifecycle属性设置为true
delegate.destroy();
}
}