|
刚接触SpringMVC,对它的xml文件配置一直比较模模糊糊,最近花了一点时间稍微看了下源代码,再加上调试,开始逐渐理解它,网上的类似的内容有很多,写本文主要是自己加深一下理解。本文适合用过SpringMVC的开发者,言归正传,首先搭建一个最简单的工程体验一下。
该工程是基于maven的,pom配置不再说明,所使用的spring版本4.0.5。 首先是web.xml文件配置,最简单的配置
Java代码
-
<!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>
-
<servlet>
-
<servlet-name>mvc</servlet-name>
-
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
-
<load-on-startup>1</load-on-startup>
-
</servlet>
-
-
<servlet-mapping>
-
<servlet-name>mvc</servlet-name>
-
<url-pattern>/*</url-pattern>
-
</servlet-mapping>
-
</web-app>
然后是mvc-servlet.xml文件的配置,上面配置DispatcherServlet会默认加载[servlet-name]-servlet.xml文件。对于我的配置,会去加载mvc-servlet.xml文件。 mvc-servlet.xml文件的内容:
Java代码
-
<?xml version="1.0" encoding="UTF-8" ?>
-
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context"
-
xsi:schemaLocation="http://www.springframework.org/schema/beans
-
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
-
http://www.springframework.org/schema/mvc
-
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
-
http://www.springframework.org/schema/util
-
http://www.springframework.org/schema/util/spring-util-2.0.xsd
-
http://www.springframework.org/schema/context
-
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
-
-
<bean name="/index" class="com.lg.mvc.HomeAction"></bean>
-
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
-
<property name="templateLoaderPath" value="/WEB-INF/views" />
-
<property name="defaultEncoding" value="utf-8" />
-
<property name="freemarkerSettings">
-
<props>
-
<prop key="locale">zh_CN</prop>
-
</props>
-
</property>
-
</bean>
-
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
-
<property name="suffix" value=".html" />
-
<property name="contentType" value="text/html;charset=utf-8" />
-
<property name="requestContextAttribute" value="request" />
-
<property name="exposeRequestAttributes" value="true" />
-
<property name="exposeSessionAttributes" value="true" />
-
</bean>
-
</beans>
在该配置中定义了一个HomeAction的Bean。内容为:
Java代码
-
package com.lg.mvc;
-
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
-
import org.springframework.web.servlet.ModelAndView;
-
import org.springframework.web.servlet.mvc.Controller;
-
-
public class HomeAction implements Controller{
-
-
@Override
-
public ModelAndView handleRequest(HttpServletRequest request,
-
HttpServletResponse response) throws Exception {
-
return new ModelAndView("hello");
-
}
-
}
这是最原始的mvc做法,要继承Controller接口,先从原始的说起,最后再过渡到@Controller和@RequestMapping注解式的配置。它在mvc-serlet.xml文件中的配置有一个关键的属性name="/index"。 WEB-INF/view目录下有一个简单的hello.html,内容为:
Java代码
-
<html>
-
<head>
-
-
</head>
-
<body>
-
hello lg !
-
</body>
-
</html>
至此该工程就写完了,部署到tomcat中,项目路径为/,运行一下。 访问 http://localhost:8080/index 至此整个工程就算搭建成功了。
下面就要说说原理了。 用过python Django框架的都知道Django对于访问方式的配置就是,一个url路径和一个函数配对,你访问这个url,就会直接调用这个函数,简单明了。对于java的面向对象来说,就要分两步走。第一步首先要找到是哪个对象,即handler,本工程的handler则是HomeAction对象。第二步要找到访问的函数,即HomeAction的handleRequest方法。所以就出现了两个源码接口 HandlerMapping和HandlerAdapter,前者负责第一步,后者负责第二步。借用网上的SpringMVC架构图。
HandlerMapping接口的实现(只举了我认识的几个) :
HandlerAdapter 接口实现:
先简单的说下这个工程的流程,访问http://localhost:8080/index首先由DispatcherServlet进行转发,通过BeanNameUrlHandlerMapping(含有 /index->HomeAction的配置),找到了HomeAction,然后再拿HomeAction和每个adapter进行适配,由于HomeAction实现了Controller接口,所以最终会有SimpleControllerHandlerAdapter来完成对HomeAction的handleRequest方法的调度。然后就顺利的执行了我们想要的方法,后面的内容不在本节中说明。
了解了大概流程,然后就需要看源代码了。 首先就是SpringMVC的入口类,DispatcherServlet,它实现了Servlet接口,不再详细说DispatcherServlet的细节,不然又是一大堆的内容。每次请求都会调用它的doService->doDispatch,我们关注的重点就在doDispatch方法中。
Java代码
-
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
-
HttpServletRequest processedRequest = request;
-
HandlerExecutionChain mappedHandler = null;
-
boolean multipartRequestParsed = false;
-
-
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
-
-
try {
-
ModelAndView mv = null;
-
Exception dispatchException = null;
-
-
try {
-
processedRequest = checkMultipart(request);
-
multipartRequestParsed = (processedRequest != request);
-
//这个是重点,第一步由HandlerMapping找到对应的handler
-
// Determine handler for the current request.
-
mappedHandler = getHandler(processedRequest);
-
if (mappedHandler == null || mappedHandler.getHandler() == null) {
-
noHandlerFound(processedRequest, response);
-
return;
-
}
-
-
// Determine handler adapter for the current request.
-
//这是第二步,找到合适的HandlerAdapter,然后由它来调度执行handler的方法
-
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
-
-
// Process last-modified header, if supported by the handler.
-
String method = request.getMethod();
-
boolean isGet = "GET".equals(method);
-
if (isGet || "HEAD".equals(method)) {
-
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
-
if (logger.isDebugEnabled()) {
-
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
-
}
-
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
-
return;
-
}
-
}
-
-
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
-
return;
-
}
-
-
try {
-
// Actually invoke the handler.
-
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
}
-
finally {
-
if (asyncManager.isConcurrentHandlingStarted()) {
-
return;
-
}
-
}
-
-
applyDefaultViewName(request, mv);
-
mappedHandler.applyPostHandle(processedRequest, response, mv);
-
}
-
catch (Exception ex) {
-
dispatchException = ex;
-
}
-
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
-
}
-
catch (Exception ex) {
-
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
-
}
-
catch (Error err) {
-
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
-
}
-
finally {
-
if (asyncManager.isConcurrentHandlingStarted()) {
-
// Instead of postHandle and afterCompletion
-
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
-
return;
-
}
-
// Clean up any resources used by a multipart request.
-
if (multipartRequestParsed) {
-
cleanupMultipart(processedRequest);
-
}
-
}
-
}
第一步详细查看:
Java代码
-
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
-
for (HandlerMapping hm : this.handlerMappings) {
-
if (logger.isTraceEnabled()) {
-
logger.trace(
-
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
-
}
-
HandlerExecutionChain handler = hm.getHandler(request);
-
if (handler != null) {
-
return handler;
-
}
-
}
-
return null;
-
}
可以看到就是通过遍历所有已注册的HandlerMapping来找到对应的handler,然后构建出一个HandlerExecutionChain,它包含了handler和HandlerMapping本身的一些拦截器,如下
Java代码
-
public class HandlerExecutionChain {
-
-
private final Object handler;
-
-
private HandlerInterceptor[] interceptors;
-
-
private List<HandlerInterceptor> interceptorList;
-
-
//其他代码省略
-
}
其中HandlerMapping的getHandler实现:
Java代码
-
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
-
Object handler = getHandlerInternal(request);
-
if (handler == null) {
-
handler = getDefaultHandler();
-
}
-
if (handler == null) {
-
return null;
-
}
-
// Bean name or resolved handler?
-
if (handler instanceof String) {
-
String handlerName = (String) handler;
-
handler = getApplicationContext().getBean(handlerName);
-
}
-
return getHandlerExecutionChain(handler, request);
-
}
这里的getHandlerInternal(request)是个抽象方法,由具体的HandlerMapping来实现,获取到的handler如果为空,则获取默认配置的handler,如果handler为String类型,则表示这个则会去Spring容器里面去找这样名字的bean。 再看下BeanNameUrlHandlerMapping的getHandlerInternal(request)的具体实现(通过一系列的接口设计,之后再好好看看这个设计,到BeanNameUrlHandlerMapping这只用实现该方法中的一部分),如下
Java代码
-
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
-
-
/**
-
* Checks name and aliases of the given bean for URLs, starting with "/".
-
*/
-
@Override
-
protected String[] determineUrlsForHandler(String beanName) {
-
List<String> urls = new ArrayList<String>();
-
if (beanName.startsWith("/")) {
-
urls.add(beanName);
-
}
-
String[] aliases = getApplicationContext().getAliases(beanName);
-
for (String alias : aliases) {
-
if (alias.startsWith("/")) {
-
urls.add(alias);
-
}
-
}
-
return StringUtils.toStringArray(urls);
-
}
-
-
}
这里面注释说,bean的name必须以/开头,它才处理,将信息存储在Map<String, Object> handlerMap中,对于本工程来说就是{'/index':HomeAction对象}。 至此这里完成了第一步,下面开始第二步,即方法HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());的具体实现:
Java代码
-
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
-
for (HandlerAdapter ha : this.handlerAdapters) {
-
if (logger.isTraceEnabled()) {
-
logger.trace("Testing handler adapter [" + ha + "]");
-
}
-
if (ha.supports(handler)) {
-
return ha;
-
}
-
}
-
throw new ServletException("No adapter for handler [" + handler +
-
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
-
}
遍历所有的HandlerAdapter,判断他们是否支持这个handler。 我们来看下HttpRequestHandlerAdapter的supports(handler)方法:
Java代码
-
public class HttpRequestHandlerAdapter implements HandlerAdapter {
-
-
@Override
-
public boolean supports(Object handler) {
-
//就是判断handler是否实现了HttpRequestHandler接口
-
return (handler instanceof HttpRequestHandler);
-
}
-
-
@Override
-
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
-
throws Exception {
-
//若handler实现了HttpRequestHandler接口,则调用该接口的方法,执行我们在该方法中写的业务逻辑
-
((HttpRequestHandler) handler).handleRequest(request, response);
-
return null;
-
}
-
-
@Override
-
public long getLastModified(HttpServletRequest request, Object handler) {
-
if (handler instanceof LastModified) {
-
return ((LastModified) handler).getLastModified(request);
-
}
-
return -1L;
-
}
-
-
}
同理SimpleControllerHandlerAdapter也是这样类似的逻辑
Java代码
-
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
-
-
@Override
-
public boolean supports(Object handler) {
-
return (handler instanceof Controller);
-
}
-
-
@Override
-
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
-
throws Exception {
-
-
return ((Controller) handler).handleRequest(request, response);
-
}
-
-
@Override
-
public long getLastModified(HttpServletRequest request, Object handler) {
-
if (handler instanceof LastModified) {
-
return ((LastModified) handler).getLastModified(request);
-
}
-
return -1L;
-
}
-
-
}
剩余两个AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter就比较复杂,我也没看。 按照本工程的配置,则SimpleControllerHandlerAdapter是支持HomeAction的,然后就会执行SimpleControllerHandlerAdapter的handle(processedRequest, response, mappedHandler.getHandler())方法。本质上就会调用HomeAction实现Controller接口的方法。至此就分析完了。 了解过程了之后,然后就是最重要的也是经常配置出问题的地方。DispatcherServlet的handlerMappings和handlerAdapters的来源问题。
DispatcherServlet初始化的时候,会调用一个方法如下:
Java代码
-
protected void initStrategies(ApplicationContext context) {
-
initMultipartResolver(context);
-
initLocaleResolver(context);
-
initThemeResolver(context);
-
//初始化一些HandlerMapping
-
initHandlerMappings(context);
-
//初始化一些HandlerAdapter
-
initHandlerAdapters(context);
-
initHandlerExceptionResolvers(context);
-
initRequestToViewNameTranslator(context);
-
initViewResolvers(context);
-
initFlashMapManager(context);
-
}
这里可以看到,它会初始化一些HandlerMapping和HandlerAdapter,这两个方法非常重要,理解了这两个方法你就会知道,配置不对问题出在哪里,下面具体看下这两个方法:
Java代码
-
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");
-
}
-
}
-
}
detectAllHandlerMappings是DispatcherServlet的一个属性,你是可以在web.xml中配置的,默认是true,如果为true,则会去从本工程mvc-servlet.xml文件中去探测所有实现了HandlerMapping的bean,如果有,则加入DispatcherServlet的handlerMappings中。如果detectAllHandlerMappings为false,则直接去容器中找id="handlerMapping"且实现了HandlerMapping的bean.如果以上都没找到,则会去加载默认的HandlerMapping。
Java代码
-
/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
-
private boolean detectAllHandlerMappings = true;
本工程由于没有配置HandlerMapping,所以它会去加载默认的,下面看看默认的配置是什么
Java代码
-
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
-
String key = strategyInterface.getName();
-
//defaultStrategies存储了默认的配置
-
String value = defaultStrategies.getProperty(key);
-
if (value != null) {
-
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
-
List<T> strategies = new ArrayList<T>(classNames.length);
-
for (String className : classNames) {
-
try {
-
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
-
Object strategy = createDefaultStrategy(context, clazz);
-
strategies.add((T) strategy);
-
}
-
catch (ClassNotFoundException ex) {
-
throw new BeanInitializationException(
-
"Could not find DispatcherServlet's default strategy class [" + className +
-
"] for interface [" + key + "]", ex);
-
}
-
catch (LinkageError err) {
-
throw new BeanInitializationException(
-
"Error loading DispatcherServlet's default strategy class [" + className +
-
"] for interface [" + key + "]: problem with class file or dependent class", err);
-
}
-
}
-
return strategies;
-
}
-
else {
-
return new LinkedList<T>();
-
}
-
}
继续看看defaultStrategies是如何初始化的:
Java代码
-
private static final Properties defaultStrategies;
-
-
static {
-
// Load default strategy implementations from properties file.
-
// This is currently strictly internal and not meant to be customized
-
// by application developers.
-
try {
-
//这里的DEFAULT_STRATEGIES_PATH就是DispatcherServlet.properties
-
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
-
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
-
}
-
catch (IOException ex) {
-
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
-
}
-
}
这里使用静态代码块来加载配置文件DispatcherServlet.properties,它所在位置就是和DispatcherServlet同一目录下面的,如下图所示:
该默认的配置文件的内容如下:
Java代码
-
# Default implementation classes for DispatcherServlet's strategy interfaces.
-
# Used as fallback when no matching beans are found in the DispatcherServlet context.
-
# Not meant to be customized by application developers.
-
-
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
-
-
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
-
-
#这里就是默认的HandlerMapping的配置
-
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
-
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
-
#这里就是默认的HandlerAdapter的配置
-
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
-
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
-
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
-
-
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
-
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
-
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
-
-
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
-
-
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
-
-
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
也就是说,当你什么都没有配置时,默认会加载以上的配置。正是由于有了上述默认配置的BeanNameUrlHandlerMapping(它要求name必须是以/开头的),它才会存储我们在mvc-servlet.xml中配置的<bean name="/index" class="com.lg.mvc.HomeAction"></bean>,同样正是由于有了SimpleControllerHandlerAdapter(由于handler实现了Controller接口,所以它的support方法支持我们的handler),才会调度执行HomeAction的handleRequest方法
|
|
|
相关推荐
在本压缩包中,你将找到由知名讲师孔浩老师提供的 Spring MVC 入门教程及小项目的源码,同时它还整合了 Hibernate,这将帮助初学者更深入地理解如何在实际项目中使用这些技术。 1. **Spring MVC 基础** - ...
这一章深入讲解了控制器(Controller)的概念,包括HandlerMapping和HandlerAdapter的作用,以及如何编写和使用@RequestMapping注解来处理HTTP请求。同时,会涉及模型数据的传递,如ModelAndView对象的使用和模型...
总结来说,这个压缩包提供了一个精简版的 Spring MVC 入门教程,包含了源码和配置,适合初学者进行实践学习。通过这个项目,你可以学习到如何使用 Spring MVC 构建 Web 应用,理解 MVC 设计模式,熟悉 Spring MVC 中...
总结,SpringMVC提供了一种优雅的方式构建Web应用,通过其组件和注解驱动的编程模型,可以轻松实现业务逻辑与展示层的分离。通过以上步骤,你可以快速地搭建一个简单的SpringMVC项目,并进行功能测试。在实际开发中...
配置中会声明`HandlerMapping`和`HandlerAdapter`,它们负责找到合适的Controller方法来处理请求。此外,还可能配置了`ViewResolver`,比如`InternalResourceViewResolver`,用于解析返回的视图名称到实际的视图资源...
"SpringMVC-HelloWorld"是一个经典的入门示例,通常包括以下几个步骤: 1. **创建Maven项目**:首先,我们需要一个基于Maven的Java Web项目,因为SpringMVC通常与Maven一起使用来管理依赖。 2. **添加SpringMVC...
通过源码阅读,你可以深入了解SpringMVC的内部机制,如`DispatcherServlet`、`HandlerMapping`、`HandlerAdapter`、`ViewResolver`等组件的工作原理。 ### 工具支持 在开发过程中,IDEA、Eclipse等集成开发环境...
SpringMVC笔记涉及了SpringMVC框架的全面知识,从基础入门到高级应用,内容丰富涵盖了架构原理、组件配置、处理器映射器和适配器的配置、视图解析、源码分析、整合MyBatis、参数绑定与校验、异常处理、RESTful支持...
下面,我们将深入探讨 SpringMVC 的基本概念和如何创建一个入门实例。 1. **SpringMVC 概述** - MVC 模式:Model(模型)负责业务逻辑,View(视图)负责显示结果,Controller(控制器)处理用户请求并协调模型和...
本章将介绍如何入门 Spring MVC,通过提供的源代码下载,你可以更深入地理解和实践相关知识。 在 Spring MVC 中,`Model` 负责业务数据的处理,`View` 负责数据显示,而 `Controller` 则是两者之间的桥梁,处理用户...
在提供的压缩包 "springmvc" 中,你可能找到了一个包含源码的简单 Spring MVC 示例项目。你可以通过导入这个项目到 IDE,例如 Eclipse 或 IntelliJ IDEA,然后运行 DispatcherServlet 来查看实际效果。通过阅读和...