1. SpringMVC概述
- Spring为展现层提供的基于MVC设计理念的Web框架
- SpringMVC通过一套MVC注解,让POJO成为处理请求的控制器,而无需实现任何接口
- 支持REST风格的URL请求
- 采用松散耦合可插拔组件结构,比其他MVC框架更具有扩展性和灵活性
- MVC提倡:每一层只编写自己的东西,不写任何其他的代码
2. SpringMVC配置web.xml
<?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>SpringMVC</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 指定SpringMVC配置文件位置 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Servlet启动就加载,值越小优先级越高 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!-- /*和/都会拦截几乎所有请求
但是/*会拦截包括jsp页面的请求,而/不会
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置字符编码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>解决POST请求乱码</description>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<description>解决响应乱码</description>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- REST请求隐藏域过滤器,处理DELETE、PUT请求 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
3. SpringMVC配置文件
<?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: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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.zmj"></context:component-scan>
<!-- 告诉SpingMVC,如果自己不能处理的,交给Tomcat,比如处理静态资源 -->
<mvc:default-servlet-handler/>
<!-- 保证动态资源和静态资源都能访问-->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
4. SpringMVC注解
- @RequestMapping() 如果标注在方法上,则表示请求这个路径才能调用这个方法;如果标注在类上,则为当前类所有方法的请求地址指定一个基准路径;
- @PathVariable 映射URL绑定的占位符,用于REST风格
- @RequestParam() 写在方法形参前,获取请求参数且请求参数中必须有这个属性,否则报错,可以设置required=false解决;如果形参内参数跟它对应的请求参数一致,可以不用加这个注解,当没有加这个注解时,即使请求没有这个参数也不会报错,值为空;
- @RequestHeader() 获取请求头信息
- @CookieValue() 获取Cookie的值
- @SessionAttributes() 只能标记在类上;一般都是BindingAwareModelMap(包括Map / ModelMap / Model) 中保存的某些数据,临时在Session域中保存一份,value指定的是key的名字,types指定起作用的类型。但是这个注解会引起其他的异常,推荐不要使用,请使用用原生的API代替;
- @ResponseBody 将方法返回的数据放在响应体中并输出;如果是对象,则jackson包自动将对象转为json格式输出,如果对象里面有日期时,可以再标注一个注解@JsonFormat(pattern="yyyy-MM-dd"),这样有关日期的json位置就会转成你设置的字符串样式了
- @RequestBody 标记在方法的形参上,获取一个请求的请求体,将json格式的数据封装到形参里面;如果形参是对象,则根据传过来的值封装到对象中
5. REST风格
- 系统希望以非常简洁的URL地址发送请求,通过请求方式GET、POST、PUT、DELETE区分查询、新增、修改、删除;
- 从页面上只能发送两种请求GET、POST,其他请求方式通过HiddenHttpMethodFilter处理POST请求的中的_method隐藏域传递<input type="hidden" name="_method" value="DELETE"><input type="hidden" name="_method" value="PUT">;如果因为服务器版本问题造成报错405,则在页面加入 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true" %> 即可解决
6. SpringMVC源码分析
6.1 doDispatch代码大致流程
- 发送的所有请求最终都到DispatcherServlet
- 调用doDispatch()方法进行处理(3,4,5,6,7):
- getHandler() 根据当前请求地址找到处理这个请求的的目标处理器;
- getHandlerAdapter() 根据当前处理器获取能执行这个处理器方法的适配器;
- 根据刚才获取到的适配器AnnotationMethodHandlerAdapter获取目标方法;
- 目标方法执行完毕后返回一个ModelAndView对象;
- 根据ModelAndView的信息转发到具体的页面,并可以在请求域中取出ModelAndView中的模型数据
6.1.1 getHandler()细节
6.1.2 getHandlerAdapter细节
6.1.3 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 执行目标方法的细节
6.1.4 methodInvoker.invokeHandlerMethod详情
6.2 SpringMVC九大组件(接口、规范)
7. 关于<mvc:annotation-driven>
- 当把这个标签写入mvc配置文件中,会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter与ExceptionHandlerExceptionResolver三个Bean;
- 支持使用ConversionService实例对表单参数进行类型转换;
- 支持使用@NumberFormat、@DateTimeFormat注解完成数据类型的格式化;
- 支持使用@Valid注解对JavaBean实例进行JSR303校验;
- 支持使用@RequestBody和@ResponseBody注解;
- <mvc:default-servlet-handler/>和<mvc:annotation-driven>如果都没有配置,则@RequestMapping映射的资源可以访问,静态资源不能访问。原因是handlerMappings里面有DefaultAnnotationHandlerMapping,而DefaultAnnotationHandlerMapping的HandlerMap中保存着每一个资源的映射信息,但是没有保存静态资源信息;
- 如果只配置<mvc:default-servlet-handler/>,则静态资源能访问,@RequestMapping映射的资源不能访问。原因是handlerMappings里面的DefaultAnnotationHandlerMapping被SimpleUrlHandlerMapping替代了,将所有的请求都直接交给了Tomcat;
- 如果只配置<mvc:annotation-driven>,则@RequestMapping映射的资源可以访问,静态资源不能访问;
- <mvc:default-servlet-handler/>和<mvc:annotation-driven>如果都有配置,则RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping,从上到下依次匹配;RequestMappingHandlerMapping的handlerMethods属性保存了每一个请求用哪个方法来处理;SimpleUrlHandlerMapping将静态资源请求直接交给Tomcat;
8. SpringMVC自定义类型转换器
- Spring定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中:
- Converter<S,T> 将S类型对象转为T类型对象;
- ConverterFactory 将相同系列多个“同质”Converter封装在一起;
- GenericConverter 根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换;
8.1 定义步骤
8.1.1 自定义一个类型转换器,实现Converter接口,这里是将页面传递过来的字符串转为Employee对象接收
package com.springmvc.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import com.springmvc.entities.Department;
import com.springmvc.entities.Employee;
@Component
public class EmployeeConverter implements Converter<String, Employee>{
@Override
public Employee convert(String source) {
// MM-mm@zmj.com-0-105
if(source != null && source != ""){
String[] vals = source.split("-");
if(vals != null && vals.length == 4){
String lastName = vals[0];
String email = vals[1];
Integer gender = Integer.parseInt(vals[2]);
Integer departmentId = Integer.parseInt(vals[3]);
Department department = new Department();
department.setId(departmentId);
return new Employee(null, lastName, email, gender, department, null, 0);
}
}
return null;
}
}
8.1.2 在springmvc.xml中将Converter放入ConversionService中,然后将WebDataBinder中的ConversionService设置成我们这个加了自定义类型转换器的ConversionService
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.springmvc"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
<!-- 告诉SpingMVC,如果自己不能处理的,交给Tomcat,比如处理静态资源 -->
<mvc:default-servlet-handler/>
<!-- 保证动态资源和静态资源都能访问 配置的转换器是自己刚刚创建的-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 可以导入外部配置文件 -->
<!-- <import resource="springmvc_1.xml"/> -->
</beans>
8.1.3 在Controller中写入方法,通过@RequestParam注解拿到前端字符串转的对象
@RequestMapping(value="/conversion")
public String testConversion(@RequestParam("employee")Employee employee){
employeeDao.saveOrUpdate(employee);
return "redirect:/springmvc/employees";
}
9.SpringMVC校验和格式化
- @NotEmpty 设置在属性上,此属性不能为空
- @Length 在@NotEmpty的前提下,min=xxx,max=xxx
- @Email 设置在表示邮箱的属性上,校验是否是邮箱格式
- @Past 设置在表示日期的属性上,校验日期是否已经过期
Spring框架中@DateTimeFormat和@NumberFormat的用法
10 . SpringMVC的拦截器
- SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截操作,或者在目标方法之后进行一些其他处理
- 拦截器是一个接口org.springframework.web.servlet.HandlerInterceptor :
- preHandle : 在目标方法运行之前调用;返回boolean;return true,则chain.doFilter()放行;return false,则不放行;
- postHandle : 在目标方法运行之后调用;
- afterCompletion : 在请求整个资源响应之后,来到目标页面之后,chain.doFilter()放行
- 一个拦截器的运行流程:preHandle---目标方法---postHandle---页面---afterCompletion
- 多个拦截器的运行流程:preHandle(顺序)---目标方法---postHandle(逆序)---页面---afterCompletion(逆序)
- 不管一个还是多个,已经放行的拦截器,肯定会执行afterCompletion
package com.bestgo.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@RequestMapping("/test01")
public String test01(){
System.out.println("test01方法被调用");
return "success";
}
}
package com.bestgo.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 1.实现HandlerInterceptor接口
* 2.在SpringMVC配置文件中注册这个拦截器
*/
public class FirstInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
System.out.println("FirstInterceptor.afterCompletion........");
}
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView arg3) throws Exception {
System.out.println("FirstInterceptor.postHandle........");
}
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2) throws Exception {
System.out.println("FirstInterceptor.preHandle........");
return true;
}
}
package com.bestgo.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 1.实现HandlerInterceptor接口
* 2.在SpringMVC配置文件中注册这个拦截器
*/
public class SecondInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
System.out.println("SecondInterceptor.afterCompletion........");
}
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView arg3) throws Exception {
System.out.println("SecondInterceptor.postHandle........");
}
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2) throws Exception {
System.out.println("SecondInterceptor.preHandle........");
return true;
}
}
<!-- 在SpringMVC配置文件中配置 -->
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 这里配置的是拦截所有的请求 -->
<bean class="com.bestgo.springmvc.interceptor.FirstInterceptor"></bean>
<mvc:interceptor>
<!-- 这里配置的拦截某个请求 -->
<mvc:mapping path="/test01"/>
<bean class="com.bestgo.springmvc.interceptor.SecondInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
11. SpringMVC的异常处理机制
- 默认的解析器有ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver;如果遇到异常后,这些都解析不了,则抛出异常
- ExceptionHandlerExceptionResolver是处理标注了@ExceptionHandler的方法抛出的异常;
- ResponseStatusExceptionResolver是处理标注了@ResponseStatus的方法抛出的异常;
- DefaultHandlerExceptionResolver是处理SpringMVC自带的异常
12. SpringMVC的运行流程
- 所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatch进行处理;
- 根据HandlerMapping中保存的请求映射信息找到处理当前请求的处理器执行链 (包含拦截器)
- 根据当前处理器找到它的的HandlerAdapter(适配器)
- 拦截器preHandle先执行
- 适配器执行目标方法:
- ModelAttribute注解标注的方法提前运行;
- 执行目标方法的时候(确定目标方法用的参数)
- 有注解
- 没有注解:
- 看是否有Model、Map以及其它
- 如果是自定义类型:
- 从隐含模型中看有没有,如果有则从隐含模型中拿
- 如果没有,则再看是否有SessionAttribute标注的属性,如果从Session中拿,拿不到则会抛异常;
- 如果都不是,则利用反射创建对象
- 拦截器的postHandle执行
- 处理结果(页面渲染流程):
- 如果有异常,使用异常解析器处理异常;处理完毕后会返回ModelAndView
- 调用render进行页面渲染
- 视图解析器根据视图得到视图对象
- 视图对象调用render方法
- 执行拦截器的afterCompletion
13. SpringMVC与Spring的整合
- SpringMVC与Spring的整合,就是为了分工明确;
- SpringMVC容器是Spring容器的子容器
- 子容器可以访问父容器,但父容器不能访问子容器,也就是说Controller只能找Service/Dao
- SpringMVC配置
- 和网站转发逻辑以及网站功能有关的内容
- 比如视图解析器、文件上传解析器、支持AJAX
- 管理控制器组件
- Spring配置
- 和业务有关的内容
- 比如事务控制、数据源
- 管理业务逻辑组件
13.1 Spring与SpringMVC关联的三个配置 web.xml spring.xml springmvc.xml
<!-- 这些是在web.xml配置Spring与SpringMVC两个容器 -->
<!-- 配置Spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置SpringMVC前端控制器 -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 开启Spring注解扫描 -->
<context:component-scan base-package="com.springmvc">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
<!-- 开启SpringMVC注解扫描 -->
<context:component-scan base-package="com.springmvc" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>