微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

SpringMVC

SpringMVC

概述

SpringMVC是Spring框架提供的构建web程序的模块

流程

  1. 导包

    commons-logging-1.1.3.jar
    spring-aop-4.0.0.RELEASE.jar
    spring-beans-4.0.0.RELEASE.jar
    spring-context-4.0.0.RELEASE.jar
    spring-core-4.0.0.RELEASE.jar
    spring-expression-4.0.0.RELEASE.jar
    spring-web-4.0.0.RELEASE.jar
    spring-webmvc-4.0.0.RELEASE.jar

  2. 写配置

web.xml的配置内容

配置SpringMVC的前端控制器

<!--配置前端控制器拦截所有请求-->

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.dispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!--
        /:拦截所有请求,不包括*.jsp
        /*:拦截所有请求,且包括*.jsp
        -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

若不在前端控制器指定SpringMVC配置文件的位置,则需在WEB-INF目录下创建一个名为“前端控制器名-servlet.xml”的SpringMVC配置文件

dispatcherServlet-servlet.xml的配置内容

<context:component-scan base-package="com.th1024"></context:component-scan>

<!--配置视图解析器,简化方法返回值,在跳转时视图解析器可以自动拼串跳转-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>
  1. 测试
@Controller //添加@Controller表名这个类是一个处理器
public class MyController {
	//表名该方法用于处理哪个请求
    @RequestMapping("/handle01")
    public String handle01(){
        System.out.println("MyController handle01...");
        return "success";
    }
}

细节

  • 运行流程
  1. 客户端点击链接,发送http://localhost:8080/1_HelloWorld/handle01请求
  2. 请求发送到Tomcat服务器,并被SpringMVC的前端控制器dispatcherServlet接收
  3. 查看请求地址是否和@RequestMapping标注的值匹配,确定处理请求调用的类及方法
  4. 前端控制器利用反射执行目标方法
  5. 方法执行完成之后会返回跳转页面地址
  6. 利用视图解析器进行拼串得到完整的页面地址
  7. 前端控制器将请求转发到页面
  • / 和 /*

Tomcat服务器中有两个Servlet,分别是DefaultServlet和JspServlet,DefaultServlet的url-pattern为/,JspServlet的url-pattern为/*,DefaultServlet是Tomcat服务器用于处理请求静态资源的Servlet,JspServlet是Tomcat服务器用于处理请求动态资源的Servlet,而配置的dispatcherServlet的url-pattern也为/,则dispatcherServlet相当于覆盖了Tomcat服务器的DefaultServlet,请求静态资源会由dispatcherServlet处理,而.jsp等动态资源仍由Tomcat服务器处理

  • @RequestMapping

@RequestMapping("/handle01")标注在方法上,表名此方法应该处理什么类型的请求,“/handle01”表名请求的地址为当前项目下加上handle01

@RequestMapping("/haha")标注在类上,则表示此类下的所有方法处理的请求地址均为当前工程下/haha/各方法的@RequestMapping的value值

@RequestMapping("/haha")
@Controller
public class RequestMappingTestController {

    @RequestMapping("/handle01")
    public String handle01(){
        System.out.println("RequestMappingTestController handle01...");
        return "error";
    }

    /*
    RequestMapping的其他属性
    method:限定请求方式
        method = RequestMethod.POST:限定只接受POST请求

    params:规定请求的参数,支持简单的表达式
        param1:表示请求必须包含名为param1的请求参数
        !param1:表示请求不能包含名为param1的请求参数
        param1 != value1:表示请求包含名为param1的请求参数,但其值不能为value1
        {"param1=value1","param2"}:请求必须同时满足两个表达式,即包含param1和param2,且param1的值必须为value1
            params = {"username!=123","pwd","!age"}):发送请求时必须带有username,pwd参数,必须没有age参数,且username参数不能为123

    headers:规定请求头,和params一样能写简单的表达式

    consumes:
    produces
     */

    @RequestMapping(value = "/handle02",method = RequestMethod.POST)
    public String handle02(){
        System.out.println("handle02...");
        return "success";
    }

    @RequestMapping(value = "/handle03",params = {"username!=123","pwd","!age"})
    public String handle03(){
        System.out.println("handle03...");
        return "success";
    }

    /*
    设置headers,User-Agent:浏览器信息
    让Edge不能访问,Chrome能访问

    Chrome:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36
    Edge:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36 Edg/90.0.818.42
     */
    @RequestMapping(value = "/handle04",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36"})
    public String handle04(){
        System.out.println("handle04...");
        return "success";
    }
}
@Controller
public class RequestMappingTest {
	
	@RequestMapping("/antTest01")
	public String antTest01(){
		System.out.println("antTest01...");
		return "success";
	}
	
	/*
	  ?匹配一个字符,0个多个都不行;
	  		模糊和精确多个匹配情况下,精确优先
	 */
	@RequestMapping("/antTest0?")
	public String antTest02(){
		System.out.println("antTest02...");
		return "success";
	}
	
	/*
	    *匹配任意多个字符
	 */
	@RequestMapping("/antTest0*")
	public String antTest03(){
		System.out.println("antTest03...");
		return "success";
	}
	
	/*
	   *:匹配一层路径

	 */
	@RequestMapping("/a/*/antTest01")
	public String antTest04(){
		System.out.println("antTest04...");
		return "success";
	}
	
	@RequestMapping("/a/**/antTest01")
	public String antTest05(){
		System.out.println("antTest05...");
		return "success";
	}
	
	//路径上可以有占位符:  占位符 语法就是可以在任意路径的地方写一个{变量名}
	//   /user/admin
	// 路径上的占位符只能占一层路径
    //@PathVariable("变量名")获取请求路径上的参数
	@RequestMapping("/user/{id}")
	public String pathVariableTest(@PathVariable("id")String id){
		System.out.println("路径上的占位符的值"+id);
		return "success";
	}
}

REST

系统希望以非常简洁的URL地址来发送请求

之前的增删改查请求

/getBook?id=1:查询1号图书

/deleteBook?id=1:删除1号图书

/updateBook?id=1:更新1号图书

/addBook:添加图书

怎么用请求方式来区分对一个资源的增删改查?

REST风格的URL地址:/资源名/资源标识符

/book/1:GET--查询1号图书

/book/1:PUT--更新1号图书

/book/1:DELETE--删除1号图书

/book:POST--添加图书

问题:从页面只能发起两种请求,GET、POST


使用REST来构建一个增删改系统

处理程序

@RequestMapping("/book")
@Controller
public class BookController {

    @RequestMapping(value = "",method = RequestMethod.POST)
    public String addBook(){
        System.out.println("添加图书");
        return "success";
    }

    @RequestMapping(value = "/{bid}",method = RequestMethod.DELETE)
    public String deleteBook(@PathVariable("bid") Integer id){
        System.out.println("删除"+ id +"号图书");
        return "success";
    }

    @RequestMapping(value = "/{bid}",method = RequestMethod.PUT)
    public String updateBook(@PathVariable("bid") Integer id){
        System.out.println("更新"+ id +"号图书");
        return "success";
    }

    @RequestMapping(value = "/{bid}",method = RequestMethod.GET)
    public String getBook(@PathVariable("bid") Integer id){
        System.out.println("获取"+ id +"号图书");
        return "success";
    }

}

配置将请求转化为规定形式的filter

<!--将普通请求转化为规定形式的请求-->
<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>

HiddenHttpMethodFilter类中转换请求方式的方法

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws servletexception, IOException {

   String paramValue = request.getParameter(this.methodParam);
   if ("POST".equals(request.getmethod()) && StringUtils.hasLength(paramValue)) {
      String method = paramValue.toupperCase(Locale.ENGLISH);
      HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
      filterChain.doFilter(wrapper, response);
   }
   else {
      filterChain.doFilter(request, response);
   }
}

使用REST风格的URL地址

  1. 创建一个post类型的表单
  2. 表单中携带一个_method参数
  3. _method的值为请求方式,如DELETE、PUT
<%--
使用REST风格的URL地址
请求url   请求方式    表示含义
/book/1   GET        查询1号图书
/book/1   DELETE     删除1号图书
/book/1   PUT        更新1好图书
/book     POST       添加1号图书
--%>
<h1>测试REST风格的URL地址</h1>
<a href="book/1">查询1号图书</a><br/>
<form action="book" method="post">
  <input type="submit" value="添加图书">
</form>

<%--发送DELETE请求--%>
<form action="book/1" method="post">
  <input type="submit" value="删除1号图书">
  <input name="_method" value="delete" type="hidden">
</form>

<%--发送PUT请求--%>
<form action="book/1" method="post">
  <input type="submit" value="更新1号图书">
  <input name="_method" value="PUT" type="hidden">
</form>

解决高版本Tomcat对REST不支持的问题

添加isErrorPage="true"

<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>

请求参数

/*
   SpringMVC如何获取请求带来的各种信息,获取请求参数的方式:直接给方法形参列表写一个和请求参数同名的变量,此变量就用来接收请求参数的值
   @RequestParam:获取请求参数,认请求必须携带该参数
   @RequestParam("user") String user == String user = request.getParameter("user");

   @RequestParam("user")
@PathVariable("user")
           /book/【{user}pathvariable】?【user=admin(requestparam)】
               value:指定要获取的参数的key required:这个参数是否必须的
               defaultValue:认值。没带认是null;

   @RequestHeader:获取请求头中某个key的值
   @RequestHeader("User-Agent") String userAgent == String userAgent = request.getParameter("User-Agent");

   @CookieValue:获取某个Cookie的值
    */

   @RequestMapping(value = "/handle01")
   public String handle01(@RequestParam(value = "user",required = false,defaultValue = "defaultUser") String user,
                          @RequestHeader(value = "User-Agent",required = false,defaultValue = "defaultUserAgent") String userAgent,
                          @CookieValue(value = "JSESSIONID",required = false) String cookieValue){
       System.out.println("变量:" + user);
       System.out.println("请求头信息" + userAgent);
       System.out.println("cookie的值" + cookieValue);
       return "success";
   }
/*
请求的参数如果是一个POJO,SpringMVC会自动为这个POJO赋值
1. 将POJO中的每一个属性,从request参数中尝试获取并封装
2. 可以进行级联封装
3. 请求参数的参数名和对象中的属性名一一对应
 */
@RequestMapping("/book")
public String addBook(Book book){
    System.out.println("获取的图书信息:" + book);
    return "success";
}
  • 传入原生API
/*
SpringMVC可以在参数上写原生API

HttpServletRequest
HttpServletResponse
HttpSession
 */

@RequestMapping("/handle02")
public String handle02(HttpServletRequest request, HttpServletResponse response, HttpSession session){
    request.setAttribute("reqParam","reqAttr");
    session.setAttribute("sessionParam","sessionAttr");
    return "success";
}
  • 配置字符编码集Filter解决乱码问题
<!--配置字符编码的filter,注意:此filter一般在其他的filter之前-->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--解决POST请求乱码-->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <!--结局响应乱码:response.setCharacterEncoding(this.encoding)-->
    <init-param>
        <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>

数据输出

SpringMVC可以通过在方法形参列表传入Map、Model或者ModelMap,将数据保存在这些参数中,参数再将数据保存在请求域中的方式,在页面获取数据

@RequestMapping("/output")
@Controller
@SessionAttributes(value = "haha",types = {String.class})
public class OutputController {

    /*
    1. SpringMVC可以通过在方法形参列表传入Map、Model或者ModelMap,将数据保存在这些参数中,参数再将数据保存在请求域中的方式,在页面获取数据

    Map、Model或者ModelMap实际运行时都为同一种类型:BindingAwareModelMap

    Map(interface(jdk))      Model(interface(spring))
         ||                   //
         ||                   //
         \/                  //
      ModelMap(class)               //
               \\          //
                \\               //
               ExtendedModelMap
                     ||
                     \/
               BindingAwareModelMap

    2. 方法的返回值可以为ModelAndView类型,既包含视图信息(页面地址)也包含模型数据(给页面带的数据)

    3. @SessionAttributes(value = "msg", types = {String.class})
    value = "msg":在往请求域中保存数据时,只要是key的值为"msg",这份数据也会同时保存在Session域中
    types = {String.class}:在往请求域中保存数据时,只要value是String类型的,这份数据也会同时保存在Session域中
    不推荐使用,往Session域中保存数据使用原生API
     */

    @RequestMapping("/handle01")
    public String handle01(Map<String,Object> map){
        map.put("msg","map存入的数据");
        map.put("haha","哈哈");
        System.out.println("map类型" + map.getClass());//org.springframework.validation.support.BindingAwareModelMap
        return "success";
    }

    @RequestMapping("/handle02")
    public String handle02(Model model){
        model.addAttribute("msg","model存入的数据");
        model.addAttribute("haha",18);
        System.out.println("model的类型" + model.getClass());//org.springframework.validation.support.BindingAwareModelMap
        return "success";
    }

    @RequestMapping("/handle03")
    public String handle03(ModelMap modelMap){
        modelMap.addAttribute("msg","modelMap存入的数据");
        System.out.println("modelMap的类型" + modelMap.getClass());//org.springframework.validation.support.BindingAwareModelMap
        return "success";
    }

    /*
    返回值可以为ModelAndView,为页面携带数据
     */
    @RequestMapping("/handle04")
    public ModelAndView handle04(){
        ModelAndView mv = new ModelAndView();
        mv.addobject("msg","modelAndView存入的数据");
        mv.setViewName("success");
        return mv;
    }
}

@modelattribute注解解决全字段更新问题

@Controller
public class modelattributeTestController {

    /*
    测试modelattribute注解
    使用场景:书城图书修改
    页面端:显示所有要修改的图书信息,包含所有字段
    servlet:调用dao,String sql = "update t_book set `name` = ?,
                                   `author` = ?,`price` = ?,
                                   `sales` = ?,`stock` = ?,
                                   `img_path` = ? where `id` = ?";

    实际场景?
    并非全字段修改,只会修改部分字段,如price,stock,sales等
    1. 不修改的字段可以直接展示,不提供输入框进行修改
    2. Controller中的处理方法参数直接写Book对象,SpringMVC会自动封装(没有带的值为null)
    3. 若此时调用全字段更新,可能会将某些字段更新为null

    如何保证全字段更新的时候,只更新页面携带的数据?
    1. 修改dao,不现实
    2. 不直接创建对象,而是更新从数据库获取的对象的某些字段值
        1) 从数据库获取对应id的对象
        2) 将页面携带的数据更新到获取的对象中,页面中没携带的数据则为原来的值
        3) 调用全字段更新保存到数据库

    @modelattribute
    标注在参数上:取出保存的数据
    标注在方法上:提前于目标方法运行
     */

    private Object o1;
    private Object o2;
    private Object b1;
    private Object b2;

    @RequestMapping("/updateBook")
    public String updateBook(Map<String,Object> map,
            @modelattribute("book") Book book){
        System.out.println("页面提交的信息" + book);
        Object book1 = map.get("book");

        b2 = book;
        o2 = map;
        System.out.println("o1 == o2?" + (o1 == o2));
        System.out.println("b1 == b2?" + (b1 == b2) + "; b2 == book1?" + (b2 == book1) );//均为true,说明使用的均为同一个对象
        return "success";
    }

    @modelattribute
    public void mymodelattribute(Map<String, Object> map){
        Book book = new Book(100,"西游记","吴承恩",98.98,11,200,null);
        System.out.println("数据库获取的信息" + book);
        map.put("book",book);
        b1 = book;
        o1 = map;
        System.out.println("modelattribute保存数据,类型为" + map.getClass());//org.springframework.validation.support.BindingAwareModelMap
    }

}

请求处理源码分析

  1. dispatcherServlet结构

img

  1. dodispatch()
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;

          //2. 为当前请求确定处理器,即能处理该请求的类
         // Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);
          //若没找到处理器则抛出异常
         if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
		
          //3. 为当前处理器获取适配器
         // Determine handler adapter for the current request.
         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 {
             //4. 处理器的适配器处理请求,目标方法返回值作为视图名,返回ModelAndView对象
            // Actually invoke the 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;
      }
       //5. 转发到页面,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);
      }
   }
}
  1. getHandler()
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   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;
}

handlerMappings:处理器映射,里面保存了每个处理器能处理哪些请求的映射信息

img

  1. getHandlerAdapter()
protected HandlerAdapter getHandlerAdapter(Object handler) throws servletexception {
   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");
}

AnnotationMethodHandlerAdapter:注解驱动的处理器适配器

img

总结:

  1. 请求发送到dispatcherServlet,调用dodispatch()方法进行处理
  2. getHandler():根据当前请求在handlerMappings中找到这个请求的映射信息,获取目标处理器类
  3. getHandlerAdapter():根据当前处理器类获取到能执行这个处理器方法的适配器
  4. 使用获取到的适配器执行目标方法(AnnotationMethodHandlerAdapter)
  5. 目标方法执行之后返回一个ModelAndView对象,封装了要跳转页面信息及模型数据
  6. 转发到具体页面,并可在请求域中获取模型数据

SpringMVC九大组件

dispatcherServlet中的几个引用类型的属性,成为SpringMVC九大组件,且均为接口,提供了非常强大的拓展性

/** MultipartResolver used by this servlet 文件上传解析器*/
private MultipartResolver multipartResolver;

/** LocaleResolver used by this servlet 区域信息解析器,和国际化有关*/
private LocaleResolver localeResolver;

/** ThemeResolver used by this servlet 主题解析器*/
private ThemeResolver themeResolver;

/** List of HandlerMappings used by this servlet Handler映射信息*/
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet Handler适配器*/
private List<HandlerAdapter> handlerAdapters;

/** List of HandlerExceptionResolvers used by this servlet Handler异常解析器*/
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** RequestToViewNameTranslator used by this servlet 请求转换器*/
private RequestToViewNameTranslator viewNameTranslator;

/** FlashMapManager used by this servlet FlashMap管理,提供重定向携带数据功能*/
private FlashMapManager flashMapManager;

/** List of ViewResolvers used by this servlet 视图解析器*/
private List<ViewResolver> viewResolvers;

初始化

protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

初始化HandlerMappings

在容器中根据类型或id寻找该组件,若没找到则使用认配置

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

mv = ha.handle(processedRequest, response, mappedHandler.getHandler())--->return invokeHandlerMethod(request, response, handler);

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);
    //包装原生API:request和response
   ServletWebRequest webRequest = new ServletWebRequest(request, response);
    //创建一个BindingAwareModelMap对象--隐含模型
   ExtendedModelMap implicitModel = new BindingAwareModelMap();

    //实际执行目标方法
   Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
    //返回ModelAndView对象
   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) {
            implicitModel.addAttribute(attrName, attrValue);
         }
      }
       //获取所有标注了@modelattribute注解的方法
      for (Method attributeMethod : this.methodResolver.getmodelattributeMethods()) {
         Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
          //获取方法执行需要的参数
         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);
         }
         if (!implicitModel.containsAttribute(attrName)) {
             //将标注@modelattribute注解的方法的返回值放入隐含模型中
            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;
   }
}

根据分析可得,@modelattribute标注在方法上,若给定了value属性,则attrName=value属性值,若未给定value属性值,则attrName=方法返回值首字母小写,然后以attrName为key,方法返回值为value,放入隐含模型

resolveHandlerArguments():获取方法执行时需要的参数

private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
      NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

    //创建一个长度等于参数个数的数组,用来保存每一个参数的值
   Class<?>[] paramTypes = handlerMethod.getParameterTypes();
   Object[] args = new Object[paramTypes.length];

    //循环解析每一个参数
   for (int i = 0; i < args.length; i++) {
      MethodParameter methodParam = new MethodParameter(handlerMethod, i);
      methodParam.initParameterNamediscovery(this.parameterNamediscoverer);
      GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
      String paramName = null;
      String headerName = null;
      boolean requestBodyFound = false;
      String cookieName = null;
      String pathVarName = null;
      String attrName = null;
      boolean required = false;
      String defaultValue = null;
      boolean validate = false;
      Object[] validationHints = null;
      int annotationsFound = 0;
      Annotation[] paramAnns = methodParam.getParameterannotations();

       //找到方法的此参数标注的所有注解并解析保存
      for (Annotation paramAnn : paramAnns) {
         if (RequestParam.class.isinstance(paramAnn)) {
            RequestParam requestParam = (RequestParam) paramAnn;
            paramName = requestParam.value();
            required = requestParam.required();
            defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
            annotationsFound++;
         }
         else if (RequestHeader.class.isinstance(paramAnn)) {
            RequestHeader requestHeader = (RequestHeader) paramAnn;
            headerName = requestHeader.value();
            required = requestHeader.required();
            defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());
            annotationsFound++;
         }
         else if (RequestBody.class.isinstance(paramAnn)) {
            requestBodyFound = true;
            annotationsFound++;
         }
         else if (CookieValue.class.isinstance(paramAnn)) {
            CookieValue cookieValue = (CookieValue) paramAnn;
            cookieName = cookieValue.value();
            required = cookieValue.required();
            defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());
            annotationsFound++;
         }
         else if (PathVariable.class.isinstance(paramAnn)) {
            PathVariable pathVar = (PathVariable) paramAnn;
            pathVarName = pathVar.value();
            annotationsFound++;
         }
         else if (modelattribute.class.isinstance(paramAnn)) {
             //若标注了@modelattribute注解,则将注解的value属性值赋值给attrName
            modelattribute attr = (modelattribute) paramAnn;
            attrName = attr.value();
            annotationsFound++;
         }
         else if (Value.class.isinstance(paramAnn)) {
            defaultValue = ((Value) paramAnn).value();
         }
         else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
            validate = true;
            Object value = AnnotationUtils.getValue(paramAnn);
            validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value});
         }
      }

      if (annotationsFound > 1) {
         throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
               "do not specify more than one such annotation on the same parameter: " + handlerMethod);
      }

       //没有标注注解的情况
      if (annotationsFound == 0) {
          //解析普通参数,之后会进入resolveStandardArgument(),解析标准参数,判断是否为原生API
         Object argValue = resolveCommonArgument(methodParam, webRequest);
         if (argValue != WebArgumentResolver.UNRESOLVED) {
            args[i] = argValue;
         }
         else if (defaultValue != null) {
            args[i] = resolveDefaultValue(defaultValue);
         }
         else {
            Class<?> paramType = methodParam.getParameterType();
             //判断是否为Model或Map类型,如果是则将隐含模型赋值给此参数
            if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
               if (!paramType.isAssignableFrom(implicitModel.getClass())) {
                  throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +
                        "Model or Map but is not assignable from the actual model. You may need to switch " +
                        "newer MVC infrastructure classes to use this argument.");
               }
               args[i] = implicitModel;
            }
            else if (SessionStatus.class.isAssignableFrom(paramType)) {
               args[i] = this.sessionStatus;
            }
            else if (httpentity.class.isAssignableFrom(paramType)) {
               args[i] = resolvehttpentityRequest(methodParam, webRequest);
            }
            else if (Errors.class.isAssignableFrom(paramType)) {
               throw new IllegalStateException("Errors/BindingResult argument declared " +
                     "without preceding model attribute. Check your handler method signature!");
            }
            else if (BeanUtils.isSimpleProperty(paramType)) {
               paramName = "";
            }
            else {
               attrName = "";
            }
         }
      }

       //确定值的环节
      if (paramName != null) {
         args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
      }
      else if (headerName != null) {
         args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
      }
      else if (requestBodyFound) {
         args[i] = resolveRequestBody(methodParam, webRequest, handler);
      }
      else if (cookieName != null) {
         args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
      }
      else if (pathVarName != null) {
         args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
      }
       
      else if (attrName != null) {
         WebDataBinder binder =
               resolvemodelattribute(attrName, methodParam, implicitModel, webRequest, handler);
         boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
         if (binder.getTarget() != null) {
            dobind(binder, webRequest, validate, validationHints, !assignBindingResult);
         }
         args[i] = binder.getTarget();
         if (assignBindingResult) {
            args[i + 1] = binder.getBindingResult();
            i++;
         }
         implicitModel.putAll(binder.getBindingResult().getModel());
      }
   }

   return args;
}

resolveStandardArgument():解析是否是原生API

@Override
protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception {
   HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
   HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);

   if (ServletRequest.class.isAssignableFrom(parameterType) ||
         MultipartRequest.class.isAssignableFrom(parameterType)) {
      Object nativeRequest = webRequest.getNativeRequest(parameterType);
      if (nativeRequest == null) {
         throw new IllegalStateException(
               "Current request is not of type [" + parameterType.getName() + "]: " + request);
      }
      return nativeRequest;
   }
   else if (ServletResponse.class.isAssignableFrom(parameterType)) {
      this.responseArgumentUsed = true;
      Object nativeResponse = webRequest.getNativeResponse(parameterType);
      if (nativeResponse == null) {
         throw new IllegalStateException(
               "Current response is not of type [" + parameterType.getName() + "]: " + response);
      }
      return nativeResponse;
   }
   else if (HttpSession.class.isAssignableFrom(parameterType)) {
      return request.getSession();
   }
   else if (Principal.class.isAssignableFrom(parameterType)) {
      return request.getUserPrincipal();
   }
   else if (Locale.class.equals(parameterType)) {
      return RequestContextUtils.getLocale(request);
   }
   else if (InputStream.class.isAssignableFrom(parameterType)) {
      return request.getInputStream();
   }
   else if (Reader.class.isAssignableFrom(parameterType)) {
      return request.getReader();
   }
   else if (OutputStream.class.isAssignableFrom(parameterType)) {
      this.responseArgumentUsed = true;
      return response.getoutputStream();
   }
   else if (Writer.class.isAssignableFrom(parameterType)) {
      this.responseArgumentUsed = true;
      return response.getWriter();
   }
   return super.resolveStandardArgument(parameterType, webRequest);
}

流程

若标了注解

​ 保存注解的详细信息

​ 如果参数有modelattribute注解,则将注解的value属性值赋给attrName

若没标注解

​ 先判断是否是普通参数(原生API)

​ 再判断是否是Model或者Map类型,有就传入隐含模型(implicitModel)

自定义类型的参数没有标注modelattribute

​ 先判断是否是原生API

​ 再判断是否是Model或者Map

​ 再判断是否是其他类型,例如SessionStatus、httpentity、Errors

​ 再判断是否是简单类型的属性,例如Integer、String和基本类型,如果是则paramName=""

​ attrName=""

即如果是自定义类型的对象,则会有两种情况

  1. 标注了@modelattribute注解,attrName=value值
  2. 没有标注@modelattribute注解,attrName=""

确定自定义类型参数的值

private WebDataBinder resolvemodelattribute(String attrName, MethodParameter methodParam,
      ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception {

   // Bind request parameter onto object...
   String name = attrName;
   if ("".equals(name)) {
       //如果attrName="",则将参数类型首字母小写作为值
      name = Conventions.getvariableNameForParameter(methodParam);
   }
   Class<?> paramType = methodParam.getParameterType();
    //确定目标对象的值
   Object bindobject;
   if (implicitModel.containsKey(name)) {
      bindobject = implicitModel.get(name);
   }
   else if (this.methodResolver.isSessionAttribute(name, paramType)) {
      bindobject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);
      if (bindobject == null) {
         raiseSessionrequiredException("Session attribute '" + name + "' required - not found in session");
      }
   }
   else {
      bindobject = BeanUtils.instantiateClass(paramType);
   }
   WebDataBinder binder = createBinder(webRequest, bindobject, name);
   initBinder(handler, name, binder, webRequest);
   return binder;
}

先判断隐含模型中是否有以变量name为key的对象(若attrName不为空串则name=attrName,若attrName=空串则name=参数类型首字母小写),若有则将这个值赋给bindobject

再判断是否为标注了SessionAttribute的属性,若标注了则从Session中获取

若以上不满足则利用反射创建对象

最后利用该对象使用数据绑定器(WebDataBinder)将请求中的数据绑定到这个对象中并返回

视图解析源码分析

第一部分:获取View对象

执行方法之后返回值均会包装为ModelAndView对象

img

processdispatchResult()方法作用是来到页面,内部调用render()方法

/**
 * Render the given ModelAndView.
 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
 * @param mv the ModelAndView to render
 * @param request current HTTP servlet request
 * @param response current HTTP servlet response
 * @throws servletexception if view is missing or cannot be resolved
 * @throws Exception if there's a problem rendering the view
 */
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对象
      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对象进行页面渲染
      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;
   }
}

resolveViewName()

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
      HttpServletRequest request) throws Exception {

    //循环遍历所有的ViewResolver:配置的InternalResourceViewResolver
   for (ViewResolver viewResolver : this.viewResolvers) {
       //ViewResolver根据方法返回值获取View对象
      View view = viewResolver.resolveViewName(viewName, locale);
      if (view != null) {
         return view;
      }
   }
   return null;
}

resolveViewName()

@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
   if (!isCache()) {
      return createView(viewName, locale);
   }
   else {
       //检查缓存中是否有已经创建好的duiyingdeView对象
      Object cacheKey = getCacheKey(viewName, locale);
      View view = this.viewAccessCache.get(cacheKey);
      if (view == null) {
         synchronized (this.viewCreationCache) {
            view = this.viewCreationCache.get(cacheKey);
            if (view == null) {
               // Ask the subclass to create the View object.
                //根据方法返回值创建View对象
               view = createView(viewName, locale);
               if (view == null && this.cacheUnresolved) {
                  view = UNRESOLVED_VIEW;
               }
               if (view != null) {
                   //将创建好的View对象放入缓存中
                  this.viewAccessCache.put(cacheKey, view);
                  this.viewCreationCache.put(cacheKey, view);
                  if (logger.isTraceEnabled()) {
                     logger.trace("Cached view [" + cacheKey + "]");
                  }
               }
            }
         }
      }
      return (view != UNRESOLVED_VIEW ? view : null);
   }
}

createView()

@Override
protected View createView(String viewName, Locale locale) throws Exception {
   // If this resolver is not supposed to handle the given view,
   // return null to pass on to the next resolver in the chain.
   if (!canHandle(viewName, locale)) {
      return null;
   }
   // Check for special "redirect:" prefix.
    //判断是否是redirect:前缀
   if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
      String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
      RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
      return applyLifecycleMethods(viewName, view);
   }
   // Check for special "forward:" prefix.
    //判断是否是forward:前缀
   if (viewName.startsWith(FORWARD_URL_PREFIX)) {
      String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
      return new InternalResourceView(forwardUrl);
   }
   // Else fall back to superclass implementation: calling loadView.
    //没有前缀则使用父类认创建一个View
   return super.createView(viewName, locale);
}

loadView()-->buildView()

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    //根据方法返回值拼串并创建View对象
   AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
   view.setUrl(getPrefix() + viewName + getSuffix());
   String contentType = getContentType();
   if (contentType != null) {
      view.setContentType(contentType);
   }
   view.setRequestContextAttribute(getRequestContextAttribute());
   view.setAttributesMap(getAttributesMap());
   if (this.exposePathVariables != null) {
      view.setExposePathVariables(exposePathVariables);
   }
    //InternalResourceView
   return view;
}

第二部分:View对象进行页面渲染

view.render()

@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
   if (logger.isTraceEnabled()) {
      logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
         " and static attributes " + this.staticAttributes);
   }

   Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);

   prepareResponse(request, response);
    //渲染要给页面输出的所有数据
   renderMergedOutputModel(mergedModel, request, response);
}

renderMergedOutputModel()

/**
 * Render the internal resource given the specified model.
 * This includes setting the model as request attributes.
 */
@Override
protected void renderMergedOutputModel(
      Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

   // Determine which request handle to expose to the Requestdispatcher.
   HttpServletRequest requestToExpose = getRequestToExpose(request);

   // Expose the model object as request attributes.
    //将模型中的数据放入请求域中
   exposeModelAsRequestAttributes(model, requestToExpose);

   // Expose helpers as request attributes, if any.
   exposeHelpers(requestToExpose);

   // Determine the path for the request dispatcher.
    //确定页面地址
   String dispatcherPath = prepareForRendering(requestToExpose, response);

   // Obtain a Requestdispatcher for the target resource (typically a JSP).
   Requestdispatcher rd = getRequestdispatcher(requestToExpose, dispatcherPath);
   if (rd == null) {
      throw new servletexception("Could not get Requestdispatcher for [" + getUrl() +
            "]: Check that the corresponding file exists within your web application archive!");
   }

   // If already included or response already committed, perform include, else forward.
   if (useInclude(requestToExpose, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
         logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
      }
      rd.include(requestToExpose, response);
   }

   else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      if (logger.isDebugEnabled()) {
         logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
      }
       //请求转发至页面
      rd.forward(requestToExpose, response);
   }
}

exposeModelAsRequestAttributes():取出模型中的所有数据并放入请求域中

/**
 * Expose the model objects in the given map as request attributes.
 * Names will be taken from the model Map.
 * This method is suitable for all resources reachable by {@link javax.servlet.Requestdispatcher}.
 * @param model Map of model objects to expose
 * @param request current HTTP request
 */
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
   for (Map.Entry<String, Object> entry : model.entrySet()) {
      String modelName = entry.getKey();
      Object modelValue = entry.getValue();
      if (modelValue != null) {
         request.setAttribute(modelName, modelValue);
         if (logger.isDebugEnabled()) {
            logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                  "] to request in view with name '" + getBeanName() + "'");
         }
      }
      else {
         request.removeAttribute(modelName);
         if (logger.isDebugEnabled()) {
            logger.debug("Removed model object '" + modelName +
                  "' from request in view with name '" + getBeanName() + "'");
         }
      }
   }
}

视图解析器解析方法返回值并创建View对象,View对象完成页面数据的存储并转发至页面

img

img

RestFulCRUD

利用SpringMVC,编写一个REST风格的CRUD

C:Create--创建

R:Retrieve--查询

U:Update--更新

D:Delete--删除

员工列表页

img

员工添加页面

img

更新员工信息

img

删除员工,点击Delete按钮之后来到员工列表页


增删改查的URL地址 /资源名/资源表示

/emp/1 GET:查询id为1号的员工

/emp/1 PUT:修改id为1号的员工

/emp/1 DELETE:删除id为1号的员工

/emp POST:新增员工

/emps GET:查询所有员工


  • 展示所有员工

访问index.jsp,请求直接转发到/emps,控制器查询出所有员工放入请求域中,转发到页面展示

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<jsp:forward page="/emps"></jsp:forward>

控制器

/**
 * 查询所有员工信息用于展示
 * @param model
 * @return
 */
@RequestMapping("/emps")
public String getEmps(Model model){
    Collection<Employee> employees = employeeDao.getAll();
    model.addAttribute("employees",employees);
    return "list";
}

展示页面

<h1>员工列表页面</h1>
<table border="1" cellpadding="5" cellspacing="0">
    <tr>
        <th>ID</th>
        <th>LastName</th>
        <th>Email</th>
        <th>Gender</th>
        <th>Department</th>
        <th>Edit</th>
        <th>Delete</th>
    </tr>
    <c:forEach items="${requestScope.employees}" var="employee">
    <tr>
        <td>${employee.id}</td>
        <td>${employee.lastName}</td>
        <td>${employee.email}</td>
        <td>${employee.gender==0?"Female":"Male"}</td>
        <td>${employee.department.departmentName}</td>
        <td><a href="${ctp}/emp/${employee.id}">Edit</a></td>
        <td><a href="${ctp}/emp/${employee.id}" class="deleteBtn">Delete</a></td>
    </tr>
    </c:forEach>
</table>
  • 添加员工(利用SpringMVC表单标签替换原生表单)

点击添加员工按钮,请求发送到控制器,保存表单标签所需数据,跳转至员工添加页面添加完成后点击提交,请求发送到控制器,控制器保存数据之后转发到列表页

添加按钮

<%
    request.setAttribute("ctp",request.getcontextpath());
%>
<h1>员工列表页面</h1>
<table border="1" cellpadding="5" cellspacing="0">
    <tr>
        <th>ID</th>
        <th>LastName</th>
        <th>Email</th>
        <th>Gender</th>
        <th>Department</th>
        <th>Edit</th>
        <th>Delete</th>
    </tr>
    <c:forEach items="${requestScope.employees}" var="employee">
    <tr>
        <td>${employee.id}</td>
        <td>${employee.lastName}</td>
        <td>${employee.email}</td>
        <td>${employee.gender==0?"Female":"Male"}</td>
        <td>${employee.department.departmentName}</td>
        <td><a href="${ctp}/emp/${employee.id}">Edit</a></td>
        <td><a href="${ctp}/emp/${employee.id}" class="deleteBtn">Delete</a></td>
    </tr>
    </c:forEach>
</table>
<a href="${ctp}/toAddEmp">添加员工</a>

控制器保存数据

/**
 * 跳转添加员工操作,并将部门信息以及表单标签要求的对象存入隐含模型中
 * @param model
 * @return
 */
@RequestMapping("/toAddEmp")
public String toAddEmp(Model model){
    Collection<Department> departments = departmentDao.getDepartments();
    model.addAttribute("departments",departments);
    model.addAttribute("employee",new Employee());
    return "addEmp";
}

添加页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>添加员工</title>
</head>
<body>
<%
    request.setAttribute("ctp",request.getcontextpath());
%>
<h1>添加员工</h1>
<%--
表单标签:
1. 使用SpringMVC表单标签可以实现将模型中的属性和HTML表单元素相绑定,以实现表单数据更便捷的编辑和表单值的回显
2. 在SpringMVC中使用form表单标签,path指定的属性,必须要在隐含模型中找到某个对象,该对象拥有这一属性认对象名为command,使用modelattribute可以修改认寻找的对象名
--%>
<form:form  action="${ctp}/emp" modelattribute="employee" method="post">
    <%--
    path:
        1. 原生input标签的name属性值
        2. 自动回显隐含模型中某个对象对应的该属性的值
    --%>
    LastName:<form:input path="lastName"/><br/>
    Email:<form:input path="email"/><br/>
    Gender:<form:radiobutton path="gender" value="1"/>Male <form:radiobutton path="gender" value="0"/>Female<br/>
    <%--
    items:指定要遍历的集合
    itemLabel:指定遍历出来的对象的哪个属性作为option标签体的值
    itemValue:指定历出来的对象的哪个属性作为要提交的Value属性值
    --%>
    Department:<form:select path="department.id" items="${departments}" itemLabel="departmentName" itemValue="id"></form:select><br/>
    <input type="submit" value="Submit"/>
</form:form>


<%--<form action="addEmp">
    LastName:<input type="text" name="lastName"><br/>
    Email:<input type="text" name="email"><br/>
    Gender:<input type="radio" name="gender" value="1">Male <input type="radio" name="gender" value="0">Female<br/>
    Department:
    <select>
        <c:forEach items="${requestScope.departments}" var="department">
            <option value="${department.id}">${department.departmentName}</option>
        </c:forEach>
    </select>
    <input type="submit" value="Submit">
</form>--%>
</body>
</html>

控制器添加员工

/**
     * 添加员工
     * @param employee
     * @return
     */
    @RequestMapping(value = "/emp",method = RequestMethod.POST)
    public String addEmp(Employee employee){
//        System.out.println("要添加的员工信息为" + employee);
        employeeDao.save(employee);
        //返回列表页面
        return "redirect:/emps";
    }

点击修改按钮,控制器根据id获取员工信息和部门信息并放入隐含模型,转发到修改页面修改之后点击提交,并封装到@modelattribute提前获取的员工对象中,控制器保存修改

控制器获取员工信息和部门信息

/**
 * 根据id获取员工信息和部门信息
 * @param id
 * @param model
 * @return
 */
@RequestMapping(value = "/emp/{id}",method = RequestMethod.GET)
public String getEmp(@PathVariable("id")Integer id,Model model){
    //1. 根据id查询出员工信息
    Employee employee = employeeDao.get(id);
    //2. 放入隐含模型中
    model.addAttribute("employee",employee);
    //3. 查询出部门信息,放入隐含模型中
    model.addAttribute("departments",departmentDao.getDepartments());
    return "editEmp";
}

员工修改页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>修改员工信息</title>
</head>
<body>
<%
    request.setAttribute("ctp",request.getcontextpath());
%>
<h1>修改&nbsp;${requestScope.employee.lastName}</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"/>Male<form:radiobutton path="gender" value="0"/>Female<br/>
    Departments:<form:select items="${departments}" path="department.id" itemLabel="departmentName" itemValue="id"></form:select><br/>
    <input type="submit" value="Submit"/>
</form:form>
</body>
</html>

@modelattribute方法

/**
     * 提前获取员工信息并放入隐藏域中
     * @param id
     * @param model
     */
    @modelattribute
    public void mymodelattribute(@RequestParam(value = "id",required = false)Integer id, Model model){
        if(id != null){
//            System.out.println(employeeDao.get(id));
            model.addAttribute("employee",employeeDao.get(id));
        }
    }

更新员工信息

/**
     * 更新员工信息
     * @param employee
     * @return
     */
    @RequestMapping(value = "/emp/{id}",method = RequestMethod.PUT)
    public String updateEmp(@modelattribute("employee") Employee employee){
//        System.out.println(employee);
        employeeDao.save(employee);
        return "redirect:/emps";
    }

利用JavaScript脚本,完成点击超链接,能向服务器发送DELETE请求功能

JavaScript脚本和表单

<form id="deleteForm" action="" method="post">
    <input type="hidden" name="_method" value="delete"/>
</form>
<script type="text/javascript">
    $(function () {
       // alert("hello");
        $(".deleteBtn").click(function () {
            var isDelete = confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() +"】吗?");
            if(isDelete){
                //1. 更改表单的提交请求地址
                $("#deleteForm").attr("action",this.href);
                //2. 表单提交
                $("#deleteForm").submit();
            }
            return false;
        });
    });
</script>

控制器

/**
 * 删除员工
 * @param id
 * @return
 */
@RequestMapping(value = "/emp/{id}",method = RequestMethod.DELETE)
public String deleteEmp(@PathVariable("id")Integer id){
    employeeDao.delete(id);
    return "redirect:/emps";
}

数据绑定

SpringMVC在封装自定义类型的对象时,如何进行数据的绑定?

页面提交的数据都是字符串,因此数据的绑定涉及到以下的操作

  • 数据绑定期间的数据类型转换,String-->Integer,String-->Boolean...
  • 数据绑定期间的数据格式化问题,如提交的日期进行转换,字符串2021-06-15转换成为Date对象
  • 数据校验,包括前端校验和后台校验,只有合法的数据才能进行绑定

SpringMVC主框架将ServletRequest对象及目标方法的参数传递给WebDataBinderFactory实例,创建WebDataBinder对象,WebDataBinder负责数据绑定的工作

WebDataBinder对象调用装配在SpringMVC上下文中的ConversionService组件进行数据类型转换和格式化工作

调用Validators组件对已绑定的数据进行数据合法性校验,生成数据绑定结果bindingResult

WebDataBinder是核心部件

@H_955_4@modelattributeMethodProcessor

/**
 * Resolve the argument from the model or if not found instantiate it with
 * its default if it is available. The model attribute is then populated
 * with request values via data binding and optionally validated
 * if {@code @java.validation.Valid} is present on the argument.
 * @throws BindException if data binding and validation result in an error
 * and the next method parameter is not of type {@link Errors}.
 * @throws Exception if WebDataBinder initialization fails.
 */
@Override
public final Object resolveArgument(
      MethodParameter parameter, ModelAndViewContainer mavContainer,
      NativeWebRequest request, WebDataBinderFactory binderFactory)
      throws Exception {

   String name = ModelFactory.getNameForParameter(parameter);
   Object attribute = (mavContainer.containsAttribute(name)) ?
         mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);

   WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
   if (binder.getTarget() != null) {
       //将页面提交的数据封装进JavaBean中
      bindRequestParameters(binder, request);
      validateIfApplicable(binder, parameter);
      if (binder.getBindingResult().hasErrors()) {
         if (isBindExceptionrequired(binder, parameter)) {
            throw new BindException(binder.getBindingResult());
         }
      }
   }

   // Add resolved attribute and BindingResult at the end of the model

   Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
   mavContainer.removeAttributes(bindingResultModel);
   mavContainer.addAllAttributes(bindingResultModel);

   return binder.getTarget();
}

img

conversionService组件中的converters,可以进行各种类型的转换

img


自定义类型转换器?

ConversionService是一个接口,利用converter实现类型转换

自定义类型转换器,需实现Converter接口,重写convert()方法自定义转换规则

<%--将所有员工信息都作为字符串快速添加--%>
<form action="${ctp}/quickAdd">
    <input type="text" name="empInfo" value="[email protected]" />
    <input type="submit" value="quickAdd"/>
</form>

自定义类型转换器

public class MyConverter implements Converter<String,Employee> {

    @Autowired
    DepartmentDao departmentDao;

    //自定义的转换规则
    @Override
    public Employee convert(String s) {
        Employee employee = new Employee();
        //s:[email protected]
        if(s.contains("-")){
            String[] split = s.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中的组件,因此需要配置包含了我们自定义的类型转换器的ConversionService组件

ConversionServicefactorybean:创建ConversionService组件

<!--配置自定义converter的ConversionService
使用FormattingConversionServicefactorybean,既具有格式化功能,也具有类型转换功能-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServicefactorybean">
    <property name="converters">
        <set>
            <bean class="com.th1024.bean.MyConverter"></bean>
        </set>
    </property>
</bean>
<!--conversion-service="conversionService":使用自定义的ConversionService-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

解析<mvc:annotation-driven></mvc:annotation-driven>

解析该标签时,创建并添加了大量组件

class AnnotationDrivenBeanDeFinitionParser implements BeanDeFinitionParser {

   private static final boolean JSR303Present = ClassUtils.isPresent(
         "javax.validation.Validator", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());

   private static final boolean jaxb2Present =
         ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());

   private static final boolean jackson2Present =
         ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader()) &&
               ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());

   private static final boolean jacksonPresent =
         ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader()) &&
               ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());

   private static boolean romePresent =
         ClassUtils.isPresent("com.sun.syndication.Feed.WireFeed", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());

   @Override
   public BeanDeFinition parse(Element element, ParserContext parserContext) {
      Object source = parserContext.extractSource(element);

      CompositeComponentDeFinition compDeFinition = new CompositeComponentDeFinition(element.getTagName(), source);
      parserContext.pushContainingComponent(compDeFinition);

      RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);

      RootBeanDeFinition handlerMappingDef = new RootBeanDeFinition(RequestMappingHandlerMapping.class);
      handlerMappingDef.setSource(source);
      handlerMappingDef.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
      handlerMappingDef.getPropertyValues().add("order", 0);
      handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
      String methodMappingName = parserContext.getReaderContext().registerWithGeneratedname(handlerMappingDef);
      if (element.hasAttribute("enable-matrix-variables") || element.hasAttribute("enableMatrixVariables")) {
         Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute(
               element.hasAttribute("enable-matrix-variables") ? "enable-matrix-variables" : "enableMatrixVariables"));
         handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
      }

      RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
      RuntimeBeanReference validator = getValidator(element, source, parserContext);
      RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);

      RootBeanDeFinition bindingDef = new RootBeanDeFinition(ConfigurableWebBindingInitializer.class);
      bindingDef.setSource(source);
      bindingDef.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
      bindingDef.getPropertyValues().add("conversionService", conversionService);
      bindingDef.getPropertyValues().add("validator", validator);
      bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

      ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
      ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
      ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
      String asyncTimeout = getAsyncTimeout(element, source, parserContext);
      RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext);
      ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
      ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);

      RootBeanDeFinition handlerAdapterDef = new RootBeanDeFinition(RequestMappingHandlerAdapter.class);
      handlerAdapterDef.setSource(source);
      handlerAdapterDef.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
      handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
      handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
      handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
      if (element.hasAttribute("ignore-default-model-on-redirect") || element.hasAttribute("ignoreDefaultModelOnRedirect")) {
         Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute(
               element.hasAttribute("ignore-default-model-on-redirect") ? "ignore-default-model-on-redirect" : "ignoreDefaultModelOnRedirect"));
         handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
      }
      if (argumentResolvers != null) {
         handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
      }
      if (returnValueHandlers != null) {
         handlerAdapterDef.getPropertyValues().add("customreturnValueHandlers", returnValueHandlers);
      }
      if (asyncTimeout != null) {
         handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
      }
      if (asyncExecutor != null) {
         handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
      }
      handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
      handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
      String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedname(handlerAdapterDef);

      String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
      RootBeanDeFinition uriCompContribDef = new RootBeanDeFinition(CompositeUriComponentsContributorfactorybean.class);
      uriCompContribDef.setSource(source);
      uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
      uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
      parserContext.getReaderContext().getRegistry().registerBeanDeFinition(uriCompContribName, uriCompContribDef);

      RootBeanDeFinition csInterceptorDef = new RootBeanDeFinition(ConversionServiceExposingInterceptor.class);
      csInterceptorDef.setSource(source);
      csInterceptorDef.getconstructorargumentValues().addindexedArgumentValue(0, conversionService);
      RootBeanDeFinition mappedCsInterceptorDef = new RootBeanDeFinition(MappedInterceptor.class);
      mappedCsInterceptorDef.setSource(source);
      mappedCsInterceptorDef.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
      mappedCsInterceptorDef.getconstructorargumentValues().addindexedArgumentValue(0, (Object) null);
      mappedCsInterceptorDef.getconstructorargumentValues().addindexedArgumentValue(1, csInterceptorDef);
      String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedname(mappedCsInterceptorDef);

      RootBeanDeFinition exceptionHandlerExceptionResolver = new RootBeanDeFinition(ExceptionHandlerExceptionResolver.class);
      exceptionHandlerExceptionResolver.setSource(source);
      exceptionHandlerExceptionResolver.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
      exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
      exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
      exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
      String methodExceptionResolverName =
            parserContext.getReaderContext().registerWithGeneratedname(exceptionHandlerExceptionResolver);

      RootBeanDeFinition responseStatusExceptionResolver = new RootBeanDeFinition(ResponseStatusExceptionResolver.class);
      responseStatusExceptionResolver.setSource(source);
      responseStatusExceptionResolver.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
      responseStatusExceptionResolver.getPropertyValues().add("order", 1);
      String responseStatusExceptionResolverName =
            parserContext.getReaderContext().registerWithGeneratedname(responseStatusExceptionResolver);

      RootBeanDeFinition defaultExceptionResolver = new RootBeanDeFinition(DefaultHandlerExceptionResolver.class);
      defaultExceptionResolver.setSource(source);
      defaultExceptionResolver.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
      defaultExceptionResolver.getPropertyValues().add("order", 2);
      String defaultExceptionResolverName =
            parserContext.getReaderContext().registerWithGeneratedname(defaultExceptionResolver);

      parserContext.registerComponent(new BeanComponentDeFinition(handlerMappingDef, methodMappingName));
      parserContext.registerComponent(new BeanComponentDeFinition(handlerAdapterDef, handlerAdapterName));
      parserContext.registerComponent(new BeanComponentDeFinition(uriCompContribDef, uriCompContribName));
      parserContext.registerComponent(new BeanComponentDeFinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
      parserContext.registerComponent(new BeanComponentDeFinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
      parserContext.registerComponent(new BeanComponentDeFinition(defaultExceptionResolver, defaultExceptionResolverName));
      parserContext.registerComponent(new BeanComponentDeFinition(mappedCsInterceptorDef, mappedInterceptorName));

      // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
      MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

      parserContext.popAndRegisterContainingComponent();

      return null;
   }

<mvc:default-servlet-handler/>和<mvc:annotation-driven/>

  • 若两个标签都不配置,则工程动态资源能访问,静态资源无法访问

HandlerMapping

img

img

DefaultAnnotationHandlerMapping保存了请求的映射信息,保证动态资源的正确访问,而静态资源的请求映射没有保存在其中,故静态资源无法访问

img

AnnotationMethodHandlerAdapter执行目标方法,过时的类

  • 只配置<mvc:default-servlet-handler/>,能访问静态资源,无法访问动态资源

HandlerMapping

img

SimpleUrlHandlerMapping直接将所有请求都交给服务器处理,能访问静态资源而不能访问动态资源

img

  • 需同时配置两个标签,才能保证既能访问动态资源,也能访问静态资源

img

RequestMappingHandlerMapping中的handlerMethods属性保存了请求应该由哪个方法来处理的信息

img

AnnotationMethodHandlerAdapter被替换成了RequestMappingHandlerAdapter,该适配器执行目标方法期间,利用各种各样的参数解析器解析参数

img


日期格式化

<!--配置自定义converter的ConversionService
使用FormattingConversionServicefactorybean,既具有格式化功能,也具有类型转换功能-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServicefactorybean">
    <property name="converters">
        <set>
            <bean class="com.th1024.bean.MyConverter"></bean>
        </set>
    </property>
</bean>

使用注解@DateTimeFormat规定日期格式

@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;

数据校验

不仅需要前端校验,同时也需要后端校验

利用JSR303进行数据校验

导包:
classmate-0.8.0.jar
hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar

给JavaBean的属性添加校验注解

@NotEmpty//该属性必须非空
@Length(min = 6,max = 18)//该属性长度必须位于6-18之间
private String lastName;

@Email//该属性必须是一个合法的电子邮箱地址
private String email;
//1 male, 0 female
private Integer gender;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past//该属性必须是一个过去的时间
private Date birth;

利用@Valid注解,使SpringMVC封装数据时进行数据校验,在要封装数据的对象之后紧跟一个BindingResult对象,封装校验结果

@RequestMapping(value = "/emp",method = RequestMethod.POST)
    public String addEmp(@Valid Employee employee, BindingResult result){
//        System.out.println("要添加的员工信息为" + employee);
        boolean hasErrors = result.hasErrors();
        if(hasErrors){
            return "addEmp";//?如何跳转到/toAddEmp的同时携带错误信息
        }else{
            employeeDao.save(employee);
            //返回列表页面
            return "redirect:/emps";
        }
    }

页面获取错误信息并回显

LastName:<form:input path="lastName"/><form:errors path="lastName"/><br/>
Email:<form:input path="email"/><form:errors path="lastName"/><br/>
Gender:<form:radiobutton path="gender" value="1"/>Male <form:radiobutton path="gender" value="0"/>Female<br/>
Brith:<form:input path="birth"/><form:errors path="birth"/><br/>

自定义错误信息

@NotEmpty(message = "用户名不能为空!")
@Length(min = 6,max = 18)
private String lastName;

SpringMVC支持Ajax

导包

jackson-annotations-2.1.5.jar
jackson-core-2.1.5.jar
jackson-databind-2.1.5.jar

创建一个处理器方法,并标注@ResponseBody注解

/**
 * @ResponseBody
 * 将返回的数据放入响应体中,如果是对象则自动转换为json字符串
 * @return
 */
@ResponseBody
@RequestMapping("/ajaxGetAll")
public Collection<Employee> ajaxGetAll(){
    Collection<Employee> all = employeeDao.getAll();
    return all;
}

对JavaBean属性进行设置

@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past
//转换成json字符串时进行类型转换
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birth;

//转换成json字符串时忽略该属性
@JsonIgnore
private Department department;

页面发送Ajax请求,并回显返回的json数据

<a href="ajaxGetAll">ajax获取所有员工</a>
<div></div>
<script type="text/javascript">
       $("a:first").click(function () {
           $.ajax({
               url:"${ctp}/ajaxGetAll",
               type:"GET",
               success:function (data) {
                   $.each(data,function () {
                       var empInfo = this.lastName + "-" + this.birth;
                       $("div").append(empInfo + "<br/>");
                   });
               }
           });
           return false;
       });
</script>

@RequestBody获取请求体数据

/**
 * @RequestBody
 * 接收json字符串并封装
 * @param employee
 * @return
 */
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody Employee employee){
    System.out.println("对象信息:" + employee);
    return "success";
}

测试页面发送数据和json字符串

<body>
<form action="${ctp}/testRequestBody" enctype="multipart/form-data" method="post">
    <input name="username" value="Tomcat" />
    <input name="password" value="666666" />
    <input type="file" name="file"/>
    <input type="submit" />
</form>
<a href="${ctp}/testRequestBody">ajax发送json字符串</a>
</body>
<script type="text/javascript">
    $("a:first").click(function() {
        //点击发送ajax请求,请求带的数据是json
        var emp = {
            lastName : "张三",
            email : "[email protected]",
            gender : 0
        };
        //alert(typeof emp);
        //js对象
        var empStr = JSON.stringify(emp);
        //alert(typeof empStr);
        $.ajax({
            url : '${ctp}/testRequestBody',
            type : "POST",
            data : empStr,
            contentType : "application/json",
            success : function(data) {
                alert(data);
            }
        });
        return false;
    });
</script>

拦截

SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作

handlerinterceptor

img

preHandle():在目标方法之前运行,返回Boolean值

postHandle():在目标方法运行之后运行

afterCompletion():在请求完成来到页面之后运行

如何自定义一个拦截器?

实现handlerinterceptor接口

重写这三个方法

配置文件中配置拦截

<!--配置拦截器-->
<mvc:interceptors>
    <!--配置某个拦截器,认为拦截所有请求-->
    <bean class="com.th1024.component.MyFirstInterceptor"></bean>

</mvc:interceptors>

运行流程

MyFirstInterceptor...preHandle...
test01....
MyFirstInterceptor...postHandle...
success.jsp....
MyFirstInterceptor...afterCompletion

若preHandle()返回false,则没有后面的流程,若preHandle()放行,则afterCompletion()都会执行

如果配置了多个拦截器?

<!--配置拦截器-->
<mvc:interceptors>
    <!--配置某个拦截器,认为拦截所有请求-->
    <bean class="com.th1024.component.MyFirstInterceptor"></bean>

    <!--具体配置拦截器-->
    <mvc:interceptor>
        <!--拦截器只拦截test01请求-->
        <mvc:mapping path="/test01"/>
        <bean class="com.th1024.component.MySecondInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

正常流程,类似JavaWeb中Filter的运行流程,“先进后出”原则

MyFirstInterceptor...preHandle...
MySecondInterceptor...preHandle...
test01....
MySecondInterceptor...postHandle...
MyFirstInterceptor...postHandle...
success.jsp....
MySecondInterceptor...afterCompletion...
MyFirstInterceptor...afterCompletion

异常处理

SpringMVC认装配以下三个异常解析器

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
   org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
   org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

若配置了<mvc:annotation-driven/>,SpringMVC会将AnnotationMethodHandlerExceptionResolver替换成ExceptionHandlerExceptionResolver

三个异常解析器的作用

ExceptionHandlerExceptionResolver:处理注解@ExceptionHandler所指定的异常类型

ResponseStatusExceptionResolver:处理标注了@ResponseStatus的自定义异常

DefaultHandlerExceptionResolver:处理SpringMVC自带的异常

处理异常

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) {
            this.logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException)exception).getModelAndView();
        } else {
            //处理异常
            Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
            mv = this.processHandlerException(request, response, handler, exception);
            errorView = mv != null;
        }
    }

    if (mv != null && !mv.wasCleared()) {
        //来到页面
        this.render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else if (this.logger.isDebugEnabled()) {
        this.logger.debug("Null ModelAndView returned to dispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
    }

    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }

    }
}

processHandlerException()

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    ModelAndView exMv = null;
    Iterator var6 = this.handlerExceptionResolvers.iterator();
	//利用循环,用每一个异常解析器轮流处理异常
    while(var6.hasNext()) {
        HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var6.next();
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
            break;
        }
    }

    if (exMv != null) {
        if (exMv.isEmpty()) {
            return null;
        } else {
            if (!exMv.hasView()) {
                exMv.setViewName(this.getdefaultviewName(request));
            }

            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
            }

            WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
            return exMv;
        }
    } else {
        //若所有异常解析器均无法处理异常,则抛出给服务器
        throw ex;
    }
}

利用@ExceptionHandler自定义异常处理解析器

/**
 * @ExceptionHandler注解,处理本类发生的异常
 * 参数位置添加Exception对象用于接收异常信息
 * 参数位置不能写Model或Map或ModelMap对象,因此返回ModelAndView对象用于携带异常信息页面
 * 如果有多个@ExceptionHandler都能处理同一种异常,处理范围更精确的优先
 * 全局的异常处理和本类的异常处理,无论处理范围精确与否,本类的处理优先
 * @param e
 * @return
 */
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public ModelAndView handleException01(Exception e){
    System.out.println("本类的:handleException01..." + e);
    ModelAndView view = new ModelAndView("myError");
    view.addobject("ex",e);
    return view;
}

自定义集中处理异常的类

/**
 * 集中处理异常
 * @ControllerAdvice:标注在集中处理异常的类上,将该类加入ioc容器中
 *
 * @author TuHong
 * @create 2021-06-22 11:21
 */
@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handleException01(Exception e){
        System.out.println("全局:handleException01..." + e);
        ModelAndView view = new ModelAndView("myError");
        view.addobject("ex",e);
        return view;
    }

    @ExceptionHandler(value = Exception.class)
    public ModelAndView handleException02(Exception e){
        System.out.println("全局:handleException02..." + e);
        ModelAndView view = new ModelAndView("myError");
        view.addobject("ex",e);
        return view;
    }
}

利用@ResponseStatus创建一个自定义异常

/**
 * @ResponseStatus:标注在自定义异常类上
 *
 * @author TuHong
 * @create 2021-06-22 11:39
 */
@ResponseStatus(reason = "用户名不正确",value = HttpStatus.NOT_ACCEPTABLE)
public class UserNameNotFoundException extends RuntimeException{

    static final long serialVersionUID = -7034897190461466939L;

}

产生自定义异常类的处理方法

@RequestMapping("/handle02")
public String handle02(String username){
    System.out.println("handle02..." + username);
    if(!"admin".equals(username)){
        System.out.println("登录失败");
        throw new UserNameNotFoundException();
    }
    System.out.println("登录成功");
    return "success";
}

来到服务器认异常页面

img

DefaultHandlerExceptionResolver处理SpringMVC产生的异常,如HttpRequestMethodNotSupportedException,来到错误页面

img

DefaultHandlerExceptionResolver能处理的异常

protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    try {
        if (ex instanceof NoSuchRequestHandlingMethodException) {
            return this.handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException)ex, request, response, handler);
        }

        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler);
        }

        if (ex instanceof HttpMediaTypeNotSupportedException) {
            return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler);
        }

        if (ex instanceof HttpMediaTypeNotAcceptableException) {
            return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler);
        }

        if (ex instanceof MissingServletRequestParameterException) {
            return this.handleMissingServletRequestParameter((MissingServletRequestParameterException)ex, request, response, handler);
        }

        if (ex instanceof ServletRequestBindingException) {
            return this.handleServletRequestBindingException((ServletRequestBindingException)ex, request, response, handler);
        }

        if (ex instanceof ConversionNotSupportedException) {
            return this.handleConversionNotSupported((ConversionNotSupportedException)ex, request, response, handler);
        }

        if (ex instanceof TypeMismatchException) {
            return this.handleTypeMismatch((TypeMismatchException)ex, request, response, handler);
        }

        if (ex instanceof HttpMessageNotReadableException) {
            return this.handleHttpMessageNotReadable((HttpMessageNotReadableException)ex, request, response, handler);
        }

        if (ex instanceof HttpMessageNotWritableException) {
            return this.handleHttpMessageNotWritable((HttpMessageNotWritableException)ex, request, response, handler);
        }

        if (ex instanceof MethodArgumentNotValidException) {
            return this.handleMethodArgumentNotValidException((MethodArgumentNotValidException)ex, request, response, handler);
        }

        if (ex instanceof MissingServletRequestPartException) {
            return this.handleMissingServletRequestPartException((MissingServletRequestPartException)ex, request, response, handler);
        }

        if (ex instanceof BindException) {
            return this.handleBindException((BindException)ex, request, response, handler);
        }

        if (ex instanceof NoHandlerFoundException) {
            return this.handleNoHandlerFoundException((NoHandlerFoundException)ex, request, response, handler);
        }
    } catch (Exception var6) {
        this.logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", var6);
    }

    return null;
}

基于配置的方式自定义异常处理解析器,SimpleMappingExceptionResolver

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- exceptionMappings:配置哪些异常去哪些页面 -->
        <property name="exceptionMappings">
            <props>
                <!-- key:异常全类名;value:要去的页面视图名; -->
                <prop key="java.lang.NullPointerException">myerror</prop>
            </props>
        </property>
        <!--指定错误信息取出时使用的key  -->
        <property name="exceptionAttribute" value="ex"></property>

    </bean>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐