SpirngMVC架构图
总结:
1、用户发起请求,到前端控制器(DispatchServlet),会执行到(doDispatch)方法;
2、循环 HandlerMapping 查找 Handler,返回一个处理器执行链(HandlerExecutionChain);
HandlerExecutionChain 包含:
a)Handler对象
b) HandlerInterceptor[] 拦截器数组
4、通过HandlerAdapter进行包装Handler,返回一个HandlerAdapter对象;
5、循环执行处理器执行链中拦截器的前置方法;
6、通过适配器执行Handler,返回一个ModelAndView;
a)Model Data(模型数据)
b)View(ViewName) 视图名称
7、DispatcherServlet将ModelAndView传给ViewReslover视图解析器;
8、ViewReslover解析后返回具体View;
9、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中);
10、DispatcherServlet响应用户。
简单总结:
1、请求进入中央处理器(DispatcherServlet)
2、通过HandlerMapping查询业务逻辑类
3、通过HandlerAdapter执行业务逻辑
4、通过ViewReslover渲染视图
5、DispatcherServlet响应用户
为什么要设计适配器?
适配器是专门用来执行业务方法的,业务方法的Controller有很多种类型,如果没有适配器的话,在DispatcherServlet中调用Controller中业务方法的时,需要判断Controller类型后再调用;如果这是我们想扩展一个自己的Controller,是需要改源码做判断调用。违反扩展开放,修改关闭的原则。如果有个适配器,这时要扩展Contrller,只需要编写自己的Controller和实现适配器接口就可以了。
开发第一个SpringMVC应用
创建SpringMVC工程
工程目录结构
导入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.springmvc</groupId>
<artifactId>SpringMVCTest</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<!-- 定义依赖版本号 -->
<properties>
<junit.version>4.10</junit.version>
<slf4j.version>1.6.4</slf4j.version>
<spring.version>4.1.3.RELEASE</spring.version>
<jstl.version>1.2</jstl.version>
<servlet-api.version>2.5</servlet-api.version>
<jsp-api.version>2.0</jsp-api.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- JSP相关 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp-api.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 端口 -->
<port>80</port>
<!-- /:直接访问,无需加项目名 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置web.xml
在src/main/webapp下创建WEB-INF目录以及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_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>spingmvc</display-name>
<!-- 配置SpringMVC框架入口 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 加载springmvc配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--
tomcat启动时完成初始化
不配置,在第一次请求后完成初始化
-->
<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.xml 常用配置
CharacterEncodingFilter过滤器
CharacterEncodingFilter
类是一个过滤器,当前台JSP页面和JAVA代码中使用了不同的字符集进行编码的时候就会出现表单提交的数据或者上传/下载中文名称文件出现乱码的问题,那这个类就可以出场了。
CharacterEncodingFilter
类具有encoding和forceEncoding两个属性,其中encoding是表示设置request的编码,forceEncoding表示是否同时设置response的编码。
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Spring加载配置文件
在web.xml中定义contextConfigLocation参数,Spring会使用这个参数去加载所有逗号分隔的xml文件,如果没有这个参数,Spring默认加载WEB-INF/applicationContext.xml文件。
配置Spring加载的配置文件:
<!-- spring config -->
<context-param>
<!-- 参数名为contextConfigLocation -->
<param-name>contextConfigLocation</param-name>
<!-- 一个或多个配置文件之间,并以逗号隔开 -->
<param-value>
classpath*:spring-basic.xml,
classpath*:conf/spring/applicationContext_core*.xml,
/WEB-工NF/daoContext.xml
</param-value>
</context-param>
<!-- 采用listener创建ApplicationContext实例 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
配置SpringMVC:
<!-- spring mvc config -->
<servlet>
<servlet-name>spring-mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-mvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
错误页面配置
在web.xml中有两种配置error-page的方法,一是通过错误码来配置,而是通过异常的类型来配置。
通过错误码来配置error-page:
<error-page>
<error-code>404</error-code>
<location>/index.jsp</location>
</error-page>
通过异常的类型配置error-page:
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/index.jsp</location>
</error-page>
注意:<error-code>
和 <exception-type>
不能在同一个<error-page>
标签上同时出现。
首页配置
默认从webapp目录下寻找首页,也可以配置WEB-INF目录下的文件。
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<welcome-file-list>
<welcome-file>/WEB-INF/views/index.jsp</welcome-file>
</welcome-file-list>
配置日志文件
在src/main/resources下创建log4j.properties
log4j.rootLogger=DEBUG,A1
log4j.logger.org.mybatis = DEBUG
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
SpringMVC配置文件
在src/main/resources下创建springmvc-servlet.xml
SpringMVC默认从WEB-INF下读取servlet配置文件,文件的命名规则是:servletName-servlet.xml
如果要自定义servlet文件,需要在servlet定义时指定contextConfigLocation。
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 定义执行器映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- 定义适配器 -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--定义模型对象 -->
<bean name="/hello.do" class="com.springmvc.handler.HelloHandler"/>
<!--
定义视图解析器(内部资源视图解析器)
Example: prefix="/WEB-INF/jsp/", suffix=".jsp", viewname="test" -> "/WEB-INF/jsp/test.jsp"
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
配置文件解析:
1、配置处理器映射器HandlerMapping
2、配置处理器适配器HandlerAdapter
3、配置模型Handler
4、配置视图解析器ViewResolver
HandlerMapping:
HandlerAdapter:
ViewResolver:
为什么要设计适配器?
Controller是多种类型的,如果没有适配器,在DispatcherServlet中调用Hadnler,需要判断Controller类型做调用,我们想去扩展自己的Controller,需要去改源码做判断调用。违反开闭(对扩展开放,对修改关闭)原则。
如果有了适配器,如果要扩展Controller,只需要写自己的Controller和实现适配器接口即可。
编写模型对象处理器
package com.springmvc.handler;
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 HelloHandler implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 处理业务逻辑
ModelAndView mv = new ModelAndView();
mv.setViewName("hello");// 设置视图名称
mv.addObject("msg", "我的第一个SpringMVC应用!");// 设置模型数据
// request.setAttribute("msg", "xxxxx");
return mv;
}
}
启动Tomcat查看效果
测试:
DispatcherServlet源码解析
DispatcherServlet源码初始化过程
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); //文件上传组件
initLocaleResolver(context); //国际化
initThemeResolver(context);// 主题
initHandlerMappings(context);// HandlerMapping 处理器映射
initHandlerAdapters(context);// HandlerAdapter 适配器
initHandlerExceptionResolvers(context); //异常相关组件
initRequestToViewNameTranslator(context); // 转化
initViewResolvers(context); // ViewResolvers 视图解析器
initFlashMapManager(context); // 闪存
}
初始化映射器
初始化适配器
SpringMVC的默认配置文件
org.springframework.web.servlet.DispatcherServlet.properties
发现默认的配置文件中已经配置了默认的映射器和适配器,所以可以简化配置,无需配置映射器和适配器。
在DispatcherServlet中的doDispatch方法是SpringMVC的执行流程的控制方法,源码如下:
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);
// 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.
// 获取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;
}
}
// 执行链中拦截器的前置方法,如果返回false则终止执行。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.
// 通过Handler适配器执行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);
}
}
}
// 1、定义执行处理器执行链(HandlerExecutionChain mappedHandler = null;)
// 2、获取处理器执行链(mappedHandler = getHandler(processedRequest);)
// 3、获取处理器适配器(HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());)
// 4、执行链中拦截器的前置方法,如果返回false则终止执行(if (!mappedHandler.applyPreHandle(processedRequest, response)))
// 5、通过适配器执行真正的业务处理器逻辑,并返回模型和视图对象(mv = ha.handle(processedRequest, response, mappedHandler.getHandler());)
// 6、执行链中拦截器的后置方法(mappedHandler.applyPostHandle(processedRequest, response, mv);)
// 7、处理返回的结果,视图解析器的处理(processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);)
// 8、查找到视图后进行渲染,然后响应给请求端
使用注解方式开发SpringMVC应用
在第一个SpringMVC应用的基础上修改SpringMVC配置文件和编写模型对象处理器。
下面两个配置不是SpringMVC默认配置,所以不能省略。
SpringMVC配置文件
<!-- 定义注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!-- 定义注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<!-- 配置扫描器,使得@Controller生效 -->
<context:component-scan base-package="com.springmvc.handler" />
编写模型对象处理器
使用@RequestMapping映射请求URL
使用@Controller注解使HelloHandler 成为处理请求的控制器
package com.springmvc.handler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/hello")
public class HelloHandler {
@RequestMapping("/show")
public ModelAndView show() {
// 处理业务逻辑
ModelAndView mv = new ModelAndView();
mv.setViewName("hello");// 设置视图名称
mv.addObject("msg", "我的第一个注解SpringMVC应用!");// 设置模型数据
return mv;
}
}
使用tomcat7启动后并测试
升级版注解驱动
作用:SpringMVC默认配置的升级版;
<mvc:annotation-driven />
对应的java类是:
org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
使用<mvc:annotation-driven />后默认加入的HandlerMapping和Adapter有:
HandlerMapping有2个:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
Adapter有3个:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
改进后的注解SpringMVC配置