DispatcherServlet
doDispatch()
执行过程过程:
(1) 所有的请求过来,DispatcherServlet
收到请求,
(2) 调用doDispatch()
方法进行处理
getHandler()
:根据当前请求在HandlerMapping
中找到这个请求的映射信息,获取到目标处理器类;HandlerMapping
包含BeanNameUrlHandlerMapping
(不用)、DefaultAnnotationHandlerMapping
getHandlerAdapter()
:根据当前处理器类获取到能执行这个处理器方法的适配器;handlerAdapters
包含AnnotationMethodHandlerAdapter
、HttpRequestHandlerAdapter
、SimpleControllerHandlerAdapter
使用获取到的适配器(DefaultAnnotationMethodHandlerAdapter
)执行目标方法
目标方法执行后返回一个ModelAndView
对象
根据ModelAndView
的信息转发到具体的页面,并可以在请求域中取出ModelAndView
中的模型数据
细节:
(1) getHandler()
如何根据当前请求就能找到哪个类(处理器/handler)能来处理:
getHandler()
会返回目标处理器类的执行链(HandlerExecutionChain
);handlerMap
——保存了每一个资源的映射信息,保存在DefaultAnnotationHandlerMapping
中;protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//handlerMappings处理器映射——保存了
//每一个处理器能处理哪些请求的映射信息,包含
//1. BeanNameUrlHandlerMapping(基于配置)
//2. DefaultAnnotationHandlerMapping(基于注解)
//例如DefaultAnnotationHandlerMapping的
//handlerMap属性保存了请求映射信息
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;
}
(2) 如何找到目标处理器类的适配器HandlerAdapter
(拿到适配器才去执行目标方法)
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//handlerAdapters中的AnnotationMethodHandlerAdapter属性
//是能解析注解方法的适配器
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");
}
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、检查是否文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// Determine handler for the current request.
//2、根据当前的请求地址找到那个类能来处理;
mappedHandler = getHandler(processedRequest);
//3、如果没有找到哪个处理器(控制器)能处理这个请求就404,或者抛异常
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//4、拿到能执行这个类的所有方法的注解方法适配器;(反射工具AnnotationMethodHandlerAdapter)
//mappedHandler.getHandler()会拿到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()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.处理(控制)器的方法被调用
//控制器(Controller),处理器(Handler)
//5、适配器来执行目标方法;(若return String)将目标方法执行完成后的返回值作为视图名,
设置保存到ModelAndView中
//目标方法无论怎么写,最终适配器执行完成以后都会将执行后的信息封装成ModelAndView
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;
}
//转发到目标页面;
//6、根据方法最终执行完成后封装的ModelAndView;转发到对应页面,而且ModelAndView中的数据可以从请求域中获取
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);
}
}
}
(3)方法执行的细节invokeHandlerMethod
:
方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
会调用
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//拿到方法的解析器(知道标了哪些注解)
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
//方法解析器根据当前请求地址找到真正的目标方法
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
//创建一个方法执行器;
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
//包装原生的request, response,
ServletWebRequest webRequest = new ServletWebRequest(request, response);
//创建了一个隐含模型
ExtendedModelMap implicitModel = new BindingAwareModelMap();
//真正执行目标方法;目标方法利用反射执行期间确定参数值,提前执行modelattribute等所有的操作都在这个方法中;
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}
进入invokeHandlerMethod
public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
try {
boolean debug = logger.isDebugEnabled();
for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
if (attrValue != null) {
//若标注了SessionAttribute,则放入隐含模型
implicitModel.addAttribute(attrName, attrValue);
}
}
//找到所有@ModelAttribute注解标注的方法;
for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
//先确定modelattribute方法执行时要使用的每一个参数的值;
Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
}
String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
continue;
}
ReflectionUtils.makeAccessible(attributeMethodToInvoke);
//提前运行ModelAttribute,
Object attrValue = attributeMethodToInvoke.invoke(handler, args);
if ("".equals(attrName)) {
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
}
//把提前运行的ModelAttribute方法的返回值也放在隐含模型中
if (!implicitModel.containsAttribute(attrName)) {
implicitModel.addAttribute(attrName, attrValue);
}
}
//再次解析目标方法参数是哪些值
Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
}
ReflectionUtils.makeAccessible(handlerMethodToInvoke);
//执行目标方法
return handlerMethodToInvoke.invoke(handler, args);
}
catch (IllegalStateException ex) {
// Internal assertion failed (e.g. invalid signature):
// throw exception with full handler method context...
throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
}
catch (InvocationTargetException ex) {
// User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
ReflectionUtils.rethrowException(ex.getTargetException());
return null;
}
}
DispatcherServlet的9个引用类型的属性被称为Spring MVC 九大组件,SpringMVC 工作时,关键位置都是由这些组件完成
//9大组件都是接口,接口就是规范,提供了非常强大的扩展性
/** 文件上传解析器*/
private MultipartResolver multipartResolver;
/** 区域信息解析器;和国际化有关 */
private LocaleResolver localeResolver;
/** 主题解析器;强大的主题效果更换 */
private ThemeResolver themeResolver;
/** Handler映射信息;HandlerMapping */
private List<HandlerMapping> handlerMappings;
/** Handler的适配器 */
private List<HandlerAdapter> handlerAdapters;
/** SpringMVC强大的异常解析功能;异常解析器 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** */
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMap+Manager:SpringMVC中运行重定向携带数据的功能 */
private FlashMapManager flashMapManager;
/** 视图解析器; */
private List<ViewResolver> viewResolvers;
DispatcherServlet
中九大组件初始化的地方:
//spring源码中留给子类的
@Override protected void onRefresh(ApplicationContext context){
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
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");
}
}
}
初始化过程:
有些组件在容器中是使用类型找的,有些组件是使用id找的;
去容器中找这个组件,如果没有找到就用默认的配置(DispatcherServlet
同路径下的一个properties文件);
@RequestMapping("/hello")
public String hello(){
// /WEB-INF/pages/hello.jsp /hello.jsp
//相对路径
return "../../hello";
}
@RequestMapping("/handle01")
public String handle01(){
System.out.println("handle01");
return "forward:/hello.jsp";
}
@RequestMapping("/handle02")
public String handle02(){
System.out.println("handle02");
return "forward:/handle01";
}
@RequestMapping("/handle03")
public String handle03(){
System.out.println("handle03....");
return "redirect:/hello.jsp";
}
//共三个请求:handle04、handle03、hello
@RequestMapping("/handle04")
public String handle04(){
System.out.println("handle04...");
return "redirect:/handle03";
}
有前缀的转发和重定向操作,配置的视图解析器就不会进行拼串;前缀路径须加上/
,否则是相对路径
forward:
——转发到一个页面redirect:
重定向到一个页面;原生的Servlet重定向/路径
需要加上项目名,而Spring MVC会为路径自动的拼接上项目名通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显。
SpringMVC认为,表单数据中的每一项最终都是要回显的
path
对应 html 元素的 name 属性,支持级联属性path
指定的每一个属性,请求域中必须有一个对象,拥有这个属性;这个对象就是默认是请求域中的command
command
对象,并把这command
对象中的每一个属性对应的显示出来modelAttribute
设置的指定对象名(取对象用的key由modelAttribute
指定)<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<h1>员工添加(原生html)h1>
<form action="">
lastName:<input type="text" name="lastName"/><br/>
email:<input type="text" name="email"/><br/>
gender:<br/>
男:<input type="radio" name="gender" value="1"/><br/>
女:<input type="radio" name="gender" value="0"/><br/>
dept:<select name="department.id">
<c:forEach items="${requestScope.depts }" var="deptItem">
<option value="${deptItem.id }">${deptItem.departmentName }option>
c:forEach>
select>
<input value="添加" type="submit"/>
form>
=======================================================
<h1>员工添加(springMVC表单标签)h1>
<%pageContext.setAttribute("ctp", request.getContextPath());%>
<form:form action="${ctp }/emp" modelAttribute="employee" method="POST">
lastName:<form:input path="lastName"/><br/>
email:<form:input path="email"/><br/>
gender:<br/>
男:<form:radiobutton path="gender" value="1"/><br/>
女:<form:radiobutton path="gender" value="0"/><br/>
dept:
<form:select path="department.id"
items="${depts }"
itemLabel="departmentName"
itemValue="id">form:select><br/>
<input type="submit" value="保存"/>
form:form>
body>
html>
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(Employee employee) {
System.out.println("要添加的员工:" + employee);
employeeDao.save(employee);
// 返回列表页面;重定向到查询所有员工的请求
return "redirect:/emps";
}
//去员工添加页面,去页面之前需要查出所有部门信息,进行展示的
@RequestMapping("/toaddpage")
public String toAddPage(Model model) {
// 1、先查出所有部门
Collection<Department> departments = departmentDao.getDepartments();
// 2、放在请求域中
model.addAttribute("depts", departments);
model.addAttribute("employee", new Employee());
// 3、去添加页面
return "add";
}
使用修改请求方式为
PUT
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
<%
pageContext.setAttribute("ctp", request.getContextPath());
%>
head>
<body>
<h1>员工修改页面h1>
<form:form action="${ctp }/emp/${employee.id }"
modelAttribute="employee" method="post">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="id" value="${employee.id }"/>
email:<form:input path="email"/><br/>
gender:
男:<form:radiobutton path="gender" value="1"/>
女:<form:radiobutton path="gender" value="0"/><br/>
dept:
<form:select path="department.id" items="${depts }"
itemLabel="departmentName" itemValue="id">form:select>
<br/>
<input type="submit" value="修改"/>
form:form>
body>
html>
@RequestMapping(value = "/emp/{id}", method = RequestMethod.GET)
public String getEmp(@PathVariable("id") Integer id, Model model) {
// 1、查出员工信息
Employee employee = employeeDao.get(id);
// 2、放在请求域中
model.addAttribute("employee", employee);
// 3、继续查出部门信息放在隐含模型中
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts", departments);
return "edit";
}
@RequestMapping(value = "/emp/{id}", method = RequestMethod.PUT)
public String updateEmp(@ModelAttribute("employee")Employee employee/* ,@PathVariable("id")Integer id */) {
System.out.println("要修改的员工:" + employee);
// xxxx 更新保存二合一;
employeeDao.save(employee);
return "redirect:/emps";
}
若想引入jQuery文件,须配置springmvc.xml
<mvc:default-servlet-handler/>
<mvc:annotation-driven>mvc:annotation-driven>
WebDataBinder
实例对象WebDataBinder
调用装配在 Spring MVC 上下文中的 ConversionService
组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中Validator
组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData
对象BindingResult
中的入参对象和校验错误对象,将它们赋给处理方法的响应入参ConversionService
中有非常多的converter
,用于不同类型的转换和格式化
ConversionService converters =
– java.lang.Boolean -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@f874ca
– java.lang.Character -> java.lang.Number : CharacterToNumberFactory@f004c9
– java.lang.Character -> java.lang.String : ObjectToStringConverter@68a961
– java.lang.Enum -> java.lang.String : EnumToStringConverter@12f060a
– java.lang.Number -> java.lang.Character : NumberToCharacterConverter@1482ac5
– java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory@126c6f
– java.lang.Number -> java.lang.String : ObjectToStringConverter@14888e8
– java.lang.String -> java.lang.Boolean : StringToBooleanConverter@1ca6626
ConversionService
是 Spring 类型转换体系的核心接口。ConversionServiceFactoryBean
在 Spring 的 IOC 容器中定义一个 ConversionService
. Spring 将自动识别出 IOC 容器中的 ConversionService
,并在 Bean 属性配置及 Spring MVC 处理方法入参绑定等场合使用它进行数据的转换ConversionServiceFactoryBean
的 converters
属性注册自定义的类型转换器Spring 定义了转换器接口Converter
,将 S 类型对象转为 T 类型对象;实现接口可作为自定义转换器注册到 ConversionServiceFactroyBean
中
步骤
①实现Converter
接口
/**
* S:Source T:Target 将s转为t
*/
public class MyStringToEmployeeConverter implements Converter<String, Employee> {
@Autowired
DepartmentDao departmentDao;
/**
* 自定义的转换规则
*/
@Override
public Employee convert(String source) {
// TODO Auto-generated method stub
// [email protected]
System.out.println("页面提交的将要转换的字符串" + source);
Employee employee = new Employee();
if (source.contains("-")) {
String[] split = source.split("-");
employee.setLastName(split[0]);
employee.setEmail(split[1]);
employee.setGender(Integer.parseInt(split[2]));
employee.setDepartment(departmentDao.getDepartment(Integer.parseInt(split[3])));
}
return employee;
}
}
②将Converter
配置在ConversionService
中,并通知配置SpringMVC
<mvc:annotation-driven conversion-service="conversionService">mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.guigu.component.MyStringToEmployeeConverter">bean>
set>
property>
bean>
③测试
@RequestMapping("/quickadd")
public String quickAdd(@RequestParam("empinfo") Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}
//假设页面,为了显示方便提交的工资是 ¥10,000.98
@NumberFormat(pattern="#,###.##")
private Double salary;
//规定页面提交的日期格式
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth = new Date();
Spring MVC可以JSR303来做数据校验,JSR303通过在 Bean 属性上标注类似于 @NotNull
、@Max
等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证:
① 使用Hibernate Validator进行后端校验
导入校验框架jar包:
hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
classmate-0.8.0.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar
给javaBean的属性添加校验注解:
@NotEmpty(message="不能为空")
@Length(min=5,max=17,message="我错了")
private String lastName;
@Email
private String email;
//1 male, 0 female
private Integer gender;
//规定页面提交的日期格式
//@Past:必须是一个过去的时间
//@Future :必须是一个未来的时间
@DateTimeFormat(pattern="yyyy-MM-dd")
@Past
private Date birth = new Date();
在SpringMVC封装对象时,告诉SpringMVC,这个javaBean需要校验:
public String addEmp(@Valid Employee employee) {
}
如何知道校验结果?给需要校验的 javaBean 后面紧跟一个 BindingResult
。这个BindingResult
就封装了前一个bean的校验结果;
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult result,
Model model) {
// 获取是否有校验错误
boolean hasErrors = result.hasErrors();
Map<String, Object> errorsMap = new HashMap<String, Object>();
if (hasErrors) {
List<FieldError> errors = result.getFieldErrors();
for (FieldError fieldError : errors) {
System.out.println("错误消息提示:" + fieldError.getDefaultMessage());
System.out.println("错误的字段是?" + fieldError.getField());
System.out.println(fieldError);
System.out.println("------------------------");
errorsMap.put(fieldError.getField(),
fieldError.getDefaultMessage());
}
model.addAttribute("errorInfo", errorsMap);
System.out.println("有校验错误");
return "add";
} else {
employeeDao.save(employee);
// 返回列表页面;重定向到查询所有员工的请求
return "redirect:/emps";
}
}
前端显示校验错误:
<form:form action="${ctp }/emp" modelAttribute="employee" method="POST">
lastName:<form:input path="lastName"/>
<form:errors path="lastName"/>-->${
errorInfo.lastName }
<br/>
email:<form:input path="email"/>
<form:errors path="email"/>-->${
errorInfo.email }
<br/>
gender:<br/>
男:<form:radiobutton path="gender" value="1"/><br/>
女:<form:radiobutton path="gender" value="0"/><br/>
birth:<form:input path="birth"/>
<form:errors path="birth"/>--->${
errorInfo.birth }
<br/>
dept:<form:select path="department.id"
items="${depts }"
itemLabel="departmentName"
itemValue="id"></form:select><br/>
<input type="submit" value="保存"/>
</form:form>
Map<String, Object> errorsMap = new HashMap<String, Object>();
if (hasErrors) {
List<FieldError> errors = result.getFieldErrors();
for (FieldError fieldError : errors) {
errorsMap.put(fieldError.getField(),fieldError.getDefaultMessage());
}
model.addAttribute("errorInfo", errorsMap);
国际化定制错误消息:
编写国际化的文件:
errors_zh_CN.properties
errors_en_US.properties
当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码;国际化文件中错误消息的key必须对应一个错误代码:
codes
[
Email.employee.email, 校验规则.隐含模型中这个对象的key.对象的属性
Email.email, 校验规则.属性名
Email.java.lang.String, 校验规则.属性类型
Email
];
@Email
校验错误,就会生成 Email.employee.email;@Email
错误;@Email
错误@Email
校验错误;配置SpringMVC管理国际化资源文件:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="errors">property>
bean>
mvc:annotation-driven
标签原理
会自动注册RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
与 ExceptionHandlerExceptionResolver
三个bean。
还将提供以下支持:
ConversionService
实例对表单参数进行类型转换@NumberFormat annotation
、@DateTimeFormat
注解完成数据类型的格式化@Valid
注解对 JavaBean 实例进行 JSR 303 验证@RequestBody
和 @ResponseBody
注解情况①:
、
都不加
动态资源能访问:DefaultAnnotationHandlerMapping
中的handlerMap
中保存了每一个资源的映射信息;静态资源不能访问:handlerMap
中没有保存静态资源映射的请求,
情况②:只加
动态不能访问:DefaultAnnotationHandlerMapping
没有了,取而代之的是SimpleUrlHandlerMapping
,其作用是将所有请求直接交给 tomcat(DefaultServlet
);静态能访问的原因:SimpleUrlHandlerMapping
把所有请求都映射给tomcat;
情况③:加
、
SimpleUrlHandlerMapping
:将请求直接交给tomcat(静态资源可访问)其中的handleMethods
属性保存了请求与方法的映射;RequestMappingHandlerMapping
:动态资源可以访问HandlerAdapter的变化: 原来的AnnotationMethodHandlerAdapter
被换成RequestMappingHandlerAdapter
改用成解析器确定参数:
①javabean属性处的注解
@JsonIgnore
private Department department;
@DateTimeFormat(pattern="yyyy-MM-dd")
@Past
@JsonFormat(pattern="yyyy-MM-dd")
private Date birth = new Date();
②测试
/**
* 将返回的数据放在响应体中;
* 如果是对象,jackson包自动将对象转为json格式
* @return
*/
@ResponseBody
@RequestMapping("/getallajax")
public Collection<Employee> ajaxGetAll(){
Collection<Employee> all = employeeDao.getAll();
return all;
}
/**
* @RequestBody:请求体;获取一个请求的请求体,可用String接收
* @RequestParam:
*
* @ResponseBody:可以把对象转为json数据,返回给浏览器
*
* @RequestBody:接收json数据,直接封装为对象
*/
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody Employee employee){
System.out.println("请求体:"+employee);
return "success";
}
/**
* 如果参数位置写HttpEntity str;
* 比@RequestBody更强,可以拿到请求头;
* @RequestHeader("")只能拿到某个请求头
*/
@RequestMapping("/test02")
public String test02(HttpEntity<String> str){
System.out.println(str);
return "success";
}
ResponseBody
的本质是将返回数据放在响应体中
/*
* ResponseEntity:响应体中内容的类型
*/
//@ResponseBody
@RequestMapping("/haha")
public ResponseEntity<String> hahah(){
MultiValueMap<String, String> headers = new HttpHeaders();
String body = "success
";
headers.add("Set-Cookie", "username=hahahaha");
return new ResponseEntity<String>(body , headers, HttpStatus.OK);
}
应用:文件下载
/**
* SpringMVC文件下载;
*/
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception{
//1、得到要下载的文件的流;
//找到要下载的文件的真实路径
ServletContext context = request.getServletContext();
String realPath = context.getRealPath("/scripts/jquery-1.9.1.min.js");
FileInputStream is = new FileInputStream(realPath);
byte[] tmp = new byte[is.available()];
is.read(tmp);
is.close();
//2、将要下载的文件流返回
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Disposition", "attachment;filename="+"jquery-1.9.1.min.js");
return new ResponseEntity<byte[]>(tmp, httpHeaders, HttpStatus.OK);
}