DispatcherServlet是SpringMVC的核心控制器,就像是SpringMVC的心脏,几乎所有的请求都会经过这个控制器,通过它,大大的降低了模块之间的耦合度。所有学SpringMVC的同学们第一步肯定都是先配置这个Servlet,不然还写啥SpringMVC啊。
那其实我第一次写SpringMVC的时候就遇到了好多坑,这里记录一下,只是冰山一角,但希望能帮助一些人。
为了更好的说明情况,我先写一些代码
首先新建一个MyController继承自Controller
public class MyController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
ModelAndView mav = new ModelAndView();
// 增加一个属性到model中,底层是调用了request.setAttribute()方法
mav.addObject("hello", "hello springmvc");
// 为这个模型视图起一个逻辑视图名
mav.setViewName("hello");
return mav;
}
}
在WEB-INF目录下建一个springmvc-servlet.xml
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 这个文件默认放在WEB-INF文件夹下 ,如果不是,则需要在web.xml中进行指定-->
<!-- 注册处理器 ,id的属性值为请求的url-->
<bean id="/my.do" class="com.codeliu.controller.MyController"></bean>
<!-- 注册视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 视图资源url的前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!-- 视图资源url的后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
配置完这些,就应该去web.xml中配置DispatcherServlet了。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>SpringMVC_01</display-name>
<!-- 注册中央调度器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- tomcat启动时自动创建servlet,数字越小优先级越高(>0) -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
之后我们在WEB-INF目录下新建一个jsp文件夹,在jsp文件夹里新建一个hello.jsp
<body>
<!-- 等价于${requestScope.hello} -->
${hello}
</body>
到这里,基本就完成了,我们启动tomcat,输入localhost:8080/SpringMVC_01/my.do,能正常显示。
SpringMVC配置文件的名字
如果把上面配置文件的名字由springmvc-servlet.xml改为springmvc.xml,则启动tomcat时就会报错,提示在WEB-INF目录下找不到一个springmvc-servlet.xml的文件。
我们从配置文档中可以找到答案
* <p>Passes a "contextConfigLocation" servlet init-param to the context instance,
* parsing it into potentially multiple file paths which can be separated by any
* number of commas and spaces, like "test-servlet.xml, myServlet.xml".
* If not explicitly specified, the context implementation is supposed to build a
* default location from the namespace of the servlet.
* <p>The default namespace is "'servlet-name'-servlet", e.g. "test-servlet" for a
* servlet-name "test" (leading to a "/WEB-INF/test-servlet.xml" default location
* with XmlWebApplicationContext). The namespace can also be set explicitly via
* the "namespace" servlet init-param.
从这里可以知道两个信息,一个是SpringMVC配置文件的默认路径是WEB-INF下面,另一个是配置文件的name默认应该是你配置的DispatcherServlet的servlet-name+servlet.xml。
知道了这个,那么这个问题也就迎刃而解了。
- 如果你配置文件放在WEB-INF目录下,但名字不想按照那个格式,可以通过namespace这个初始化参数进行指定。
比如我想把配置文件的名字改成springmvc.xml,但文件位置不移动,则在注册servlet时加上初始化参数如下
<init-param>
<!-- 如果springmvc的配置文件放在WEB-INF目录下,但不想名字的形式为*-servlet.xml,
则可以通过namespace属性值进行指定 -->
<param-name>namespace</param-name>
<param-value>springmvc</param-value>
</init-param>
- 如果你的配置文件不想放在WEB-INF目录下,则通过contextConfigLocation这个初始化参数进行指定
比如我把springmvc.xml移动到src目录下,就进行如下配置
<init-param>
<!-- 默认Springmvc的配置文件是在WEB-INF目录下,如果要放在其他目录,则需要指定 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
<!-- 如果springmvc的配置文件放在WEB-INF目录下,但不想名字的形式为*-servlet.xml,
则可以通过namespace属性值进行指定 -->
<!-- <param-name>namespace</param-name>
<param-value>springmvc</param-value> -->
</init-param>
配置了这个参数则namespace这个参数就不用配置了,因为通过这个参数已经把路径和名字都指定了。
(2)配置Servlet的servlet-name值
从上面的信息我们也可以看到,如果在默认情况下,你的servlet-name值必须和配置文件的前缀保持一致,但如果你通过上面的初始化参数进行了设置,那就无关紧要了。
(3)配置servlet-mapping时的url-pattern值
这个也是大有文章啊。我们上面写的是*.do的形式。我们分析一下下面三种情况
/*.do的形式:我们把url-pattern值的值改成/*.do,这种情况你会发现连tomcat都无法启动,所以以后遇到这种情况得想想是不是自己的url-pattern值多写了一个/。
/*的形式:我们把url-pattern值改成/*,这时启动tomcat没有错误,当你输入
http://localhost:8080/SpringMVC_01
或者
http://localhost:8080/SpringMVC_01/my.do
的时候,发现都是404
这个404就有点怪,因为它完全不知道你访问的是哪个资源。原因如下
当你写成这个形式的时候,DispatcherServlet会讲将动态页面(注意是动态页面)的跳转请求也当做是一个controller请求,然后它就会调用处理器映射器为其查找相应的处理器,这种情况下,毫无疑问是找不到的,所以你访问所有的jsp页面都会404。
- /的形式:我们把url-pattern值改成/*,这时启动tomcat没有错误,当你输入
http://localhost:8080/SpringMVC_01/
或者
http://localhost:8080/SpringMVC_01/my.do
诶发现都没错,那这种情况有什么问题呢?
我们修改默认的index.jsp如下
<body>
<img alt="picture" src="images/1.jpg">
</body>
在里面放一张图片
接着重新打开
http://localhost:8080/SpringMVC_01/
你会发现图片无法正常显示。原因就是写成这种形式,DispatcherServlet会将向静态资源的请求(注意是静态资源如css,png,jpg,js等),当前是一个controller请求,中央调度器会调用处理映射器为其查找相应的处理器,毫无疑问,这也是不可能找到的,所以这种情况下,静态资源的请求也全部是404。
总结如下
(1)/*之类的肯定不能写,因为访问啥都出错
(2)/的形式访问静态资源会出错
(4)/的救星
如果我们url-pattern的值还就成/咋的啦,也不是不行,不过就得进行另外的一些配置了。
- 使用tomcat默认的servlet
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
这个servlet的介绍如下,是专门用于处理静态资源的。
<!-- The default servlet for all web applications, that serves static -->
<!-- resources. It processes all requests that are not mapped to other -->
<!-- servlets with servlet mappings (defined either here or in your own -->
<!-- web.xml file). -->
这样我们可以直接在web.xml中进行这个servlet的映射,而不用注册了。
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
这样可以把静态资源的请求转向默认的servlet进行处理,就不会出现找不到静态资源的情况了。
- 使用mvc:default-servlet-handler解决
我们得先在springmvc.xml中引入mvc的约束
xmlns:mvc="http://www.springframework.org/schema/mvc"
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
只需在springmvc.xml中添加一条语句即可
<mvc:default-servlet-handler/>
通过它,静态资源也能正常访问。原理如下:该标签会将对静态资源的请求添加到SimpleUrlHandlerMapping的urlMap中,key就是请求的url,而value则是默认servlet请求处理器DefaultServletHttpRequestHandler对象,而该处理器调用了tomcat的默认servlet来处理静态资源。
所以归根到底,还是使用了tomcat的默认servlet。
- 使用mvc:resources解决
在spring3.0.4版本之后,spring中有了专门处理静态资源访问请求的处理器ResourceHttpRequestHandler。并且添加了mvc:resources标签,专业用于处理静态资源无法访问的问题。所有我们只需要springmvc.xml中加入下面的语句
<mvc:resources location="/images/" mapping="/images/**"></mvc:resources>
location表示静态资源所在的目录,包含WEB-INF目录及其子目录。
mapping表示对该资源的请求。后面是两个星号*
该标签会把对静态资源的请求添加到SimpleUrlHandlerMapping的urlMap中,key就是与mapping属性匹配的url,而value则是静态处理器对象ResourceHttpRequestHandler
相比于第二种方式,这种方式的缺点是如果有css资源、js资源那么你还得添加标签。