其实 Spring 的基本思想就是“万物都是 bean”,那么为了满足 spring 工程的需要,spring 中有一些默认的 bean 选项,它们用于处理请求,渲染视图等。比如上一篇文章就用过的 viewResolver 的配置。当然,servlet 也允许你配置使用不同特定的 bean,但是,如果你没有配置,spring 将会按照默认的 bean 进行配置。本章将会详细说明文档中列出的 bean 的配置以及具体的使用例子,所讲述的 bean 类型包括:
本章节将基于文档实践(一)的代码进行后续的操作,因此我们使用了单个 ContextConfig 来配置工程 Context 对象,也就是 root-context.xml 文件。另一方面,为了实现 HandlerMapping 在 xml 配置的功能,我们关掉了
<mvc:annotation-driven/> 复制代码
的功能,使得 @Controller 注解下的类不再会被自动配置并且做 url 的映射,现在再去试一下 localhost:8080/hello.do 的话,已经是 404 Not Found 了。之后再进行后续的实践过程。
这里 HandlerMapping 和 HandlerAdapter 一起讲是因为,HandlerMapping 需要 HandlerAdapter 的支持才能正常运行。HandlerMapping 用于将请求的 url 映射到对应的 controller 上面,如果没有进行配置的话,@Controller 注解即为 HandlerMapping,上一篇的 ExampleController 即有着和上述相似的功能。值得注意的是,Spring MVC 4.0 之后主推 Annotation Driven,也就是注解驱动模式下的工程,因此,对应的 adapter 已经标记为 deprecated,不推荐使用,这里只做帮助理解使用。
由于工程中的 Controller 都是用注解配置的,因此,在 DispatcherServlet 根据 bean 的配置信息(root-context.xml,我们用 Context 对象来配置 bean 的信息)知道了自己所需要调用的 controller 之后,他需要根据注解来提取其他的所需要的信息。这时候就需要 HandlerAdapter 来做这些解析的事情。
然而,目前的 Spring MVC 的配置都基于注解,因此,HandlerAdapter 也退居幕后,@Controller 注解包含了其中逻辑,在 Annotation-driven 被我们关掉的场景下,也只要做好 HandlerMapping,就可以成功地映射你想要的 url
HandlerMapping 本质还是一个 Bean,他在 Spring MVC 装配完成之后,执行着将 URL 的请求转发到对应的 Controller 执行后续视图,数据等返回的工作。因此,在配置 HandlerMapping Bean 的时候,需要配置 property 的 mappings 字段,并且在 字段下面指定对应的请求映射。具体代码如下:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/handler-mapping.do">handlerMappingController</prop> </props> </property> </bean> 复制代码
为了同步一下,目前 root-context.xml (Spring Context 对象配置文件) 的配置加入了 HandlerMapping 的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="com.test.myapp.example"/> <!--注册一个用于 handlerMapping 的 bean 用于检测 handlerMapping 效果--> <bean id="handlerMappingController" class="com.test.myapp.example.handlermapping.HandlerMappingController"/> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/handler-mapping.do">handlerMappingController</prop> </props> </property> </bean> <!--<bean id="simpleHandler" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>--> <!--<mvc:annotation-driven/>--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/views/" p:suffix=".jsp" p:order="1"> </bean> </beans> 复制代码
并且新增了 HandlerMappingController.java 的配置:
package com.test.myapp.example.handlermapping; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Usage: 测试 handler mapping 的有效性 * @author: srfan * Date: 10/26/18 4:11 PM */ @Controller public class HandlerMappingController { @RequestMapping(value="/handler-mapping.do", method = RequestMethod.GET) public String helloWorld() { return "handler_mapping_hello"; } } 复制代码
我们看到,HandlerMapping 下面配置了 /handler-mapping.do 的映射。因此,在运行工程之后,输入 localhost:8080/handler-mapping.do,就可以看到对应的 handler_mapping_hello.jsp 上的前端视图返回。
HandlerExceptionResolver 是工程中用于捕获特定 Exception 的 Bean,可以提前设定自己需要捕获并且定向的 Exception,并且交由 HandlerExceptionResolver 映射到特定的视图页上面。 目前常用的方法有:
HandlerExceptionResolver 接口只有一个待实现的方法
ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4); 复制代码
为了工程上面比较直观简便的实现,我们只需要做最简单的实现:拿到 Exception 的具体类,并且返回对应的 error 的视图,并且记录下 Exception 的 message,显示在视图页面上面。因此我们的工序如下:
package com.test.myapp.example.handlermapping; public class MyCustomException extends RuntimeException { public MyCustomException(String msg) { super(msg); } } 复制代码
这个 Exception 类很简单,只是把 message 放进 Exception 中,无需赘述,主要是要让 ExceptionResolver 捕获该 Exception。
package com.test.myapp.example.handlermapping; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class ExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { if (e instanceof MyCustomException) { ModelAndView modelAndView = new ModelAndView("error"); modelAndView.addObject("msg", e.getMessage()); return modelAndView; } return null; } } 复制代码
我们使用 ExceptionResolver 实现了 resolveException 方法,并且会解析 MyCustomException 并且在 ModelAndView 对象加入一个变量,并且返回名为 "error" 的 jsp 视图。我们也可以在 error.jsp 上显示这个 msg 字段的信息。
为了对照效果,我们实现两个接口,一个会抛出 MyCustomException,另一个则会抛出普通的 IllegalArgumentException,而我们需要捕获的则是 MyCustomException。
package com.test.myapp.example.handlermapping; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class HandlerMappingController { @RequestMapping(value="/handler-mapping.do", method = RequestMethod.GET) public String helloWorld() { return "handler_mapping_hello"; } @RequestMapping(value="/custom-exception.do", method = RequestMethod.GET) public String throwException() { throw new MyCustomException("oh, you got custom exception message~!"); } @RequestMapping(value="/argument-exception.do", method = RequestMethod.GET) public String throwArgumentException() { throw new IllegalArgumentException("oh, you got argument exception message~!"); } } 复制代码
视图文件 error.jsp 比较简单,只要体现 msg 字段即可:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Ooooops, you meet MyCustomException</title> </head> <body> <h1>${msg}</h1> </body> </html> 复制代码
运行工程后,在浏览器分别输入:
另一种方法是使用 @ExceptionHandler 的注解,该注解用于 method 的签名上面,我们可以实现一个 Controller 的基类并让实际接收 url 请求的 Controller 继承该基类。值得注意的是,这个方法实现的 ExceptionResolver 只会在该 Controller 内部有效,而来自其他 Controller 类的 Exception 则无法得到解析。具体代码步骤如下:
我们为这一次测试也设置了自定义的 Exception 类,实现方法也很简单,可以自定义 Exception 中的信息:
package com.test.myapp.example.exceptionresolver; public class CustomExceptionForAnnotation extends RuntimeException { public CustomExceptionForAnnotation(String msg) { super(msg); } } 复制代码
我们的 Controller 基类需要 Resolve CustomExceptionForAnnotation,需要用 @ExceptionHandler(CustomExceptionForAnnotation.class) 进行配置,具体方法如下:
package com.test.myapp.example.exceptionresolver; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; public abstract class BaseExceptionResolver { @ExceptionHandler({CustomExceptionForAnnotation.class}) public ModelAndView handleCustomException(CustomExceptionForAnnotation ex) { ModelAndView modelAndView = new ModelAndView("error"); modelAndView.addObject("msg", ex.getMessage()); return modelAndView; } } 复制代码
可以看到,该类中所含有的方法仅会解析 CustomExceptionForAnnotation 类,并且将其重新导向 error.jsp 视图,最后输出对应的 message 信息到前端。
为了使测试结果有对照性,我们实现了两个 Controller 类,一个继承自 BaseExceptionResolver,另一个则没有。理论上说,继承了 BaseExceptionResolver 的 Controller 将可以解析上面的 Exception,而另一个则不能。具体的配置方法如下:
继承了 BaseExceptionResolver 的 Controller 类 package com.test.myapp.example.exceptionresolver;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class MyExceptionController extends BaseExceptionResolver { @RequestMapping("exception-for-annotation.do") public void exceptionForAnnotation() { throw new CustomExceptionForAnnotation("Oooops, you get CustomExceptionForAnnotation message"); } } 复制代码
未继承 BaseExceptionResolver: package com.test.myapp.example.exceptionresolver;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class MyExceptionOutsideController { @RequestMapping("exception-for-annotation-outside.do") public void exceptionForAnnotation() { throw new CustomExceptionForAnnotation("Oooops, you get CustomExceptionForAnnotation message"); } } 复制代码
我们仍然使用了 error.jsp 视图来做最后的测试工作,我们看到 BaseExceptionResolver 在捕获异常后,仍然会输出 error.jsp 的视图。我们将会请求两个具体 Controller 类的 url,观察是否会有我们想要的视图的输出:
本章主要讲述了 HandlerMapping 和 HandlerExceptionResolver 的具体实现代码,一个是处理正常的 url 请求的映射工具,而另一个则是专门处理工程在运行过程中出现 Exception 的处理方法。下一次我将继续介绍后面这几个特殊 Bean 的用法。