最近在面试招人,很多同学对于DispatcherServlet不太熟悉,有的觉得没了解也没什么用,其实不然,SpringMVC的逻辑实现都在这里,而且这里有很多好的设计模式的应用值得我们借鉴和学习。有利于我们更深的理解SpringMVC。
首先DispatcherServlet是servlet接口的一个实现类。servlet是基于http协议的,在服务端(如Tomcate)运行的,是按照servlet规范编写的一个Java类。做Java Web开发的应该都了解它。主要用于处理客户端的请求并将其结果发送至客户端。servlet的生命周期可分三个阶段:初始化、运行和销毁。
Servlet例子:
(1) 建议Servlet
/**
* @author lhn
*/
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
handleTest(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
handleTest(req, resp);
}
@Override
public void init() {
System.err.println("初始化喽");
}
private void handleTest(HttpServletRequest request,HttpServletResponse response) {
System.err.println(" handleTest");
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher("/index.jsp");
try {
rd.forward(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
虽然只有一个类,但却是一个完整的Web处理过程。SpringMVC 的dispatcherServlet的最终目的也是在做这些事情。init方法在启动时被执行初始化。SpringMVC时该方法内执行了被始化initServletBean(). get/post()方法里处理了跳转逻辑。springMVC中也做了类似的逻辑,当然要比我们复杂的多。
(2) 添加配置
<servlet>
<servlet-name>myservletservlet-name>
<servlet-class>com.lhn.web.servlet.MyServletservlet-class>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>myservletservlet-name>
<url-pattern>*.actionurl-pattern>
servlet-mapping>
请求:localhost/a.action
通过上面我们可以了解到在Servlet初始化阶段会调用init方法,so我们先从DispatcherServlet类中的init方法入手,首先看它是否重写了该方法。在其父类HttpServletBean中找到了该方法。
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
try {
//解析web.xml中init-param 并封装至pvs中。
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//将当前的Servlet类转为Spring可操作的Wrapper类
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) {
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的初始化过程主要是通过当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring提供的注入功能进行对应属性的注入。这些属性如contextAttribute、contextClass、nameSpace、contextConfigLocation等。都可以在web.xml文件中以初始化参数的方式配置在servlet声明中。DipatcherServlet继承自Frameworker,该类中包含了对应的同名属性,Spring会将这些参数注入到对应的值中。属性注入步骤如下:
上面代码中的第7行ServletConfigPropertyValues及做了该事情,同时也对属性进行验证。
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?new HashSet<String>(requiredProperties) : null;
Enumeration<String> en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = en.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
if (missingProps != null && missingProps.size() > 0) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
该方法对初始化进行封装,通过requiredProperties参数验证servlet中配置的必须性。一旦没指定必须值,则异常。
如果有Resource类型的属性,就会使用ResouceEditor去解析。
BeanWrapper自动注入属性。
该过程由子类完成。在FrameworkServlet中覆盖了HttpServletBean中的initServletBean方法,如下
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 {
//关键初始化WebApplicationContext
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()方法。
initWebApplicationContext方法的主要工作就是创建或者是刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化。
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// webApplicationContext 实例在构造函数中被注入
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
//刷新上下文环境
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//根据contextAttribute属性加载
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
本方法中的初始化主要包含几个部分
webApplicationContext的寻找及创建包含以下几个步骤
(1)通过构造函数的注入进行初始化。
当进入initWebApplicationContext方法后通过判断this.webApplicationContext != null 后,
便可以确定this.webApplicationContext是否是通过构造函数来初始化的。由于DispatcherServlet只可被声明一次,而this.webApplicationContext除了initWebApplicationContext()方法可以创建外,还可以通过构造函数来初始化。
(2)通过contextAttribute进行初始化
通过web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为:WebApplicationContext.class.getName() + ".ROOT"
,也就是在ContextLoaderLister加载时会创建webApplicationContext实例,并将实例以WebApplicationContext.class.getName() + ".ROOT"
为key放入ServletContext中,当然我们也可以重写初始化逻辑使用自己创建的WebApplicationContext,并在servlet的配置中通过初始化参数contextAttribute指定key.
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;
}
(3) 重新创建WebApplicationContext 实例
通过以上方法都没有拿到实例,则只能重新创建新的实例。
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取servlet的初始化参数,contextClass,如果没有配置,则默认为:XmlWebApplicationContext.class
Class> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//通过反射方式实例化contextClass
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
//初始化Spring环境加载配置文件
configureAndRefreshWebApplicationContext(wac);
return wac;
}
无论是通过构造方法注入还是通过单独创建,都免不了会调用configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext 实例进行刷新,下面看该方法做了哪些工作。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// the wac environment's #initPropertySources will be called in any case when
// the context is refreshed; do it eagerly here to ensure servlet property sources
// are in place for use in any post-processing or initialization that occurs
// below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//加载配置文件及整合parent至wac
wac.refresh();
}
无论调用方法怎么变,都会调用父类提供的refresh()方法进行配置文件加载。
onRefresh是FrameworkServlet类中提供一个模版方法,在其子类DispatcherServlet中对它进行了重写,用于刷新Spring在Web功能实现中所必须使用的全局变量。我们说一下他的初始化过程以及使用的场景。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
//(1)初始化MultipartResolver
initMultipartResolver(context);
//(2)初始化LocaleResolver
initLocaleResolver(context);
//(3)初始化ThemeResolver
initThemeResolver(context);
//(4)初始化HandlerMappings
initHandlerMappings(context);
//(5)初始化HandlerAdapters
initHandlerAdapters(context);
//(6)初始化HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
//(7)初始化RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
//(8)初始化ViewResolvers
initViewResolvers(context);
//(9)初始化FlashMapManager
initFlashMapManager(context);
}
在Spring中,MultipartResolver主要用于处理文件上传。默认情况下,Spring是没有multipart处理的,因为一些开发者想要自己处理它们。如果想使用Spring的multipart,则需要在Web应用的上下文中添加Multipart解析器。这样每一个请求就会被检查是否包含Multipart。如果包含Multipart,则定义的MultipartResolver就会解析它,这样请求中的MultipartResolver属性就会被处理。配置如下:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--该属性用于配置可上传文件的最大byte数 -->
<property name="maximumFileSize"><value>100000</value></property >
</bean>
CommonsMultipartResolver也提供也其它功能用于帮助用户完成上传功能。有空可以看一下。
MultipartResolver就是在initMultipartResolver方法中被加入至DispatcherServlet中的。
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}
在刷新前已经完成了Spring中配置文件的解析,这里只需要在 ApplicationContext通过getBean方法获取Bean就可。
在Spring中配置国际化配置。
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver + "]");
}
}
}
查看LocaleResolver接口的实现可以看出有3种方式进行国际化。大家可以有兴趣可以看一下。
在Web中我们可能会遇到通过主题Theme来控制网页风格,每一个人或者一类人看到不同风格的网页以增加用户体验。
感觉兴趣的同学也可以看一下。
这个我们常与我们打交道,当客户端发出Request时DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据ApplicationContext 的配置来回传给 DispatcherServlet相应的Controller。
在基于SrpingMVC的Web应用程序中,我们可以配置多个HandlerMapping。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列的HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Hander,DispatcherServlet则使用当前返回的Handler进行Web请求处理,而不再继续询问其它的HandlerMapping。否则将继续寻找,直到找到一个可用的Handler为止。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
从代码中可以看出默认情况下,SpringMVC将加载当前系统中所有实现了HandlerMapping接口的bean。如果只期望加载指定的HandlerMapping时,可以修改Web.xml中的DispatcherServlet的初始参数。
<init-param>
<param-name>detectAllHandlerMappingsparam-name>
<param-value>falseparam-value>
init-param>
此时将执行以下代码
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
Spring将查找名为“handlerMapping”的bean,并作为当前系统中惟一的HandlerMapping。如果没有定义HandlerMapping的话,则执行以下代码
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
SpringMVC将按照org.springframework.web.servlet.DispatcherServlet所在目录下的DispatcherServlet.properties中所定义的org.springframework.web.servlet.HandlerMapping的内容来加载默认的HandlerMapping.
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
OrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}
(未完待续)