1、Spring MVC是什么?
spring mvc是基于java实现了Web MVC设计模式的的轻量级框架,即使用了MVC的思想,讲web层进行解耦,使用请求->相应模型的请求驱动类型。
spring mvc的前端控制器是dispatcherServlet,应用控制器分为处理器映射器(handler mapping)、 处理器适配(handler adapter)和视图解析器(view resolver),页面控制器controller(仅包含ModelAndView handleRequest(request,response)方法)的实现;支持本地化(AcceptHeaderLocaleResolver、cookieLocaleResolver、sessionLocaleResolver、LocaleChangeInterceptor);提供了非常灵活的数据绑定、认证、格式化机制。
2、Spring MVC能帮我们做什么?
简洁的web层;
强大的数据绑定,验证;
跟spring天生的无缝集成,ioc,aop等;
能够容易的整合视图技术,如velocity,freemarker等,因为数据模型model、modelMap都是map;
灵活的URL映射,相对于strust2来说;
静态资源的访问控制;
支持restful风格;
3、spring mvc的请求流程
1、用户发送请求,前端控制器dispatcherServlet接收请求,执行doServie方法,加载一些request初始化参数,然后调用dispatch方法,优先判断request是不是上传文件的请求,如果是,讲调用multipartResolver来处理该请求;
2、如果不是,根据URL找到对应的映射,执行getHandler方法,返回对应的handlerExecutionChain,找不到则报错,No Mapping found for HTTP request with URI XXX;
3、找到对应的handler之后,接着调用getHandlerAdapter方法,返回适配该handler的adapter;
4、HandlerExecutionChain执行applyPreHandle,即执行Interceptor的preHandle方法,根据preHandle的返回值来判断是够要执行interceptor.afterCompletion()方法;
5、由适配器去执行adapter.handle()方法,来返回视图modelAndView(数据模型和逻辑视图名),
6、如果modelAndView存在,则调用processDispatchResult方法去操作返回的mv,如果不出现异常,则执行render方法渲染视图;
7、响应response,操作结束;
4、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 {
//步骤1、检测是否是上传文件,如果是,则通过MultipartResolver解析
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//步骤2、请求到处理器的映射,通过handlerMapping进行映射,返回HandlerExecutionChain (一个handler,多个Interceptor),通过这种策略模式很容易增加别的策略
mappedHandler = getHandler(processedRequest, false);
//如果不存在,则抛出No Mapping
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//步骤3、将handler包装成处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 判断请求类型 get post
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 {
// 步骤4、适配器执行handler,返回相应的modelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
//如果viewName不存在,加载默认的
applyDefaultViewName(request, mv);
//执行post请求的preHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
//步骤5、详细见下边代码,处理mv,包含exception,errorview,当不存在异常的情况,执行render方法渲染视图
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);
}
}
}
//步骤5详细代码,render渲染视图
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
//render方法,会把model中的数据传入view.render(mv.getModelInternal(), request, response);
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
5、spring mvc的开发配置
1、dispatcherServlet在web.xml中配置部署,从而拦截请求到spring mvc;
2、handlerMapping的配置,将请求映射到处理器handler;
3、handlerAdapter的配置,从而支持多种类型的处理器;
4、viewReslover的配置,将modelAndView解析成view视图;
5、controller处理器,可配置,可注解,通常采用注解方式。
6、hello world
1、web.xml配置dispatcherServlet
<!-- servlet配置 -->
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2、handlerMapping和handlerAdapter的配置
第一种方式:
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="stringHttpMessageConverter" />
<ref bean="mappingJacksonHttpMessageConverter" />
</list>
</property>
</bean>
//ajax乱码问题
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
//接受json数据
<bean id="mappingJacksonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
第二种方式:<mvc:annotation-driven/>注解可以自动装配handlerMapping和handlerAdapter这两个bean
<mvc:annotation-driven/>
<mvc:annotation-driven>
<mvc:message-converters>
//ajax请求拿到数据乱码,因为在StringHttpMessageConverter里面默认设置了字符集是ISO-8859-1,所以要修改成utf-8
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
//接受前台json数据配置
<bean
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
3、viewReslover配置,prefix和suffix分别是真实视图的前后缀
<!-- 配置SpringMVC的视图解析器 -->
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
<!-- jsp view resolver -->
<bean id="jspViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
<property name="contentType" value="text/html;charset=UTF-8" />
<property name="order" value="1"></property>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
</list>
</property>
</bean>
4、controller处理器的配置
@RequestMapping(value = "hello")
public ModelAndView hello() throws Exception {
ModelAndView mv = this.getModelAndView();
mv.addObject("hello", "hello world!");
mv.setViewName("system/admin/hello");
return mv;
}
5、jsp页面渲染,${hello}即modelAndView中的值
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@include file="/WEB-INF/jsp/common/taglib.jsp"%>
<!DOCTYPE html>
<html lang="en">
<head>
<base href="${project_name}/">
</head>
<body>
${hello}
</body>
</html>
7、POST请求中文乱码问题,在web.xml中配置编码Filter
<!-- 编码配置 -->
<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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>