5千字的SpringMVC總結,我以爲你會須要

思惟導圖

微信公衆號已開啓:【java技術愛好者】,還沒關注的記得關注哦~html

文章已收錄到個人Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary前端

概述

SpringMVC再熟悉不過的框架了,由於如今最火的SpringBoot的內置MVC框架就是SpringMVC。我寫這篇文章的動機是想經過回顧總結一下,從新認識SpringMVC,所謂溫故而知新嘛。java

爲了瞭解SpringMVC,先看一個流程示意圖:git

從流程圖中,咱們能夠看到:程序員

  • 接收前端傳過來Request請求。
  • 根據映射路徑找到對應的處理器處理請求,處理完成以後返回ModelAndView。
  • 進行視圖解析,視圖渲染,返回響應結果。

總結就是:參數接收,定義映射路徑,頁面跳轉,返回響應結果github

固然這只是最基本的核心功能,除此以外還能夠定義攔截器,全局異常處理,文件上傳下載等等。web

1、搭建項目

在之前的老項目中,由於尚未SpringBoot,沒有自動配置,因此須要使用web.xml文件去定義一個DispatcherServlet。如今互聯網應用基本上都使用SpringBoot,因此我就直接使用SpringBoot進行演示。很簡單,引入依賴便可:spring

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、定義Controller

使用SpringMVC定義Controller處理器,總共有五種方式。apache

2.1 實現Controller接口

早期的SpringMVC是經過這種方式定義:json

/**
 * @author Ye Hongzhi 公衆號:java技術愛好者
 * @name DemoController
 * @date 2020-08-25 22:28
 **/

@org.springframework.stereotype.Controller("/demo/controller")
public class DemoController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        //業務處理
        return null;
    }
}

2.2 實現HttpRequestHandler接口

跟第一種方式差很少,也是經過實現接口的方式:

/**
 * @author Ye Hongzhi 公衆號:java技術愛好者
 * @name HttpDemoController
 * @date 2020-08-25 22:45
 **/

@Controller("/http/controller")
public class HttpDemoController implements HttpRequestHandler{
    @Override
    public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
        //業務處理
    }
}

2.3 實現Servlet接口

這種方式已經不推薦使用了,不過從這裏能夠看出SpringMVC的底層使用的仍是Servlet

@Controller("/servlet/controller")
public class ServletDemoController implements Servlet {
    //如下是Servlet生命週期方法
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
    }
}

由於不推薦使用這種方式,因此默認是不加載這種適配器的,須要加上:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Bean
    public SimpleServletHandlerAdapter simpleServletHandlerAdapter() {
        return new SimpleServletHandlerAdapter();
    }
}

2.4 使用@RequestMapping

這種方式是最經常使用的,由於上面那些方式定義須要使用一個類定義一個路徑,就會致使產生不少類。使用註解就相對輕量級一些。

@Controller
@RequestMapping("/requestMapping/controller")
public class RequestMappingController {

    @RequestMapping("/demo")
    public String demo() {
        return "HelloWord";
    }
}

2.4.1 支持Restful風格

並且支持Restful風格,使用method屬性定義對資源的操做方式:

 @RequestMapping(value = "/restful", method = RequestMethod.GET)
    public String get() {
        //查詢
        return "get";
    }

    @RequestMapping(value = "/restful", method = RequestMethod.POST)
    public String post() {
        //建立
        return "post";
    }

    @RequestMapping(value = "/restful", method = RequestMethod.PUT)
    public String put() {
        //更新
        return "put";
    }

    @RequestMapping(value = "/restful", method = RequestMethod.DELETE)
    public String del() {
        //刪除
        return "post";
    }

2.4.2 支持Ant風格

 //匹配 /antA 或者 /antB 等URL
    @RequestMapping("/ant?")
    public String ant() {
        return "ant";
    }

    //匹配 /ant/a/create 或者 /ant/b/create 等URL
    @RequestMapping("/ant/*/create")
    public String antCreate() {
        return "antCreate";
    }

    //匹配 /ant/create 或者 /ant/a/b/create 等URL
    @RequestMapping("/ant/**/create")
    public String antAllCreate() {
        return "antAllCreate";
    }

2.5 使用HandlerFunction

最後一種是使用HandlerFunction函數式接口,這是Spring5.0後引入的方式,主要用於作響應式接口的開發,也就是Webflux的開發。

有興趣的能夠網上搜索相關資料學習,這個講起來可能要很大篇幅,這裏就不贅述了。

3、接收參數

定義完Controller以後,須要接收前端傳入的參數,怎麼接收呢。

3.1 接收普通參數

在@RequestMapping映射方法上寫上接收參數名便可:

@RequestMapping(value = "/restful", method = RequestMethod.POST)
public String post(Integer id, String name, int money) {
    System.out.println("id:" + id + ",name:" + name + ",money:" + money);
    return "post";
}

3.2 @RequestParam參數名綁定

若是不想使用形參名稱做爲參數名稱,可使用@RequestParam進行參數名稱綁定:

 /**
     * value: 參數名
     * required: 是否request中必須包含此參數,默認是true。
     * defaultValue: 默認參數值
     */

    @RequestMapping(value = "/restful", method = RequestMethod.GET)
    public String get(@RequestParam(value = "userId", required = false, defaultValue = "0") String id) {
        System.out.println("id:" + id);
        return "get";
    }

3.3 @PathVariable路徑參數

經過@PathVariable將URL中的佔位符{xxx}參數映射到操做方法的入參。演示代碼以下:

@RequestMapping(value = "/restful/{id}", method = RequestMethod.GET)
public String search(@PathVariable("id") String id) {
    System.out.println("id:" + id);
    return "search";
}

3.4 @RequestHeader綁定請求頭屬性

獲取請求頭的信息怎麼獲取呢?

使用@RequestHeader註解,用法和@RequestParam相似:

 @RequestMapping("/head")
    public String head(@RequestHeader("Accept-Language") String acceptLanguage) {
        return acceptLanguage;
    }

3.5 @CookieValue綁定請求的Cookie值

獲取Request中Cookie的值:

 @RequestMapping("/cookie")
    public String cookie(@CookieValue("_ga") String _ga) {
        return _ga;
    }

3.6 綁定請求參數到POJO對象

定義了一個User實體類:

public class User {
    private String id;
    private String name;
    private Integer age;
    //getter、setter方法
}

定義一個@RequestMapping操做方法:

 @RequestMapping("/body")
    public String body(User user) {
        return user.toString();
    }

只要請求參數與屬性名相同自動填充到user對象中:

3.6.1 支持級聯屬性

如今多了一個Address類存儲地址信息:

public class Address {
    private String id;
    private String name;
    //getter、setter方法
}

在User中加上address屬性:

public class User {
    private String id;
    private String name;
    private Integer age;
    private Address address;
    //getter、setter方法
}

傳參時只要傳入address.name、address.id即會自動填充:

3.6.2 @InitBinder解決接收多對象時屬性名衝突

若是有兩個POJO對象擁有相同的屬性名,不就產生衝突了嗎?好比剛剛的user和address,其中他們都有id和name這兩個屬性,若是同時接收,就會衝突:

 //user和address都有id和name這兩個屬性 
 @RequestMapping(value = "/twoBody", method = RequestMethod.POST)
    public String twoBody(User user, Address address) {
        return user.toString() + "," + address.toString();
    }

這時就可使用@InitBinder綁定參數名稱:

 @InitBinder("user")
    public void initBindUser(WebDataBinder webDataBinder) {
        webDataBinder.setFieldDefaultPrefix("u.");
    }

    @InitBinder("address")
    public void initBindAddress(WebDataBinder webDataBinder) {
        webDataBinder.setFieldDefaultPrefix("addr.");
    }

3.6.3 @Requestbody自動解析JSON字符串封裝到對象

前端傳入一個json字符串,自動轉換成pojo對象,演示代碼:

 @RequestMapping(value = "/requestBody", method = RequestMethod.POST)
    public String requestBody(@RequestBody User user) {
        return user.toString();
    }

注意的是,要使用POST請求,發送端的Content-Type設置爲application/json,數據是json字符串

甚至有一些人喜歡用一個Map接收:

可是千萬不要用Map接收,不然會形成代碼很難維護,後面的老哥估計看不懂你這個Map裏面有什麼數據,因此最好仍是定義一個POJO對象。

4、參數類型轉換

實際上,SpringMVC框架自己就內置了不少類型轉換器,好比你傳入字符串的數字,接收的入參定爲int,long類型,都會自動幫你轉換。

就在包org.springframework.core.convert.converter下,如圖所示:

有的時候若是內置的類型轉換器不足夠知足業務需求呢,怎麼擴展呢,很簡單,看我操做。什麼是Java技術愛好者(戰術後仰)。

首先有樣學樣,內置的轉換器實現Converter接口,我也實現:

public class StringToDateConverter implements Converter<StringDate{
    @Override
    public Date convert(String source) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            //String轉換成Date類型
            return sdf.parse(source);
        } catch (Exception e) {
            //類型轉換錯誤
            e.printStackTrace();
        }
        return null;
    }
}

接着把轉換器註冊到Spring容器中:

@Configuration
public class ConverterConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addFormatters(FormatterRegistry registry) {
        //添加類型轉換器
        registry.addConverter(new StringToDateConverter());
    }
}

接着看測試,全部的日期字符串,都自動被轉換成Date類型了,很是方便:

5、頁面跳轉

在先後端未分離以前,頁面跳轉的工做都是由後端控制,採用JSP進行展現數據。雖然如今互聯網項目幾乎不會再使用JSP,可是我以爲仍是須要學習一下,由於有些舊項目仍是會用JSP,或者須要重構。

若是你在RequestMapping方法中直接返回一個字符串是不會跳轉到指定的JSP頁面的,須要作一些配置。

第一步,加入解析jsp的Maven配置。

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>7.0.59</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>

第二步,添加視圖解析器。

@Configuration
public class WebAppConfig extends WebMvcConfigurerAdapter {
    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setViewClass(JstlView.class);
        return viewResolver;
    }
}

第三步,設置IDEA的配置。

第四步,建立jsp頁面。

第五步,建立Controller控制器。

@Controller
@RequestMapping("/view")
public class ViewController {
    @RequestMapping("/hello")
    public String hello() throws Exception {
        return "hello";
    }
}

這樣就完成了,啓動項目,訪問/view/hello就看到了:

就是這麼簡單,對吧

6、@ResponseBody

若是採用先後端分離,頁面跳轉不須要後端控制了,後端只須要返回json便可,怎麼返回呢?

使用@ResponseBody註解便可,這個註解會把對象自動轉成json數據返回。

@ResponseBody註解能夠放在類或者方法上,源碼以下:

//用在類、方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}

演示一下:

@RequestMapping("/userList")
@ResponseBody
public List<User> userList() throws Exception {
    List<User> list = new ArrayList<>();
    list.add(new User("1","姚大秋",18));
    list.add(new User("2","李星星",18));
    list.add(new User("3","冬敏",18));
    return list;
}

測試一下/view/userList:

7、@ModelAttribute

@ModelAttribute用法比較多,下面一一講解。

7.1 用在無返回值的方法上

在Controller類中,在執行全部的RequestMapping方法前都會先執行@ModelAttribute註解的方法。

@Controller
@RequestMapping("/modelAttribute")
public class ModelAttributeController {
 //先執行這個方法
    @ModelAttribute
    public void modelAttribute(Model model){
        //在request域中放入數據
        model.addAttribute("userName","公衆號:java技術愛好者");
    }

    @RequestMapping("/index")
    public String index(){
        //跳轉到inex.jsp頁面
        return "index";
    }
}

index.jsp頁面以下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首頁</title>
</head>
<body>
<!-- 獲取到userName屬性值 -->
<h1>${userName}</h1>
</body>
</html>

至關於一個Controller的攔截器同樣,在執行RequestMapping方法前先執行@ModelAttribute註解的方法。因此要慎用。

啓動項目,訪問/modelAttribute/index能夠看到:

即便在index()方法中沒有放入userName屬性值,jsp頁面也能獲取到,由於在執行index()方法以前的modelAttribute()方法已經放入了。

7.2 放在有返回值的方法上

其實調用順序是同樣,也是在RequestMapping方法前執行,不一樣的在於,方法的返回值直接幫你放入到Request域中。

//放在有參數的方法上
@ModelAttribute
public User userAttribute() {
    //至關於model.addAttribute("user",new User("1", "Java技術愛好者", 18));
    return new User("1""Java技術愛好者"18);
}

@RequestMapping("/user")
public String user() {
    return "user";
}

建立一個user.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首頁</title>
</head>
<body>
<h1>ID:${user.id}</h1>
<h1>名稱:${user.name}</h1>
<h1>年齡:${user.age}歲</h1>
</body>
</html>

測試一下:

放入Request域中的屬性值默認是類名的首字母小寫駝峯寫法,若是你想自定義呢?很簡單,能夠這樣寫:

//自定義屬性名爲"u"
@ModelAttribute("u")
public User userAttribute() {
    return new User("1""Java技術愛好者"18);
}
/**
JSP就要改爲這樣寫:
<h1>ID:${u.id}</h1>
<h1>名稱:${u.name}</h1>
<h1>年齡:${u.age}歲</h1>
*/

7.3 放在RequestMapping方法上

@Controller
@RequestMapping("/modelAttribute")
public class ModelAttributeController {
    
    @RequestMapping("/jojo")
    @ModelAttribute("attributeName")
    public String jojo() {
        return "JOJO!我不作人了!";
    }
}

這種狀況下RequestMapping方法的返回的值就不是JSP視圖了。而是把返回值放入Request域中的屬性值,屬性名爲attributeName。視圖則是RequestMapping註解上的URL,因此建立一個對應的JSP頁面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首頁</title>
</head>
<body>
<h1>${attributeName}</h1>
</body>
</html>

測試一下:

7.4 放在方法入參上

放在入參上,意思是從前面的Model中提取出對應的屬性值,當作入參傳入方法中使用。以下所示:

@ModelAttribute("u")
public User userAttribute() {
    return new User("1""Java技術愛好者"18);
}

@RequestMapping("/java")
public String user1(@ModelAttribute("u") User user) {
    //拿到@ModelAttribute("u")方法返回的值,打印出來
    System.out.println("user:" + user);
    return "java";
}

測試一下:

8、攔截器

攔截器算重點內容了,不少時候都要用攔截器,好比登陸校驗,權限校驗等等。SpringMVC怎麼添加攔截器呢?

很簡單,實現HandlerInterceptor接口,接口有三個方法須要重寫。

  • preHandle():在業務處理器處理請求以前被調用。預處理。
  • postHandle():在業務處理器處理請求執行完成後,生成視圖以前執行。後處理。
  • afterCompletion():在DispatcherServlet徹底處理完請求後被調用,可用於清理資源等。返回處理(已經渲染了頁面);

自定義的攔截器,實現的接口HandlerInterceptor:

public class DemoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //預處理,返回true則繼續執行。若是須要登陸校驗,校驗不經過返回false便可,經過則返回true。
        System.out.println("執行preHandle()方法");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //後處理
        System.out.println("執行postHandle()方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //在DispatcherServlet徹底處理完請求後被調用
        System.out.println("執行afterCompletion()方法");
    }
}

而後把攔截器添加到Spring容器中:

@Configuration
public class ConverterConfig extends WebMvcConfigurationSupport {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
    }
}

/**表明全部路徑,測試一下:

9、全局異常處理

SpringMVC自己就對一些異常進行了全局處理,因此有內置的異常處理器,在哪裏呢?

HandlerExceptionResolver接口的類圖就知道了:

從類圖能夠看出有四種異常處理器:

  • DefaultHandlerExceptionResolver,默認的異常處理器。根據各個不一樣類型的異常,返回不一樣的異常視圖。
  • SimpleMappingExceptionResolver,簡單映射異常處理器。經過配置異常類和view的關係來解析異常。
  • ResponseStatusExceptionResolver,狀態碼異常處理器。解析帶有 @ResponseStatus註釋類型的異常。
  • ExceptionHandlerExceptionResolver,註解形式的異常處理器。對 @ExceptionHandler註解的方法進行異常解析。

第一個默認的異常處理器是內置的異常處理器,對一些常見的異常處理,通常來講不用管它。後面的三個纔是須要注意的,是用來擴展的。

9.1 SimpleMappingExceptionResolver

翻譯過來就是簡單映射異常處理器。用途是,咱們能夠指定某種異常,當拋出這種異常以後跳轉到指定的頁面。請看演示。

第一步,添加spring-config.xml文件,放在resources目錄下,文件名見文知意便可:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- 定義默認的異常處理頁面 -->
        <property name="defaultErrorView" value="err"/>
        <!-- 定義異常處理頁面用來獲取異常信息的屬性名,默認名爲exception -->
        <property name="exceptionAttribute" value="ex"/>
        <!-- 定義須要特殊處理的異常,用類名或徹底路徑名做爲key,異常也頁名做爲值 -->
        <property name="exceptionMappings">
            <props>
                <!-- 異常,err表示err.jsp頁面 -->
                <prop key="java.lang.Exception">err</prop>
                <!-- 可配置多個prop -->
            </props>
        </property>
    </bean>
</beans>

第二步,在啓動類加載xml文件:

@SpringBootApplication
@ImportResource("classpath:spring-config.xml")
public class SpringmvcApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringmvcApplication.class, args);
    }

}

第三步,在webapp目錄下建立一個err.jsp頁面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>異常頁面</title>
</head>
<body>
<h1>出現異常,這是一張500頁面</h1>
<br>
<%-- 打印異常到頁面上 --%>
<% Exception ex = (Exception)request.getAttribute("ex"); %>
<br>
<div><%=ex.getMessage()%></div>
<% ex.printStackTrace(new java.io.PrintWriter(out)); %>
</body>
</html>

這樣就完成了,寫一個接口測試一下:

@Controller
@RequestMapping("/exception")
public class ExceptionController {
    @RequestMapping("/index")
    public String index(String msg) throws Exception {
        if ("null".equals(msg)) {
            //拋出空指針異常
            throw new NullPointerException();
        }
        return "index";
    }
}

效果以下:

這種異常處理器,在如今先後端分離的項目中幾乎已經看不到了。

9.2 ResponseStatusExceptionResolver

這種異常處理器主要用於處理帶有@ResponseStatus註釋的異常。請看演示代碼:

自定義一個異常類,而且使用@ResponseStatus註解修飾:

//HttpStatus枚舉有全部的狀態碼,這裏返回一個400的響應碼
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class DefinedException extends Exception{
}

寫一個Controller接口進行測試:

@RequestMapping("/defined")
public String defined(String msg) throws Exception {
    if ("defined".equals(msg)) {
        throw new DefinedException();
    }
    return "index";
}

啓動項目,測試一下,效果以下:

9.3 ExceptionHandlerExceptionResolver

註解形式的異常處理器,這是用得最多的。使用起來很是簡單方便。

第一步,定義自定義異常BaseException:

public class BaseException extends Exception {
    public BaseException(String message) {
        super(message);
    }
}

第二步,定義一個錯誤提示實體類ErrorInfo:

public class ErrorInfo {
    public static final Integer OK = 0;
    public static final Integer ERROR = -1;
    private Integer code;
    private String message;
    private String url;
    //getter、setter
}

第三步,定義全局異常處理類GlobalExceptionHandler:

//這裏使用了RestControllerAdvice,是@ResponseBody和@ControllerAdvice的結合
//會把實體類轉成JSON格式的提示返回,符合先後端分離的架構
@RestControllerAdvice
public class GlobalExceptionHandler {

    //這裏自定義了一個BaseException,當拋出BaseException異常就會被此方法處理
    @ExceptionHandler(BaseException.class)
    public ErrorInfo errorHandler(HttpServletRequest req, BaseException e) throws Exception {
        ErrorInfo r = new ErrorInfo();
        r.setMessage(e.getMessage());
        r.setCode(ErrorInfo.ERROR);
        r.setUrl(req.getRequestURL().toString());
        return r;
    }
}

完成以後,寫一個測試接口:

@RequestMapping("/base")
public String base(String msg) throws Exception {
    if ("base".equals(msg)) {
        throw new BaseException("測試拋出BaseException異常,歐耶!");
    }
    return "index";
}

啓動項目,測試:

絮叨

SpringMVC的功能實際上確定還不止我寫的這些,不過學會上面這些以後,基本上已經能夠應對平常的工做了。

若是要再深刻一些,最好是看看SpringMVC源碼,我以前寫過三篇,責任鏈模式與SpringMVC攔截器,適配器模式與SpringMVC,全局異常處理源碼分析

上面全部例子的代碼都上傳Github了:

https://github.com/yehongzhi/mall

以爲有用就點個贊吧,你的點贊是我創做的最大動力~

拒絕作一條鹹魚,我是一個努力讓你們記住的程序員。咱們下期再見!!!

能力有限,若是有什麼錯誤或者不當之處,請你們批評指正,一塊兒學習交流!

本文分享自微信公衆號 - java技術愛好者(yehongzhi_java)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索