码迷,mamicode.com
首页 > 编程语言 > 详细

SpringMVC 学习笔记

时间:2017-08-16 11:35:06      阅读:302      评论:0      收藏:0      [点我收藏+]

标签:$.ajax   stream   驱动   error   sage   defaults   ase   row   abs   

  • Spring 为展现层提供的基于 MVC 设计理念的优秀的 Web 框架,是目前最主流的 MVC 框架之一。
  • Spring3.0 后全面超越 Struts2,成为最优秀的 MVC 框架。
  • Spring MVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无须实现任何接口。
  • Spring MVC 支持 REST 风格的 URL 请求。
  • Spring MVC 采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。

Spring MVC 处理请求的大致过程:
技术分享
技术分享


使用 Spring MVC 框架的注解进行资源请求的管理分发

Spring MVC 框架使用 DispatcherServlet 接收所有客户端请求(暂时用 /,后期可以具体使用 .do 和 .htm 以防止干扰对静态资源的请求),并在服务器端根据 @RequestMapping 注释映射到相应 Handler 进行处理。
http://blog.csdn.net/zgzczzw/article/details/53926635

<!-- Web 应用加载参数,即 servlet context 参数 -->
<servlet>
  <servlet-name>springDispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>springDispatcherServlet</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

在 springmvc 的配置文件中设置 InternalResourceViewResolver,当我们对 SpringMVC 控制的资源发起请求时,这些请求都会被 SpringMVC 的 DispatcherServlet 处理,接着 Spring 会分析看哪一个 HandlerMapping 定义的所有请求映射中存在对该请求的最合理的映射。然后通过该 HandlerMapping 取得其对应的 Handler,接着再通过相应的 HandlerAdapter 处理该Handler。HandlerMapping 用来查找处理请求的对象,HandlerAdapter 用来处理请求参数并且在对 Handler 进行处理之后返回一个 ModelAndView 对象。在获得了 ModelAndView 对象之后,Spring 就需要把该 View 渲染给用户,即返回给浏览器。在这个渲染的过程中,发挥作用的就是 ViewResolver 和 View。当 Handler 返回的 ModelAndView 中不包含真正的视图,只返回一个逻辑视图名称的时候,这时 ViewResolver 就会把该逻辑视图名称解析为真正的视图 View 对象。View 是真正进行视图渲染,把结果返回给浏览器的。
load-on-startup 标签:配置是否在应用加载阶段初始化 Servlet。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/in-pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>



InternalResourceViewResolver

它是 URLBasedViewResolver 的子类,所以 URLBasedViewResolver 支持的特性它都支持。在实际应用中 InternalResourceViewResolver 也是使用的最广泛的一个视图解析器。那么 InternalResourceViewResolver 有什么自己独有的特性呢?单从字面意思来看,我们可以把 InternalResourceViewResolver 解释为内部资源视图解析器,这就是 InternalResourceViewResolver 的一个特性。InternalResourceViewResolver 会把返回的视图名称都解析为 InternalResourceView 对象,InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。比如上面在 InternalResourceViewResolver 中定义了prefix="/WEB-INF/in-pages",suffix=".jsp",然后请求的 Controller 处理器方法返回的视图名称为 success(下面代码),那么这个时候 InternalResourceViewResolver 就会把 success 解析为一个 InternalResourceView 对象,先把返回的模型属性都存放到对应的 HttpServletRequest 属性中,然后利用 RequestDispatcher 在服务器端把请求 forword到"/WEB-INF/in-pages/success.jsp"。这就是InternalResourceViewResolver 一个非常重要的特性,我们都知道存放在"/WEB-INF/"下面的内容是不能直接通过 request 请求的方式请求到的,为了安全性考虑,我们通常会把 jsp 文件放在WEB-INF目录下,而 InternalResourceView 在服务器端跳转的方式可以很好的解决这个问题。
http://elim.iteye.com/blog/1770554


处理器映射

@RequestMapping 可以声明在 Handler 或者其所在类上,使用 value 进行请求映射,当浏览器端调取资源,会被 DispatcherServlet 分配最合适的 HandlerMapping(默认 BeanNameUrlHandlerMapping)来处理此请求的映射工作,HandlerMapping 会找到最合适的的 Handler(如下"/test/get/{param}"被映射到 TestRequestMapping 的 get()方法),然后使用 HandlerAdapter(如 RequestMappingHandlerAdapter)进行处理操作并返回 ModelAndView。

@RequestMapping 的属性类似传统的 request 的方法,可以获取请求头等信息。

例:return 一个值,被 InternalResourceViewResolver 类处理,加上前后缀进行字符串拼接,转发到指定页面(关于这里的转发与重定向:http://www.cnblogs.com/qiujiazhen/p/5451787.html):

@Controller
@RequestMapping(value = "test")
public class TestRequestMapping {

    private static final String MESSAGE = "welcome";

    @RequestMapping(value = "get/{param}", method = RequestMethod.GET)
    public String get(@PathVariable("param") Integer param) {
        System.out.println("getMe:" + param + ";");
        return MESSAGE;
    }
……

:映射的地址可以不写"/"(比如上面的 test),Spring 框架会帮加上:
技术分享

但是最好不要依赖这一特性,所以还是养成加上的习惯!


实现 PUT/DELETE 方法

在 web.xml 文件中配置 HiddenHttpMethodFilter 可以实现接收客户端的 PUT/DELETE 请求。HiddenHttpMethodFilter 是 Filter 组件的实现,其 doFilterInternal 方法会将请求的 POST 方法的隐藏域参数 "_method"(若存在)对应的值解析为相应请求,重新包装 HttpServletRequest 对象,最后放行请求,这样一来便实现了 PUT/DELETE 请求的解析。

<form action="test/put/123" method="post">
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="提交PUT请求">
</form>

注意:如果相应请求地址所映射的方法的 @RequestMapping 注释的 method 属性与该重新被包装的 Request 对象不一致,则调用资源的行为不会获得方法支持。


Handler 参数绑定

@PathVariable、@RequestParam、@RequestHeader 等注释可以加在形参之前(对应请求信息),让 Spring MVC 框架将 HTTP 请求的对应信息绑定到相应的方法形参。也可以在 Handler 形参列表中声明传统的参数,同样可以正常使用,比如:HttpServletRequest、Writer 等。

参数如果是对象形式(这个对象可能来自 @ModelAttribute、Session 域、通过反射新建,这三种方式会在下面介绍)来接收请求参数,需要将属性参数键与相应 JavaBean 的 Setter 对应,之后 Spring 框架会包装参数到相应 Bean 的对象以参数传入(这种方式支持 name 以级联方式设置):

@RequestMapping(value = "pojo")
public String pojo(User user) {
    System.out.println(user);
    return MESSAGE;
}
<form action="test/pojo" method="post">
    用户名:<input type="text" name="name"><br/>
    密码: <input type="password" name="password"><br/>
    省份: <input type="text" name="address.province"><br/>
    城市: <input type="text" name="address.city"><br/>
    <input type="submit" value="注册">
</form>

User、Address 等 Bean 省略。


模型数据信息处理

Spring MVC 提供了以下几种途径输出模型数据:

  • ModelAndView
  • Map / ModelMap / Model
  • @SessionAttributes
  • @ModelAttribute
返回 ModelAndView

ModelAndView 既包含视图信息,也包含模型数据信息,处理方法返回值类型为 ModelAndView 时, 方法体即可通过该对象添加模型数据。

技术分享

@RequestMapping("testModelAndView")
public ModelAndView testModelAndView() {
    ModelAndView mav = new ModelAndView();
    ModelMap modelMap = mav.getModelMap();
    User user = new User("cdf", "123456");
    modelMap.addAttribute("user", user);
    mav.setViewName("welcome");
    return mav;
}

关键代码:

  1. 获取更改后的 ModelAndView:
    DispatcherServlet 的 doDispatch 方法
    技术分享

  2. 获取结果,解析 ModelAndView 所包装的 Model 和 View
    技术分享
    技术分享

  3. 真正解析 View
    技术分享

  4. 真正解析 Model
    技术分享
    AbstractView 的子类实现真正的 renderMergedOutputModel 方法解析 Model,如 InternalResourceView(下图):
    技术分享
    技术分享
    技术分享
    “暴露”Model 的 key、value 给 request,最后真正实现的还是传统的方法 -- setAttribute
    技术分享

Map、Model 作为形参

方法形参为 org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 时,在处理方法返回时,Map 中的数据会自动添加到模型中(最终还是要 render ModelAndView,原理并没改变):
Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。如果方法形参为 Map 或 Model 类型,Spring MVC 会将隐含模型的引用传给对应形参。在方法体内,开发者可以通过形参访问到模型中的所有数据,也可以向模型中添加新的属性数据。

@RequestMapping("testModelAndView2")
    public String testModelAndView(Model model, Map<String, Object> map) {
        User user = new User("cdf", "123456");
        model.addAttribute("user", user);
        map.put("date", new Date());
        return "welcome";
    }
}

上例中返回的页面可以显示 user 和 date。

@SessionAttributes

@SessionAttributes 注释可以将模型中的某个属性暂存到 HttpSession 中,以便多个请求之间可以共享这个属性。在控制器类上标注 @SessionAttributes, Spring MVC 将在模型中对应的属性暂存到 HttpSession 域中。

用法如下:

  • @SessionAttributes(types=User.class)
  • @SessionAttributes(value={“user1”, “user2”})
  • @SessionAttributes(types={User.class, Dept.class})
  • @SessionAttributes(value={“user1”, “user2”}, types={Dept.class})

例:

@SessionAttributes(names = { "user" }, types = { com.cdf.springmvc.modelandview.User.class })
@Controller
public class TestHandler {
    @RequestMapping("testSessionAttribute")
    public String testSessionAttribute(Model model) {
        User user = new User("cdf", "123456");
        model.addAttribute("user", user);
        return "welcome";
    }
……

注意

  1. 同类其它方法若非均需用到指定的属性,那么一般会设置“Session 域中的属性” 的value(alias names) 与“request 域中的属性”不同,以防止出现“Session attribute 属性名 required - not found in session”的异常(比如上例还有个 Handler,但是没配置 User)。
  2. 这里与以往不同的是,参数仍然会被放到 request 域中,所以两个域都会有这个 user。
@ModelAttribute
  1. 方法形参使用 @ModelAttribute 标注后, 形参对象就会放到数据模型中。
  2. 在方法定义上使用 @ModelAttribute 注解,Spring MVC 在调用目标处理方法前,会先逐个调用在方法级上标注了 @ModelAttribute 的方法。

标参数上(指定 key 给 ModelMap 结构,具体下面讲):

<form action="testModelAndView4" method="post">
    name:<input type="text" name="name"><br/>
    password:<input type="password" name="password"><br/>
    <input type="submit" value="提交"><br/>
</form>
@RequestMapping("testModelAndView4")
public ModelAndView testModelAndView(@ModelAttribute("u") User user) {
    ModelAndView mv = new ModelAndView("welcome");
    return mv;
}
<h1>Welcome, ${requestScope.u.name }!<br/>your password is ${requestScope.u.password }</h1>

标方法上(映射到 testModelAndView4 时,控制台会打印"Pre"):

@ModelAttribute
public void testPre() {
    System.out.println("Pre");
}

@RequestMapping("testModelAndView4")
public ModelAndView testModelAndView() {
    ModelAndView mv = new ModelAndView("welcome");
    return mv;
}

形参对象的获取方式

  1. 加了 @ModelAttribute 注解,从对应的 map 中找到的。
    • 如果目标方法形参使用了 @ModelAttribute 来修饰,则 key 为注解的 value 值。
    • 如果目标方法形参没有使用 @ModelAttribute 来修饰,则 key 为类名首字母小写 User==>user,这也是例中前端页面获取 ${requestScope.user} 的原因。
  2. 从 Session 域对象中找到的。
  3. SpringMVC 从上述的两个地方没找到, 通过反射的形式创建一个对象。

标参数上的例中,ModelAndView 获取 User 对象 user 的流程是:
前端页面发送请求,传来“其 name 对应了 User 类的 Setter 方法的参数,请求映射到了 testModelAndView4 上后,被封装到 User user 中,通过 @ModelAttribute 把 User 对象放进一个 Model(实际上是 ModelAndView 里的那个 Model 的副本,这个副本最后也会放 entry 域中)里,其 key 为指定的"u"(若没指定value="u",那么 以 User 的首字母小写 "user" 为 key 添加到 Model 里),然后视图解析器解析 View,将 ‘key-value‘s 们添加到 request 域中,最后转发到目标页面,页面再通过 EL 表达式显示 user。

视图和视图解析器

视图

请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图。

Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart 等各种表现形式的视图。

对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦。

  • 视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
  • 为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口。
  • 视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题。
视图解析器
  • 程序员可以选择一种视图解析器或混用多种视图解析器。
  • 每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。
  • SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出 ServletException 异常。

技术分享
JSP 是最常见的视图技术,可以使用 InternalResourceViewResolver 作为视图解析器:

<bean>
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/in-pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

例:自定义视图,选择视图解析器解析

// 自定义视图,使用 BeanNameViewResolver 解析返回值(myview)并,实现把 model 的 value 都输出到返回的页面中去。
@Component
public class MyView implements View {

    @Override
    public String getContentType() {
        return "text/html;charset=uft-8";
    }

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        PrintWriter writer = response.getWriter();
        Collection<?> values = model.values();
        for (Object object : values) {
            writer.write(object.toString());
        }
    }
}
@RequestMapping("testView")
public String testView(Map<String, Object> map) {
    map.put("1", new String("now is "));
    map.put("2", new Date());
    map.put("3", new String(",welcome!"));
    return "myView";// 返回上面自定义的 View
}

按解析器的 order 属性值大小来解析

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/in-pages/"></property>
    <property name="suffix" value=".jsp"></property>
    <property name="order" value="2"></property>
</bean>

<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
    <property name="order" value="1"></property>
</bean>



若要使用 JSTL 的 fmt 标签则需要在 SpringMVC 的配置文件中配置国际化资源文件(注意 messageSource 是固定的):

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

注:若返回使用了 JSTL 的 JSP 页面,SpringMVC 会自动将视图改为 JstlView 类型。


若希望直接响应(不通过映射)通过 SpringMVC 渲染的页面,可以使用 mvc:view-controller 标签实现(一定要注意此时是否配置了 mvc:annotation-driven,防止出现映射请求失效的问题):

<mvc:view-controller path="straightRedirect" view-name="welcome"/>
关于重定向

一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理
如果返回的字符串中带 forward: 或 redirect: 前缀时,SpringMVC 会对他们进行特殊处理:将 forward: 和 redirect: 当成指示符,其后的字符串作为 URL 来处理
redirect:welcome.jsp:会完成一个到 welcome.jsp 的重定向的操作
forward:welcome.jsp:会完成一个到 welcome.jsp 的转发操作
注意:如果重定向到映射(通常是要访问 WEB-INF 下的不能直接访问的页面时),需要特别注意路径问题,此时一定要分清是使用相对路径还是绝对路径!


RESTful 风格 CRUD

以下是实现简单的对 User 的增删改查的操作示例,资源请求风格为 REST。页面表单使用了 Spring 提供的表单标签,通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显

:优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀,我们若将 DispatcherServlet 请求映射配置为 "/",则 Spring MVC 将捕获 WEB 容器的 所有 请求,包括静态资源的请求, SpringMVC 会将他们当成一个普通请求处理,因找不到对应处理器将导致错误。

可以在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler/> 的方式解决静态资源的问题:
<mvc:default-servlet-handler/> 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。

一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name 属性显式指定。

SpringMVC 配置:
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.cdf"></context:component-scan>

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

<!-- 引入外部文件 -->
<context:property-placeholder location="classpath:db.properties"/>

<!-- 配置C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="driverClass" value="${jdbc.driver}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" lass="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>

<!-- 开启声明式事务 -->
<tx:annotation-driven mode="proxy" transaction-manager="transactionManager"/>

<!-- 使用具名参数的 JdbcTemplate -->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
    <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

<!-- 处理静态资源 -->
<mvc:default-servlet-handler/>


映射处理层:
@Component
@RequestMapping
public class MyHandler {
    @Autowired
    private UserDAO userDAO;
    @Autowired
    private DepartmentDAO departmentDAO;

    // 显示 User 列表
    @RequestMapping(value = { "/users" }, method = { RequestMethod.GET })
    public String showUserList(Map<String, Object> map) {
        List<User> allUsers = userDAO.getAllUsers();
        map.put("users", allUsers);
        return "user-list";
    }

    // 去 User 编辑页面
    @RequestMapping(value = { "/user/{id}" }, method = { RequestMethod.GET })
    public String toEditUser(@PathVariable(name = "id") Integer id, Map<String, Object> map) {
        List<Department> allDepartments = departmentDAO.getAllDepartments();
        map.put("departments", allDepartments);// 用于下拉列表
        map.put("user", userDAO.getUserById(id));// 用于回显
        return "user-edit";
    }

    // 去 User 添加页面
    @RequestMapping(value = { "/user" }, method = { RequestMethod.GET })
    public String toAddUser(Map<String, Object> map) {
        List<Department> allDepartments = departmentDAO.getAllDepartments();
        map.put("departments", allDepartments);
        map.put("user", new User());
        return "user-edit";
    }

    // 添加 User
    @RequestMapping(value = { "/user" }, method = { RequestMethod.POST })
    public String addUser(User user) {
        userDAO.saveUser(user);
        return "redirect:/users";
    }

    // 修改 User
    @RequestMapping(value = { "/user" }, method = { RequestMethod.PUT })
    public String editUser(User user) {
        userDAO.updateUser(user);
        return "redirect:/users";
    }

    // 删除 User
    @RequestMapping(value = { "/user/{id}" }, method = { RequestMethod.DELETE })
    public String deleteUser(@PathVariable(name = "id") Integer id) {
        userDAO.deleteUser(id);
        return "redirect:/users";
    }
}


User List 视图:
<body>
    <h1 align="center">User List</h1>
    <table align="center" border="1px" width="50%" cellspacing="0px">
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Gender</th>
            <th>Department</th>
            <th>Operation</th>
        </tr>
        <c:forEach items="${requestScope.users }" var="user">
            <tr align="center">
                <td>${user.id }</td>
                <td>${user.name }</td>
                <td>${user.email }</td>
                <td>${user.gender==0?‘女‘:‘男‘}</td>
                <td>${user.depId }</td>
                <td>
                    <a class="delete" href="user/${user.id }">删除</a>&nbsp;&nbsp;<a href="user/${user.id }">修改</a>
                </td>
            </tr>
        </c:forEach>
    </table>

    <h3 align="center"><a href="user" style="text-decoration: none;color: orange;">Add New User</a></h3>

    <!-- 用于删除操作 -->
    <form action="" method="post">
        <input type="hidden" name="_method" value="DELETE">
    </form>
</body>


User Edit 视图

一般情况下,通过 GET 请求获取表单页面,而通过 POST 请求提交表单页面,因此获取表单页面和提交表单页面的 URL 是相同的。只要满足该最佳条件的契约,

<body>
    <div style="float: left; border: 50px auto;">
        <%
            Map<String, String> map = new HashMap<String, String>();
            map.put("1", "男");
            map.put("0", "女");
            request.setAttribute("genders", map);
            boolean flag = false;
        %>
        <!-- SpringMVC 认为表单值一定要回显,默认情况下会使用"command"到 request 域对象中找对应的数据。
        可以使用 form:form 中 的 modelAttribute 属性指定使用哪个 key 到 request 域对象中找数据。 -->
        <form:form action="${pageContext.servletConfig.servletContext.contextPath }/user" method="post" modelAttribute="user">
            <!-- 以 User 的  id 字段是否为空判断 修改/添加 操作 -->
            <c:if test="${empty requestScope.user.id }">
                <!-- 添加 -->
                Name:<form:input path="name" />
                <!-- path 相当于传统 input 标签的 name 属性,支持级联 -->
            </c:if>
            <c:if test="${not empty requestScope.user.id }">
                <!-- 修改,这里的 id 值为回显的值,由于指定了 modelAttribute,所以值是拿的到的,
                这样一来修改完的 User 就是完整的了,不会存在缺少id的情况 -->
                <form:hidden path="id" />
                <!-- 对于_method 不能使用 form:hidden 标签,因为 modelAttribute 对应的 bean 中没有 _method 属性,
                因此回显数据的时候找不到对应的属性就会报错 -->
                <input type="hidden" name="_method" value="PUT">
            </c:if>
            <br />

            Email:<form:input path="email" />
            <br />

            Gender:<form:radiobuttons path="gender"
                items="${requestScope.genders }" />
            <br />

            <!-- 由于 User 的 Department 依靠 depId 这个 int 型字段,发送请求后封装到 user 需要 depId,所以这里的 path 是 depId -->
            <!-- itemLabel、itemValue 分别是每个 item 的显示名和表单传的 value 值 -->
            <!-- 最终传到后台的会是 path=itemValue 形式 -->
            Department:<form:select path="depId"
                items="${requestScope.departments }" itemLabel="name" itemValue="id"></form:select>
            <br />

            <c:if test="${empty requestScope.user.id }">
                <input type="submit" value="Add User" />
            </c:if>
            <c:if test="${not empty requestScope.user.id }">
                <input type="submit" value="Update User" />
            </c:if>
        </form:form>
    </div>
</body>

SpringMVC 表单标签共有属性:

  • htmlEscape:是否对表单值的 HTML 特殊字符进行转换,默认值为 true
  • cssClass:表单组件对应的 CSS 样式类名
  • cssErrorClass:表单组件的数据存在错误时,采取的 CSS 样式

  • form:radiobutton:单选框组件标签,当表单 bean 对应的属性值和 value 值相等时,单选框被选中

  • form:radiobuttons:单选框组标签,用于构造多个单选框
    • items:可以是一个 List、String[] 或 Map
    • itemValue:指定 radio 的 value 值。可以是集合中 bean 的一个属性值
    • itemLabel:指定 radio 的 label 值
    • delimiter:多个单选框可以通过 delimiter 指定分隔符
  • form:checkbox:复选框组件。用于构造单个复选框
  • form:checkboxs:用于构造多个复选框。使用方式同 form:radiobuttons 标签
  • form:select:用于构造下拉框组件。使用方式同 form:radiobuttons 标签
  • form:option:下拉框选项组件标签。使用方式同 form:radiobuttons 标签
  • form:errors:显示表单组件或数据校验所对应的错误
    • <form:errors path= “ *” /> :显示表单所有的错误
    • <form:errors path= “ user*” /> :显示所有以 user 为前缀的属性对应的错误
    • <form:errors path= “ username” /> :显示特定表单对象属性的错误



数据转换、格式化、校验

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

数据绑定流程

Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的形参中。数据绑定的核心部件是 DataBinder,运行机制如下:
技术分享


类型转换、格式化(通常是一起的)过程大概如下:
技术分享
技术分享


执行 bindRequestParameters 后:
技术分享

数据转换

ConversionService 是 Spring 类型转换体系的核心接口。
可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定义一个 ConversionService。Spring 将自动识别出 IOC 容器中的 ConversionService,可以在 Bean 属性配置及 SpringMVC 处理方法形参绑定等场合使用它进行数据的转换及格式化。

可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器,下面模拟实现一个把请求参数的 String 转换为 User 的自定义规则 Conterver(注意在 annotation-driven 中声明 ConversionService):

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters" ref="myConverter"></property>
</bean>

<!-- 将自定义的 ConversionService 注册到 SpringMVC 的上下文中 -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
@Component
public class MyConverter implements Converter<String, User> {
    @Override
    public User convert(String param) {
        String[] parts = param.split("-", 4);// 请求参数格式:name-email-gender-department
        if (parts == null)
            return null;
        User user = new User();
        user.setName(parts[0]);
        user.setEmail(parts[1]);
        user.setGender(Integer.parseInt(parts[2]));
        user.setDepId(Integer.parseInt(parts[3]));
        return user;
    }
}
// 测试自定义 Converter
@RequestMapping(value = { "/converter" }, method = { RequestMethod.POST })
public String testConverter(@RequestParam(value = "user") User user) {
    System.out.println("请求字符串通过 MyConverter 解析后,得到的 User 是:\n" + user);
    userDAO.saveUser(user);
    return "redirect:/users";
}

测试:
技术分享

DEBUG 模式发现:
技术分享

结果:
技术分享


<mvc:annotation-driven>

<mvc:annotation-driven> 会自动注册 RequestMappingHandlerMapping(默认是 AnnotationMethodHandlerAdapter,Spring3.2之后被 RequestMappingHandlerMapping 替代) 、RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个bean,还将提供以下支持:

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

技术分享
配置处理静态资源(<mvc:default-servlet-handler>)后,若不配置 annotation-driven,会导致 ConversionService 失效,如上图第二种情况(此时使用的 ConversionService 为 DefaultConversionService,它并不具备数据格式化的功能),此时需要配置 annotation-driven 并设置 conversion-service 为“包含可以转换并格式化请求数据的 Converter 的 ConverterService”。此时还会导致 RequestMappingHandlerAdapter 加载不了,最终导致只能处理静态资源请求,而无法映射到 Handler 的方法。

@InitBinder

由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定,并且提供设置校验器、转换器等等方法。

  • @InitBinder方法不能有返回值,它必须声明为void。
  • @InitBinder方法的参数通常是是 WebDataBinder。

例:

@InitBinder
public void testInitBinder(WebDataBinder binder) {
    binder.setDisallowedFields("name");
}


数据格式化

Spring 在格式化模块中定义了一个实现 ConversionService 接口的实现类: FormattingConversionService,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能
FormattingConversionService 拥有一个 FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者。
装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 形参绑定及模型数据输出时使用注解驱动了。<mvc:annotation-driven/> 默认创建的 ConversionService 实例即为 FormattingConversionServiceFactroyBean(这就是为什么上文例子中配置了<mvc:annotation-driven/>但没设定 ConversionService 属性时,也可以正常映射参数到 Bean 属性的原因)。

FormattingConversionServiceFactroyBean 内部已经注册了 :

  • NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解。@NumberFormat 拥有两个互斥的属性:

    • style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、 Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型)。
    • pattern:类型为 String,自定义样式,如patter="#,###"。
  • JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解,可对 java.util.Date、java.util.Calendar、java.long.Long 时间类型进行标注,@DateTimeFormat 属性说明:

    • pattern 属性:类型为字符串。指定解析/格式化字段数据的模式,如:”yyyy-MM-dd hh:mm:ss”。
    • iso 属性:类型为 DateTimeFormat.ISO。指定解析/格式化字段数据的 ISO 模式,包括四种:ISO.NONE(不使用) -- 默认、ISO.DATE(yyyy-MM-dd) 、ISO.TIME(hh:mm:ss.SSSZ)、 ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)。
    • style 属性:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整日期/时间格式、-:忽略日期或时间格式。

例:

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

做数据转换、格式化、校验后,SpringMvC 会将发生的异常全部封装到 BindingResult 对象中去,可以通过方法形参传入一个 BindingResult(或其父类 Errors) 对象来获取错误信息:

// 添加 User
@RequestMapping(value = { "/user" }, method = { RequestMethod.POST })
public String addUser(User user, BindingResult result) {
    List<FieldError> fieldErrors = result.getFieldErrors();
    for (FieldError fieldError : fieldErrors) {
        System.out.println("错误发生在:" + fieldError.getField() + "\n错误信息:" + fieldError.getDefaultMessage());
    }
    userDAO.saveUser(user);
    return "redirect:/users";
}

打印错误信息示例:
技术分享
注意,这里的 BindingResult 对象声明需要紧挨着 Bean 声明。

数据校验

JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中。
JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证:
技术分享
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
技术分享

  • Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。

  • Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验。

  • Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在 Spring 容器中定义了一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中。

  • Spring 本身并没有提供 JSR303 的实现,所以必须将 JSR303 的实现者的 jar 包放到类路径下

  • Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”,即使处理方法的签名中没有对应于表单/命令对象的结果形参,校验结果也会保存在 “隐含对象” 中。隐含模型中的所有数据最终将通过 HttpServletRequest 的对象,因此在 JSP 中可以获取错误信息属性列表暴露给 JSP 视图。在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息(需要注意的是:此标签需要在<form:form>内使用)。

<mvc:annotation-driven/> 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的形参上标注 @Valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作。

在已经标注了 JSR303 注解的表单/命令对象前标注一个 @Valid,Spring MVC 框架在将请求参数绑定到该行参对象后,就会调用校验框架根据注解声明的校验规则实施校验。

Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的形参中,这个保存校验结果的形参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中。

需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的形参。

Hibernate Validator 则是JBoss社区开源的一个JSR 303 Bean Validation 规范的优秀实践:
技术分享

@Past
private Date birthday;
// 添加 User
@RequestMapping(value = { "/user" }, method = { RequestMethod.POST })
public String addUser(@Valid @ModelAttribute("user") User user, BindingResult result, Map<String, Object> map) {
    List<FieldError> fieldErrors = result.getFieldErrors();
    System.out.println(user.getBirthday());
    if (result.hasErrors()) {// 出错,回到添加页面
        for (FieldError fieldError : fieldErrors) {
            System.out.println("错误发生在:" + fieldError.getField() + "\n错误信息:" + fieldError.getDefaultMessage());
        }
        map.put("departments", departmentDAO.getAllDepartments());
        return "user-edit";
    }
    userDAO.saveUser(user);
    return "redirect:/users";
}



处理 JSON

坑:注意 springmvc 4.0 之前的版本是 MappingJacksonHttpMessageConverter,而之后是 MappingJackson2HttpMessageConverter,前者适配 1.x 版本的 jackson,后者为 2.x 版本,而且后者的依赖关系要增加两项:除了 jackson-core 外还要 jackson-databind 和 jackson-annotations!!!

SpringMVC 提供了 <mvc:message-converters> 标签以声明 MessageConverter,此接口定义了 Java 对象与 JMS 消息之间的转换策略。该标签是 <mvc:annotation-driven> 子标签,可以在该标签中声明 bean,让 SpringMVC 在 @ResponseBody 绑定的方法/属性上作用该类的转换策略(例如 String 转 XML 等)。

技术分享

SpringMVC 框架默认支持 jackson 的使用,不需要另外声明其对应的 MessageConverter 实现类,只需要导入相应 jar 包即可,下面演示使用阿里巴巴开源的 json 转换工具--fastjson 在 SpringMVC 中的使用方式:
注册 fastjson 的 MessageConverter:

<mvc:annotation-driven conversion-service="conversionService">
    <mvc:message-converters register-defaults="true">
        <!-- @ResponseBody 乱码问题,将 StringHttpMessageConverter 的默认编码设为UTF-8 -->
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <constructor-arg value="UTF-8" />
        </bean>
        <!-- 配置 fastjson 支持 -->
        <bean
            class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <property name="charset" value="UTF-8" />
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json</value>
                    <value>text/html;charset=UTF-8</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
// 测试接收异步并返回 json 数据
@ResponseBody
@RequestMapping(value = { "/ajax" }, method = { RequestMethod.POST })
public String testAJAX() {
    return "测试异步获取信息-使用 fastjson";
}
<div>
    <a id="ajax" href="ajax">异步请求:弹框显示返回信息</a>
</div>
$("#ajax").click(function() {
    var url = $(this).attr("href");
    $.ajax({
        type:"POST",
        url:url,
        success:function(msg){
            alert(msg);
        }
    })
    return false;
});

测试结果:
技术分享



HttpMessageConverter<T>

HttpMessageConverter<T> 是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为 T),我们可以将对象(类型为 T)输出为响应信息。

HttpMessageConverter<T>接口定义的方法:
  • Boolean canRead(Class<?> clazz , MediaType mediaType): 指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象,同时指定支持 MIME 类型(text/html,applaiction/json等)
  • Boolean canWrite(Class<?> clazz , MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在MediaType 中定义。
  • List<MediaType> getSupportMediaTypes():该转换器支持的媒体类型。
  • T read(Class<? extends T> clazz , HttpInputMessage inputMessage):将请求信息流转换为 T 类型的对象。
  • void write(T t , MediaType contnetType , HttpOutputMessgae outputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类型为 contentType。
前端与后台的消息传递:

技术分享

HttpMessageConverter<T> 的实现类

技术分享

DispatcherServlet 默认装配 RequestMappingHandlerAdapter,而 RequestMappingHandlerAdapter 默认装配如下 HttpMessageConverter:
技术分享

由于框架有 jackson 的对应 MessageAdapter(MappingJackson2HttpMessageConverter),在添加 jackson 相关 jar 包后,RequestMappingHandlerAdapter 装配的 HttpMessageConverter 变为:
技术分享

在上例中,我们使用了 fastjson 包(未添加 jackson 相关包),并在配置文件中配置 fastjson 后,RequestMappingHandlerAdapter 装配的 HttpMessageConverter 才变为(注意,此时默认使用的 MappingJackson2HttpMessageConverter 就不加载了):
技术分享

使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的形参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:

  • 使用 @RequestBody / @ResponseBody 对处理方法进行标注。
  • 使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的形参或返回值。

当控制器处理方法使用到 @RequestBody/@ResponseBody 或 HttpEntity<T>/ResponseEntity<T> 时, Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter, 进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的 HttpMessageConverter 将报错,具体:

  • @RequestBody
    读取 Request 请求的 body 部分数据,使用系统默认配置的 HttpMessageConverter 进行解析,然后把相应的数据绑定到要返回的对象上。再把 HttpMessageConverter 返回的对象数据绑定到 controller 中方法的参数上。
  • @ResponseBody
    将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区(响应体)。

@RequestBody 和 @ResponseBody 不需要成对出现。



国际化

默认情况下,SpringMVC 根据请求头中的 Accept-Language 判断客户端的本地化类型。

当接受到请求时,SpringMVC 会在上下文中查找一个本地化解析器(LocalResolver),找到后使用它获取请求所对应的本地化类型信息。

SpringMVC 还允许装配一个动态更改本地化类型的拦截器,这样通过指定一个请求参数就可以控制单个请求的本地化类型。

我们需要关注以下问题:

  1. 在页面能够根据浏览器语言设置情况对文本、时间、数值等进行本地化的显示。

  2. 在 Handler 方法中获取当前 Locale 对应的国际化资源文件中的信息。

  3. 通过超链接切换 Locale,不再依赖浏览器的原设置情况(不是简单的写两个页面)。

解决上述问题的方式如下:

问题1

可以通过按一定的规则命名、编写国际化资源文件,并在配置文件中声明 ResourceBundleMessageSource,之后使用 JSTL 的 <fmt:message> 标签即可。

问题2

在 Handler 中装配 ResourceBundleMessageSource 并在 Handler 方法形参处传入 Locale 对象,即可使用 ResourceBundleMessageSource 对象的方法(getMessage())来获取当前使用的国际化资源文件中指定的信息。

问题3
  1. 配置拦截器:LocaleChangeInterceptor,以拦截请求中相应参数。
    • LocaleChangeInterceptor:从请求参数中获取本次请求对应的本地化类型。
  2. 配置 LocaleResolver:SessionLocaleResolver,以解析并设置 Locale 到域中。
    • AcceptHeaderLocaleResolver:根据 HTTP 请求头的 Accept-Language 参数确定本地化类型,如果没有显式定义本地化解析器, SpringMVC 默认使用该解析器。
    • CookieLocaleResolver:根据指定的 Cookie 值确定本地化类型。
    • SessionLocaleResolver:根据 Session 中特定的属性确定本地化类型。

例:

<!-- 配置拦截器 -->
<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>

<!-- 配置 LocaleResolver -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>

默认使用请求参数名为“locale”的参数值作为创建 Session 域中 Locale 对象的依据:
技术分享

默认使用 id 为“localeResolver”的 LocaleResolver:
技术分享

若未声明 LocaleResolver,SpringMVC 默认使用 AcceptHeaderLocaleResolver:
技术分享

经过上述两个步骤后,就可以实现“通过在请求中使用一个 key 为‘locale’的请求参数指定语言信息,从而转换 fmt:message 标签的显示内容”。实现过程大致如下:
技术分享

技术分享

技术分享

技术分享

流程图:
技术分享



文件上传

Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResolver,需要导入 Apache Commons FileUpload、Apache Commons IO 包。

技术分享

技术分享

Spring MVC 上下文中默认没有装配 MultipartResolver,需在上下文中配置 MultipartResolver。

示例:

<form:form
    action="${pageContext.servletContext.contextPath }/fileUpload"
    method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="上传">
</form:form>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"></property>
    <property name="maxUploadSize" value="1024000"></property>
</bean>
@RequestMapping(value = { "/fileUpload" }, method = RequestMethod.POST)
public String fileRec(@RequestParam("file") MultipartFile file, HttpSession session) {
    System.out.println("接收文件的真实文件名:" + file.getOriginalFilename());
    System.out.println("MultipartFile 对象 file 的实际类型:" + file.getClass().getName());

    InputStream inputStream = null;
    OutputStream fos = null;
    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;
    try {
        // MultipartFile 内建了 InputStream
        inputStream = file.getInputStream();
        System.out.println("MultipartFile 封装的输入流的真实类型:" + inputStream);
        bis = new BufferedInputStream(inputStream);

        // 将获取到的文件存储到 FileRec 目录下
        ServletContext servletContext = session.getServletContext();
        String realPath = servletContext.getRealPath("FileRec");
        String path = realPath + File.separator + file.getOriginalFilename();
        System.out.println("存储路径:" + path);

        fos = new FileOutputStream(path);
        bos = new BufferedOutputStream(fos);

        int len = 0;
        byte[] b = new byte[1024];
        while ((len = bis.read(b)) != -1) {
            bos.write(b, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (bos != null) {
                bos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            if (bis != null) {
                bis.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return "user-list";
}



拦截器

Spring MVC 可以使用拦截器对请求进行拦截处理,自定义的拦截器须实现 HandlerInterceptor 接口。该接口定义了以下方法:

  • preHandle():在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理,并根据业务逻辑返回 true 或 false 以执行 doDispatch 的继续或者退出。
  • postHandle():在业务处理器处理完请求后、DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求 request 进行处理。
  • afterCompletion():在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。

流程如下:

技术分享

技术分享

技术分享

  1. preHandle() 按照所属 HandlerInterceptor 的声明顺序执行,而 postHandle() 和 afterCompletion() 则按照声明逆序执行。
  2. 如果配置了多个拦截器,且后配置的拦截器的 preHandle() 方法返回了 false,那么它之前配置的拦截器的 afterComplention() 还是会执行。换句话说,成功对请求完成处理了的 HandlerInterceptor 的 afterComplention() 必然会执行

有关第1点的说明:

技术分享

技术分享

技术分享

有关第2点的说明:

技术分享

技术分享

例:
自定义两个拦截器,其 preHandle() 方法分别返回 true 和 false:

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("MyInterceptor----preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor----postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("MyInterceptor----afterCompletion");
    }

}
public class MyInterceptor2 implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("MyInterceptor2----preHandle");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor2----postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("MyInterceptor2----afterCompletion");
    }

}

先后声明两个拦截器:

<!-- 配置拦截器,在 mvc:interceptors 标签中声明的 Interceptor 会拦截所有请求。 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 在 mvc:interceptor 标签中声明的 Interceptor 会拦截对应 mvc:mapping 定义的请求。 -->
        <mvc:mapping path="/testMyInterceptor" />
        <bean class="com.cdf.interceptor.MyInterceptor"></bean>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/testMyInterceptor" />
        <bean class="com.cdf.interceptor.MyInterceptor2"></bean>
    </mvc:interceptor>
</mvc:interceptors>

请求映射:

<a href="${pageContext.servletContext.contextPath }/testMyInterceptor">测试拦截器</a>
@RequestMapping(value = "/testMyInterceptor", method = RequestMethod.GET)
public String testInterceptor() {
    System.out.println("成功进入…");
    return "redirect:users";
}

测试结果:
技术分享
可以看到,方法并没有成功进入,并且 MyInterceptor2 的 afterCompletion 方法也执行了,与预想的一样。



异常处理

SpringMVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。

当配置文件没有声明 mvc:annotation-driven 时,DispatcherServlet 默认装配:
技术分享

而当声明了 mvc:annotation-driven 时,ExceptionHandlerExceptionResolver 会取代已过时的 AnnotationMethodHandlerExceptionResolver:
技术分享

技术分享

ExceptionHandlerExceptionResolver
  • 主要处理 Handler 中用 @ExceptionHandler 注解注释了的方法。

  • @ExceptionHandler 注解定义的方法优先级问题:例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,即标记了 RuntimeException 的方法。

  • ExceptionHandlerMethodResolver 内部若找不到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 注解注释的类中的 @ExceptionHandler 注解方法。

  • @ExceptionHandler 注解定义的方法形参中不能定义 Map。

例:

@ExceptionHandler(RuntimeException.class)
public ModelAndView testAdvice(RuntimeException e) {
    ModelAndView mv = new ModelAndView("error");
    mv.addObject("exception", e);
    return mv;
}

// 测试异常处理
@RequestMapping(value = "/testRuntimeException", method = RequestMethod.GET)
public String testNullPointerException() {
    throw new RuntimeException("测试异常");
}

// 测试我的异常处理
@RequestMapping(value = "/testMyException", method = RequestMethod.GET)
public String testMyException() {
    throw new MyException("测试我的异常");
}
@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler
    public ModelAndView testMyExceptionHandler(MyException e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("advice", e);
        return mv;
    }
}
ResponseStatusExceptionResolver
  • 在自定义异常类声明上使用 @ResponseStatus 注解。

  • 若在处理器方法中抛出了上述异常且 ExceptionHandlerExceptionResolver 不解析此异常,那么 @ResponseStatus 注解的 value 定义的 HttpStatus 状态码将会响应给客户端。

例:

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "我们不是谷歌,我们只是谷歌的搬运工~")
public class MyException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }

}

技术分享

DefaultHandlerExceptionResolver

对一些特殊的异常进行处理,比如:
NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException 等。

SimpleMappingExceptionResolver

如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。

例:

<!-- 配置 SimpleMappingExceptionResolver 让异常发生时响应其对应的视图-->
<bean id="simpleMappingExceptionResolver"
    class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="com.cdf.exception.MyException">runtime-exception</prop>
        </props>
    </property>
</bean>



SpringMVC 运行流程

技术分享

SpringMVC 学习笔记

标签:$.ajax   stream   驱动   error   sage   defaults   ase   row   abs   

原文地址:http://www.cnblogs.com/chendifan/p/7371860.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!