WebApplicationContext 中特殊的 bean 類型(一)--- 請求/異常處理

前言

其實 Spring 的基本思想就是「萬物都是 bean」,那麼爲了知足 spring 工程的須要,spring 中有一些默認的 bean 選項,它們用於處理請求,渲染視圖等。好比上一篇文章就用過的 viewResolver 的配置。固然,servlet 也容許你配置使用不一樣特定的 bean,可是,若是你沒有配置,spring 將會按照默認的 bean 進行配置。本章將會詳細說明文檔中列出的 bean 的配置以及具體的使用例子,所講述的 bean 類型包括:html

  • HandlerMapping 和 HandlerAdapter
  • HandlerExceptionResolver
  • LocaleResolver & LocaleContextResolver
  • ThemeResolver
  • MultipartResolver

HandlerAdapter 和 HandlerMapping 解析

前期準備

本章節將基於文檔實踐(一)的代碼進行後續的操做,所以咱們使用了單個 ContextConfig 來配置工程 Context 對象,也就是 root-context.xml 文件。另外一方面,爲了實現 HandlerMapping 在 xml 配置的功能,咱們關掉了前端

<mvc:annotation-driven/>
複製代碼

的功能,使得 @Controller 註解下的類再也不會被自動配置而且作 url 的映射,如今再去試一下 localhost:8080/hello.do 的話,已是 404 Not Found 了。以後再進行後續的實踐過程。java

這裏 HandlerMapping 和 HandlerAdapter 一塊兒講是由於,HandlerMapping 須要 HandlerAdapter 的支持才能正常運行。HandlerMapping 用於將請求的 url 映射到對應的 controller 上面,若是沒有進行配置的話,@Controller 註解即爲 HandlerMapping,上一篇的 ExampleController 即有着和上述類似的功能。值得注意的是,Spring MVC 4.0 以後主推 Annotation Driven,也就是註解驅動模式下的工程,所以,對應的 adapter 已經標記爲 deprecated,不推薦使用,這裏只作幫助理解使用。web

HandlerAdapter

因爲工程中的 Controller 都是用註解配置的,所以,在 DispatcherServlet 根據 bean 的配置信息(root-context.xml,咱們用 Context 對象來配置 bean 的信息)知道了本身所須要調用的 controller 以後,他須要根據註解來提取其餘的所須要的信息。這時候就須要 HandlerAdapter 來作這些解析的事情。spring

然而,目前的 Spring MVC 的配置都基於註解,所以,HandlerAdapter 也退居幕後,@Controller 註解包含了其中邏輯,在 Annotation-driven 被咱們關掉的場景下,也只要作好 HandlerMapping,就能夠成功地映射你想要的 url瀏覽器

HandlerMapping

HandlerMapping 本質仍是一個 Bean,他在 Spring MVC 裝配完成以後,執行着將 URL 的請求轉發到對應的 Controller 執行後續視圖,數據等返回的工做。所以,在配置 HandlerMapping Bean 的時候,須要配置 property 的 mappings 字段,而且在 字段下面指定對應的請求映射。具體代碼以下:spring-mvc

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
     	<props>
         	<prop key="/handler-mapping.do">handlerMappingController</prop>
        </props>
	</property>
</bean>
複製代碼

HandlerAdapter 和 HandlerMapping 的測試

爲了同步一下,目前 root-context.xml (Spring Context 對象配置文件) 的配置加入了 HandlerMapping 的配置:mvc

<?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 的配置:app

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 上的前端視圖返回。jsp

HandlerExceptionResolver 解析

HandlerExceptionResolver 是工程中用於捕獲特定 Exception 的 Bean,能夠提早設定本身須要捕獲而且定向的 Exception,而且交由 HandlerExceptionResolver 映射到特定的視圖頁上面。 目前經常使用的方法有:

  • 實現 HandlerExceptionResolver 接口
  • 在方法上使用 @ExceptionHandler 註解

實現 HandlerExceptionResolver 接口

HandlerExceptionResolver 接口只有一個待實現的方法

ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4);
複製代碼

爲了工程上面比較直觀簡便的實現,咱們只須要作最簡單的實現:拿到 Exception 的具體類,而且返回對應的 error 的視圖,而且記錄下 Exception 的 message,顯示在視圖頁面上面。所以咱們的工序以下:

實現一個自定義的 Exception: MyCustomException

package com.test.myapp.example.handlermapping;

public class MyCustomException extends RuntimeException {
    public MyCustomException(String msg) {
        super(msg);
    }
}
複製代碼

這個 Exception 類很簡單,只是把 message 放進 Exception 中,無需贅述,主要是要讓 ExceptionResolver 捕獲該 Exception。

實現 HandlerExceptionResolver 接口:ExceptionResolver

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 字段的信息。

HandlerMappingController 添加兩個會拋出 Exception 的接口

爲了對照效果,咱們實現兩個接口,一個會拋出 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 配置

視圖文件 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 註解

另外一種方法是使用 @ExceptionHandler 的註解,該註解用於 method 的簽名上面,咱們能夠實現一個 Controller 的基類並讓實際接收 url 請求的 Controller 繼承該基類。值得注意的是,這個方法實現的 ExceptionResolver 只會在該 Controller 內部有效,而來自其餘 Controller 類的 Exception 則沒法獲得解析。具體代碼步驟以下:

設置自定義 Exception: CustomExceptionForAnnotation

咱們爲這一次測試也設置了自定義的 Exception 類,實現方法也很簡單,能夠自定義 Exception 中的信息:

package com.test.myapp.example.exceptionresolver;

public class CustomExceptionForAnnotation extends RuntimeException {
    public CustomExceptionForAnnotation(String msg) {
        super(msg);
    }
}
複製代碼

實現有 @ExceptionHandler 註解的 Controller 基類

咱們的 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 類

爲了使測試結果有對照性,咱們實現了兩個 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,觀察是否會有咱們想要的視圖的輸出:

  • localhost:8080/exception-for-annotation.do: 成功輸出了咱們放入 CustomExceptionForAnnotation 的信息。
  • localhost:8080/exception-for-annotation-outside.do: 頁面輸出了 500 的錯誤信息,而且帶上了 Exception 中的信息,由於其沒有繼承 BaseExceptionResolver,所以也沒有對應的 Exception 解析器了。

小結

本章主要講述了 HandlerMapping 和 HandlerExceptionResolver 的具體實現代碼,一個是處理正常的 url 請求的映射工具,而另外一個則是專門處理工程在運行過程當中出現 Exception 的處理方法。下一次我將繼續介紹後面這幾個特殊 Bean 的用法。

相關文章
相關標籤/搜索