Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven

1. 前端控制器DispatcherServlet

架构

Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第1张图片

doDispatch()执行过程

过程:
(1) 所有的请求过来,DispatcherServlet收到请求,

(2) 调用doDispatch()方法进行处理

  1. getHandler():根据当前请求在HandlerMapping中找到这个请求的映射信息,获取到目标处理器类;HandlerMapping包含BeanNameUrlHandlerMapping(不用)、DefaultAnnotationHandlerMapping

  2. getHandlerAdapter():根据当前处理器类获取到能执行这个处理器方法的适配器;handlerAdapters包含AnnotationMethodHandlerAdapterHttpRequestHandlerAdapterSimpleControllerHandlerAdapter

  3. 使用获取到的适配器(DefaultAnnotationMethodHandlerAdapter )执行目标方法

  4. 目标方法执行后返回一个ModelAndView对象

  5. 根据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;
        }
    }

SpringMVC 九大组件

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文件);

2. 视图解析

@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会为路径自动的拼接上项目名

3. 表单标签(实现简单CRUD)

通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显。

SpringMVC认为,表单数据中的每一项最终都是要回显的

  • path对应 html 元素的 name 属性,支持级联属性
  • path指定的每一个属性,请求域中必须有一个对象,拥有这个属性;这个对象就是默认是请求域中的command
  • Spring MVC的表单标签会从请求域中获取一个command对象,并把这command对象中的每一个属性对应的显示出来
  • Spring MVC会去取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>

4.数据转换 & 数据格式化 & 数据校验

数据绑定流程

  1. Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 WebDataBinder 实例对象
  2. WebDataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
  3. 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象
  4. Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
    Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第2张图片

自定义类型转换器

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 处理方法入参绑定等场合使用它进行数据的转换
  • 可通过 ConversionServiceFactoryBeanconverters 属性注册自定义的类型转换器

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 进行验证:
Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第3张图片
① 使用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
    ];
  1. 如果是隐含模型中employee对象的email属性字段发生了@Email校验错误,就会生成 Email.employee.email;
  2. Email.email:所有的email属性只要发生了@Email错误;
  3. Email.java.lang.String,:只要是String类型发生了@Email错误
  4. Email:只要发生了@Email校验错误;

配置SpringMVC管理国际化资源文件:

	
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="errors">property>
    bean>

5. mvc:annotation-driven标签原理

会自动注册RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver 三个bean。

还将提供以下支持:

  • 支持使用 ConversionService 实例对表单参数进行类型转换
  • 支持使用 @NumberFormat annotation@DateTimeFormat 注解完成数据类型的格式化
  • 支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
  • 支持使用 @RequestBody@ResponseBody 注解

情况① 都不加
在这里插入图片描述
Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第4张图片
Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第5张图片
动态资源能访问:DefaultAnnotationHandlerMapping中的handlerMap中保存了每一个资源的映射信息;静态资源不能访问:handlerMap中没有保存静态资源映射的请求,

情况②:只加
Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第6张图片
在这里插入图片描述
动态不能访问:DefaultAnnotationHandlerMapping没有了,取而代之的是SimpleUrlHandlerMapping,其作用是将所有请求直接交给 tomcat(DefaultServlet);静态能访问的原因:SimpleUrlHandlerMapping把所有请求都映射给tomcat;

情况③:加

HandlerMapping的变化
Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第7张图片

  • SimpleUrlHandlerMapping:将请求直接交给tomcat(静态资源可访问)其中的handleMethods属性保存了请求与方法的映射;
  • RequestMappingHandlerMapping:动态资源可以访问

HandlerAdapter的变化: 原来的AnnotationMethodHandlerAdapter被换成RequestMappingHandlerAdapter
Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第8张图片
改用成解析器确定参数:
Spring MVC 学习(3)DispatcherServlet原理、表单标签(CRUD)、数据转换、mvc:annotation-driven_第9张图片

6. 处理JOSN、AJAX

处理JSON

①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);
	}

你可能感兴趣的:(SSM框架,spring,java,web)