SpringMVC中为什么要配置Listener和Servlet

2024-09-03 05:32

本文主要是介绍SpringMVC中为什么要配置Listener和Servlet,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一直以来,我们使用SpringMVC的时候习惯性都配置一个ContextLoaderListener,虽然曾经有过疑问,配置的这个监听器和Servlet究竟做了什么,但也没深究。

要说任何Web框架都离不开Servlet,它是一个容器,也是一种规范,你要和Web搞上关系,无非就是那么几种,监听器、过滤器和Servlet,最终都是为了切进ServletContext。

SpringMVC是基于Servlet的,直接配个Servlet不就行了嘛,但我没有真正这么做过。上次群里有个人说根本不需要配置监听器,他从来不配那玩意,也没啥事,于是我也实验了一把。

然后有了下面的总结:

ContextLoaderListener

启动方式:

<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在contextInitialized方法中调用initWebApplicationContext方法

/*** Initialize the root web application context.*/
public void contextInitialized(ServletContextEvent event) {this.contextLoader = createContextLoader();if (this.contextLoader == null) {this.contextLoader = this;}this.contextLoader.initWebApplicationContext(event.getServletContext());
}

1. ServletContext中查找指定key的实例

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的实例如果存在则抛出异常,不存在则创建。

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

2. createWebApplicationContext(servletContext)

可以指定自己的contextClass 如果没有 则使用默认的:

org.springframework.web.context.support.XmlWebApplicationContext

如果是自己的需要实现ConfigurableWebApplicationContext(通过isAssignableFrom方法判断,注意和instanceof区别)

3. 配置和刷新WebApplicationContext

configureAndRefreshWebApplicationContext(cwac, servletContext);

  • wac.setServletContext(sc); 把servletContext放到上下文中持有
  • wac.setConfigLocation(configLocationParam); 利用servletContext的getInitParameter方法获取配置文件的位置。
  • wac.refresh(); 调用继承的refresh方法初始化IOC容器。

4. 把创建的根上下文放到servletContext中

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this.context);

把这个上下文放到servletContext中。

DispatcherServlet

GenericServlet中定义了一个

 public void init() throws ServletException {}

被HttpServlet继承 HttpServletBean又继承了HttpServlet

在init方法中调用initServletBean();

protected void initServletBean() throws ServletException {}

利用模板方法模式,在子类FrameworkServlet中实现,然后在下面的方法中初始化WebApplicationContext:

initWebApplicationContext();

if (wac == null) {// No context instance was injected at construction time -> see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context idwac = findWebApplicationContext();
}
if (wac == null) {// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);
}

1. 先取rootContext

WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

在这里会去servletContext中取出key为‘ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE’的WebApplicationContext作为一个rootContext

由于监听器启动创建了这个rootContext,现在可直接拿出来

2. findWebApplicationContext()

protected WebApplicationContext findWebApplicationContext() {String attrName = getContextAttribute();if (attrName == null) {return null;}WebApplicationContext wac =WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");}return wac;
}

由于attrName为null,直接返回null。

3. createWebApplicationContext(rootContext)

在没有启动监听器的情况下,就没有创建父上下文,然后依然是使用 :
org.springframework.web.context.support.XmlWebApplicationContext

ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());

4. configureAndRefreshWebApplicationContext(wac);

wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
……
wac.refresh();

使用shiro报错

如果只配置一个dispatcherServlet然后使用shiro

启动时报错:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘lifecycleBeanPostProcessor’ is defined

注意:springMVC的配置文件中包含了对lifecycleBeanPostProcessor的引用
这时候要将SpringMVC的配置文件扫描扩大到所有spring的配置文件

访问时报错:

java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?

关掉shiro配置后,不再报错,说明是shiro引起的,跟着shiro的配置,找到了一个突破口:DelegatingFilterProxy

<filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param>
</filter>
<filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}

initFilterBean()的时候调用一次findWebApplicationContext()

doFilter()时还会调用

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) {if (this.delegate == null) {WebApplicationContext wac = findWebApplicationContext();if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");}this.delegate = initDelegate(wac);}delegateToUse = this.delegate;}}// Let the delegate perform the actual doFilter operation.invokeDelegate(delegateToUse, request, response, filterChain);
}protected WebApplicationContext findWebApplicationContext() {if (this.webApplicationContext != null) {// the user has injected a context at construction time -> use itif (this.webApplicationContext instanceof ConfigurableApplicationContext) {if (!((ConfigurableApplicationContext)this.webApplicationContext).isActive()) {// the context has not yet been refreshed -> do so before returning it((ConfigurableApplicationContext)this.webApplicationContext).refresh();}}return this.webApplicationContext;}String attrName = getContextAttribute();if (attrName != null) {return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);}else {return WebApplicationContextUtils.getWebApplicationContext(getServletContext());}
}

出问题的关键在此:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

先取attrName,如果不为null,到servletContext中取一个指定名称的WebApplicationContext。没有配置这个属性,默认都是取root webApplicationContext。

现在没有配置监听器,root webApplicationContext当然不存在,不过给它指定一个contextAttribute也许可以解决,于是我在web.xml中给shiro加了一个初始化参数:

<init-param><param-name>contextAttribute</param-name><param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.springServlet</param-value>
</init-param>

这下竟然可以跑了,但是其它问题还没验证。反正我不推荐这种方式。

关于DelegatingFilterProxy可以看这里了解下:
http://blog.csdn.net/xh16319/article/details/9771063

总结:

  1. 监听器启动和servlet启动都会initWebApplicationContext,由各自的初始化方法来触发。

  2. 两者各自的上下文最终都会保存到servletContext中,root WebApplicationContext使用固定的attrName,dispatcherServlet使用FrameworkServlet.class.getName() + “.CONTEXT.”+servletName。

  3. 最佳实践还是监听器加servlet的配置,各司其职,父上下文做核心容器,子上下文处理web相关。如果你要把springMVC换成其他框架如struts,也不会有什么影响。

  4. 监听器可以在整个webapp启动时初始化IOC容器,并在关闭时做一些处理。

  5. 不管是基于xml和注解的Application还是WebApplication,最终都是那套,调用继承自AbstractApplicationContext的refresh()方法。

引用下stackoverflow上关于是否要监听器的回答:

In your case, no, there’s no reason to keep the ContextLoaderListener and applicationContext.xml. If your app works fine with just the servlet’s context, that stick with that, it’s simpler.

Yes, the generally-encouraged pattern is to keep non-web stuff in the webapp-level context, but it’s nothing more than a weak convention.

The only compelling reasons to use the webapp-level context are:

  • If you have multiple DispatcherServlet that need to share services
  • If you have legacy/non-Spring servlets that need access to Spring-wired services
  • If you have servlet filters that hook into the webapp-level context (e.g. Spring Security’s DelegatingFilterProxy, OpenEntityManagerInViewFilter, etc)
    None of these apply to you, so the extra complexity is unwarranted.

Just be careful when adding background tasks to the servlet’s context, like scheduled tasks, JMS connections, etc. If you forget to add to your web.xml, then these tasks won’t be started until the first access of the servlet.

You need to understand the difference between Web application context and root application context .

In the web MVC framework, each DispatcherServlet has its own WebApplicationContext, which inherits all the beans already defined in the root WebApplicationContext. These inherited beans defined can be overridden in the servlet-specific scope, and new scope-specific beans can be defined local to a given servlet instance.

相关链接:

http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/mvc.html

http://www.codesenior.com/en/tutorial/Spring-ContextLoaderListener-And-DispatcherServlet-Concepts

http://bbs.csdn.net/topics/391076893?page=1

这篇关于SpringMVC中为什么要配置Listener和Servlet的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1132158

相关文章

Java中对象的创建和销毁过程详析

《Java中对象的创建和销毁过程详析》:本文主要介绍Java中对象的创建和销毁过程,对象的创建过程包括类加载检查、内存分配、初始化零值内存、设置对象头和执行init方法,对象的销毁过程由垃圾回收机... 目录前言对象的创建过程1. 类加载检查2China编程. 分配内存3. 初始化零值4. 设置对象头5. 执行

SpringBoot整合easy-es的详细过程

《SpringBoot整合easy-es的详细过程》本文介绍了EasyES,一个基于Elasticsearch的ORM框架,旨在简化开发流程并提高效率,EasyES支持SpringBoot框架,并提供... 目录一、easy-es简介二、实现基于Spring Boot框架的应用程序代码1.添加相关依赖2.添

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

SpringBoot中整合RabbitMQ(测试+部署上线最新完整)的过程

《SpringBoot中整合RabbitMQ(测试+部署上线最新完整)的过程》本文详细介绍了如何在虚拟机和宝塔面板中安装RabbitMQ,并使用Java代码实现消息的发送和接收,通过异步通讯,可以优化... 目录一、RabbitMQ安装二、启动RabbitMQ三、javascript编写Java代码1、引入

spring-boot-starter-thymeleaf加载外部html文件方式

《spring-boot-starter-thymeleaf加载外部html文件方式》本文介绍了在SpringMVC中使用Thymeleaf模板引擎加载外部HTML文件的方法,以及在SpringBoo... 目录1.Thymeleaf介绍2.springboot使用thymeleaf2.1.引入spring

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在