Springboot——深入Spring mvc開發

前言

前一篇博客粗略介紹了spring mvc的大體流程,但是對spring mvc中還有一些細節需要總結一下,比如:獲取參數的方式,數值類型的轉換,多種試圖的應用,數據邏輯的校驗,針對控制器如何利用AOP進行增強等內容,這裏進行一個簡單總結

控制器獲取參數的方式

在之前的博客中,Handler是對控制器的包裝,在處理器運行的過程中,Handler會調度控制器的方法,只是在進入控制器方法之前,會對HTTP的參數和上下文進行解析,將他們轉換成爲控制器所需要的參數。這一步是處理器首先要做的事情

1、無註解下的參數傳遞

在沒有註解的情況下也是可以進行參數傳遞的,且參數允許爲空。但是參數名稱要與Http請求的參數名稱一致。

實例:

	@GetMapping("/no/annotation")
	@ResponseBody
	public Map<String, Object> noAnnotation(Integer intVal, Long longVal, String str) {
		Map<String, Object> paramsMap = new HashMap<>();
		paramsMap.put("intVal", intVal);
		paramsMap.put("longVal", longVal);
		paramsMap.put("str", str);
		return paramsMap;
	}

在瀏覽器中輸入url:localhost:8080/my/no/annotation?intVal=1&longVal=10000&str=test

其運行結果如下:

2、使用@RequestParam獲取參數

由於互聯網公司前後端都進行了分離,前後端的命名規則可能不同,因此不能全部要求前端的命名規則和後端參數對應。所以就有了@RequestParam註解,默認情況下@RequestParam標註的參數是不能爲空的,如果讓其可爲空,可將required屬性設置爲false

實例代碼:

	@GetMapping("/annotation")
	@ResponseBody
	public Map<String, Object> requestParam(@RequestParam("int_val") Integer intVal,
			@RequestParam(value = "long_val",required = false) Long longVal, @RequestParam("str_val") String strVal) {
		Map<String, Object> paramsMap = new HashMap<>();
		paramsMap.put("intVal", intVal);
		paramsMap.put("longVal", longVal);
		paramsMap.put("strVal", strVal);
		return paramsMap;
	}

實例url:localhost:8080/my/annotation?int_val=2&long_val=1000&str_val=test

結果和麪的一樣,這裏不再貼出。 

3、傳遞數組和JSON數據

3.1傳遞數組

在url中可以直接傳遞數組,相關數據元素用逗號隔開即可,這裏就直接上實例吧

	@GetMapping("/requestArray")
	@ResponseBody
	public Map<String, Object> requestArray(int[] intArr, Long[] longArr, String[] strArr) {
		Map<String, Object> paramsMap = new HashMap<>();
		paramsMap.put("intArr", intArr);
		paramsMap.put("longArr", longArr);
		paramsMap.put("strArr", strArr);
		return paramsMap;
	}

測試url:localhost:8080/my/requestArray?intArr=1,2,3&longArr=100,200,300&strArr=test1,test2,test3

3.2傳遞json數據

只需要在指定參數上標記上@RequestBody,Spring mvc會自動將json數據映射成指定的對象。前提是JSON數據需要與指定的對象屬性保持一致。直接上實例吧

對應的jsp頁面:

<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>新增用戶用戶</title>
<!-- 加載Query文件-->
<script src="https://code.jquery.com/jquery-3.2.0.js">
</script>
<script type="text/javascript">
$(document).ready(function() {
    $("#submit").click(function() {
        var username = $("#username").val();
        var note = $("#note").val();
        if ($.trim(username)=='') {
            alert("用戶名不能爲空!");
            return;
        }

        //拼接json數據
        var params = {
            username : username,
            note : note
        };
        $.post({
            url : "./insert",
            // 此處需要告知傳遞參數類型爲JSON,不能缺少
            contentType : "application/json",
            // 將JSON轉化爲字符串傳遞
            data : JSON.stringify(params),
            // 成功後的方法
            success : function(result) {
                if (result == null || result.id == null) {
                    alert("插入失敗");
                    return;
                }
                alert("插入成功");
            }
        });
    });
});
</script>
</head>
<body>
    <div style="margin: 20px 0;"></div>
    <form id="insertForm">
        <table>
            <tr>
                <td>用戶名稱:</td>
                <td><input id="username" name="username"></td>
            </tr>
            <tr>
                <td>備註</td>
                <td><input id="note" name="note"></td>
            </tr>
            <tr>
                <td></td>
                <td align="right"><input id="submit" type="button" value="提交" /></td>
            </tr>
        </table>
    </form>
</body>

該頁面的JS中有一段就是拼接JSON字符串。

後端對應的controller代碼

	@GetMapping("/toAdd")
	public String add(){
		return "/user/add";
	}

	@PostMapping("/insert")
	@ResponseBody
	public User insert(@RequestBody User user){
		user.setId(1l);
		System.out.println(user.toString());
		return user;
	}

其中的@RequestBody註解就將JSON中對應的數據映射成了指定的對象,這個就能較好的完成JSON的參數傳遞,測試結果如下:

 3.3通過url傳遞參數

有些url請求具有restful風格,這些參數直接通過url進行傳遞,沒有進行特別的指定。這就需要用到@PathVariable註解,這個註解結合Handler可以定位參數的名稱和位置。直接上實例吧

	@GetMapping("/{userId}")
	@ResponseBody
	public User get(@PathVariable("userId") Long id){
		User user = new User();
		user.setId(id);
		user.setUsername("liman");
		user.setSex("男");
		user.setNote("just test");
		System.out.println(user.toString());
		return user;
	}

請求的url:localhost:8080/my/1

運行結果:

3.4獲取格式化參數

有時候需要針對一些參數進行格式化,比如一些日期和貨幣,這就需要@DateTimeFormat和@NumberFormat前者是針對日期進行格式化,後者是針對數字進行格式化。

還是直接上實例吧

	// 映射JSP頁面
	@GetMapping("/format/form")
	public String showFormat() {
		return "/format/formatter";
	}

	// 獲取提交參數
	@PostMapping("/format/commit")
	@ResponseBody
	public Map<String, Object> format(Date date,
			@NumberFormat(pattern = "#,###.##") Double number) {
		Map<String, Object> dataMap = new HashMap<>();
		dataMap.put("date", date);
		dataMap.put("number", number);
		return dataMap;
	}

如果在配置文件中配置了 spring.mvc.date-format=yyyy-MM-dd 屬性,可以不用@DateTimeFormat註解。上述實例既是如此。

參數的自定義轉換

處理器獲得參數的邏輯

這裏的參數自定義轉換,更多的是針對HTTP的請求體的轉換。當一個請求來到的時候,在處理器執行的過程中,會首先從HTTP請求和上下文環境來獲取參數。如果是簡單參數,spring mvc會採用簡單的參數轉換器,如果需要轉換的參數是HTTP的請求體,則會調用HTTPMessageConverter接口的方法對請求體的信息進行轉換。

 canRead和read方法,canRead先確定請求體是否可讀,如果判定爲可讀之後,之後就是read方法,將前端的JSON數據轉換爲指定的類型參數。

Spring mvc的自定義參數轉換過程中,主要是通過WebDataBinder機制來獲取參數,WebDataBinder主要作用是解析HTTP請求的上下文,之後在控制器的調用之前轉換參數並且提供參數的邏輯驗證功能。Handler會從HTTP請求中讀取數據,然後通過三種接口來進行各類參數轉換。這三種接口分別是:Convert,Formatter和GenericConvert,這三種接口的實現類,在spring mvc中都採用了註冊機的實現機制。spring mvc已經爲我們註冊了很多轉換器,大多數情況下都無需自己開發,但是如果需要自定義規則的時候就需要在註冊機上註冊自己的轉換器了。

 上圖簡單點說,就是在處理器正式處理參數之前,會經過WebDataBinder提供的三個參數轉換接口,這三個參數轉換接口會將請求中的參數進行轉換,轉換成POJO,然後WebDataBinder也對這些轉換的參數進行驗證,之後再調用控制器的邏輯進行數據處理。

其中Convert就是一個普通的轉換器,例如將請求報文中的字符串轉換爲Integer類型。Formatter則是一個格式化處轉換器,日期字符串的轉換就是這個完成的。GenericConvert是集合轉換器,上述實例中的數組的傳遞,就是通過這個轉換器完成。

這三種簡單的類型轉換器都是通過註冊機制註冊給了spring mvc,因此不需要我們自動配置

1、一對一轉換器(Converter)

只是支持參數從一種類型轉換成另外一種類型,接口定義十分簡單,這裏以字符串轉換爲POJO類型爲例來進行說明

自定義轉換器,實現Converter接口,並重寫convert方法

@Component
public class StringToUserConverter implements Converter<String,User> {
    @Override
    public User convert(String userStr) {
        User user = new User();
        String[] str = userStr.split("_");
        Long id = Long.parseLong(str[0]);
        String username = str[1];
        String note = str[2];
        user.setId(id);
        user.setNote(note);
        user.setUsername(username);
//        System.out.println(user.toString());
        return user;
    }
}

這裏只需要打上@Component註解,spring boot會自動將其註冊爲轉換器,在初始化的時候,會把這個類自動的註冊到轉換機制中。畢竟實現了Convert接口,spring可以識別。

驗證方法

	@GetMapping("/converter")
	@ResponseBody
	public User getUserByConverter(@RequestParam User user){
		return user;
	}

測試url:http://localhost:8080/my/converter?user=1_liman_test

數據驗證

spring mvc在提供了一些簡單的驗證參數機制的同時,也支持我們進行自定義的參數驗證機制。JSR-303是spring boot引入的Hibernate Validator機制來支持JSR-303驗證機制。

JSR-303驗證

這個就直接上實例吧

public class ValidatorPojo {

    // 非空判斷
    @NotNull(message = "id不能爲空")
    private Long id;

    @Future(message = "需要一個將來日期") // 只能是將來的日期
    // @Past //只能去過去的日期
    @DateTimeFormat(pattern = "yyyy-MM-dd") // 日期格式化轉換
    @NotNull // 不能爲空
    private Date date;

    @NotNull // 不能爲空
    @DecimalMin(value = "0.1") // 最小值0.1元
    @DecimalMax(value = "10000.00") // 最大值10000元
    private Double doubleValue = null;

    @Min(value = 1, message = "最小值爲1") // 最小值爲1
    @Max(value = 88, message = "最大值爲88") // 最大值88
    @NotNull // 不能爲空
    private Integer integer;

    @Range(min = 1, max = 888, message = "範圍爲1至888") // 限定範圍
    private Long range;

    // 郵箱驗證
    @Email(message = "郵箱格式錯誤")
    private String email;

    @Size(min = 20, max = 30, message = "字符串長度要求20到30之間。")
    private String size;

    //setter和getter方法均省去
}

上述實例中,幾乎針對幾種常用的數據類型的驗證註解都有了介紹,下面進行測試,這裏直接貼出發送驗證請求的JSP頁面代碼

<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>測試JSR-303</title>
<!-- 加載Query文件-->
<script src="https://code.jquery.com/jquery-3.2.0.js"></script>
<script type="text/javascript">
	$(document).ready(function() {
		// 請求驗證的POJO
		var pojo = {
			id : 1,
			date : '2019-08-08',
			doubleValue : 999.09,
			integer : 80,
			range : 80,
			email : '[email protected]',
			size : '25liamnataoimdoasgasld',
			regexp : 'a,b,c,d'
		}
		$.post({
			url : "./validate",
			// 此處需要告知傳遞參數類型爲JSON,不能缺少
			contentType : "application/json",
			// 將JSON轉化爲字符串傳遞
			data : JSON.stringify(pojo),
			// 成功後的方法
			success : function(result) {
			    alert(result);
			}
		});
	});
</script>
</head>
<body></body>
</html>

後端代碼

	/***
	 * 解析驗證參數錯誤
	 * @param vp —— 需要驗證的POJO,使用註解@Valid 表示驗證
	 * @param errors  錯誤信息,它由Spring MVC通過驗證POJO後自動填充,並返回給前端
	 * @return 錯誤信息Map
	 */
	@RequestMapping(value = "/valid/validate")
	@ResponseBody
	public Map<String, Object> validate(
            @Valid @RequestBody ValidatorPojo vp, Errors errors) {
	    Map<String, Object> errMap = new HashMap<>();
	    // 獲取錯誤列表
	    List<ObjectError> oes = errors.getAllErrors();
	    for (ObjectError oe : oes) {
	        String key = null;
	        String msg = null;
	        // 字段錯誤
	        if (oe instanceof FieldError) {
	            FieldError fe = (FieldError) oe;
	            key = fe.getField();// 獲取錯誤驗證字段名
	        } else {
	            // 非字段錯誤
	            key = oe.getObjectName();// 獲取驗證對象名稱
	        }
	        // 錯誤信息
	        msg = oe.getDefaultMessage();
	        errMap.put(key, msg);
	    }
	    return errMap;
	}

Errors參數會在spring mvc自動驗證POJO之後,會進行填充,我們直接獲取就好。

自定義參數驗證機制

在之前的示例圖中已經看到WebDataBinder也提供參數驗證機制,我們可以自己實現相關的驗證邏輯,自己實現的驗證邏輯代碼只需要實現Validator接口即可。

package com.learn.chapter10.validator;


import com.learn.chapter10.domain.User;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment: 自定義的數據邏輯驗證器
 */
public class UserValidator implements Validator {

    //判斷驗證的目標對象
    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.equals(User.class);
    }

    //驗證邏輯
    @Override
    public void validate(Object target, Errors errors) {
        if(target == null){
            errors.rejectValue("",null,"用戶不能爲空");
        }
        User user= (User)target;
        if(StringUtils.isEmpty(user.getUsername())||!"liman".equals(user.getUsername())){
            errors.rejectValue("username",null,"用戶名只能爲liman,沒有辦法他就是這麼牛逼");
        }
    }
}

但是這些驗證器,spring並不會自動啓動它,因爲沒有綁定爲WebDataBinder機制,在spring mvc中還有一個註解就是@InitBinder,這個註解就是在控制器調用相關方法前,處理器會執行@InitBinder標註的方法。這個註解標註的方法,參數就是WebDataBinder,可以在控制器方法執行前,將驗證器註冊給WebDataBinder,如下所示:

package com.learn.chapter10.controller;

import com.learn.chapter10.domain.User;
import com.learn.chapter10.validator.UserValidator;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.Valid;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
@Controller
@RequestMapping("/user")
public class UserController {

    //綁定邏輯驗證器
    @InitBinder
    public void initBinder(WebDataBinder webDataBinder) {
        webDataBinder.setValidator(new UserValidator());
        //定義日期格式,不再需要@DateTimeFormat註解
        webDataBinder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
    }

    @GetMapping("/userValidator")
    @ResponseBody
    public Map<String,Object> validator(@Valid User user, Errors errors,Date date){
        Map<String,Object> map =new HashMap<>();
        map.put("user",user);
        map.put("date",date);

        if(errors.hasErrors()){
            List<ObjectError> oes = errors.getAllErrors();
            for(ObjectError oe:oes){
                if(oe instanceof FieldError){
                    FieldError fe = (FieldError)oe;
                    map.put(fe.getField(),fe.getDefaultMessage());
                }else{
                    map.put(oe.getObjectName(),oe.getDefaultMessage());
                }
            }
        }
        return map;
    }
}

數據模型

spring mvc在完成相關參數的獲取和轉換之後,才真正進入到處理器的邏輯處理,在邏輯處理之後,控制器可以自己定義自己的視圖和數據模型(ModelAndView),模型是存放數據的地方,視圖是展示給用戶看的頁面。

在ModelAndView類中存在一個ModelMap類型的屬性,ModelMap繼承了LinkedHashMap,在spring mvc應用中如果在控制器方法中使用ModelAndView、Model或者ModelMap作爲參數,spring mvc會自動創建數據模型對象,並返回給前端。

package com.learn.chapter10.controller;

import com.learn.chapter10.domain.User;
import com.learn.chapter10.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
@Controller
@RequestMapping("/data")
public class DataModelController {

    @Autowired
    private UserService userService;

    @GetMapping("/model")
    public String userModel(Long id,Model model){
        User user = userService.getUser(id);
        model.addAttribute("user",user);
        //這裏返回字符串,在spring mvc中會自動創建ModelAndView且綁定名稱
        return "data/user";
    }

    @GetMapping("/modelMap")
    public ModelAndView userModelMap(Long id , ModelMap modelMap){
        User user = userService.getUser(id);
        ModelAndView mv = new ModelAndView();
        mv.setViewName("data/user");
        modelMap.put("user",user);
        return mv;
    }

    @GetMapping("/mav")
    public ModelAndView userModelAndView(Long id,ModelAndView mv){
        User user = userService.getUser(id);
        mv.addObject("user",user);
        mv.setViewName("data/user");
        return mv;
    }
}

簡而言之一句話,如果控制器方法中單獨出現了ModelMap和Model,兩個參數,spring mvc會自動將其與ModelAndView綁定。

視圖和視圖解析器 

一般視圖分爲邏輯視圖和非邏輯視圖,邏輯視圖是需要視圖解析器進一步定位視圖的,而邏輯視圖不需要。除了JSON視圖和JSP視圖之外,其實還有很多其他類型的視圖,如Excel和PDF等。

PDF視圖實例

其實PDF視圖是不需要任何視圖解析器去定位的。屬於非邏輯視圖。但是爲了能夠使用PDF,需要先引入相關maven配置

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.lowagie</groupId>
            <artifactId>itext</artifactId>
            <version>2.1.7</version>
        </dependency>

但是爲了將數據模型渲染成PDF,需要繼承一個抽象類——AbstractPdfView,這個抽象類中的buildPdfDocument方法包含了幾乎所有構建pdf需要的參數。

在具體實現的時候,我們也像平常寫業務類那樣暴露一個接口,然後實現相關生成PDF的邏輯

1、繼承AbstractPdfView

package com.learn.chapter10.controller;

import com.learn.chapter10.service.PdfExportService;
import com.lowagie.text.Document;
import com.lowagie.text.pdf.PdfWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.document.AbstractPdfView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
@Component
public class PdfView extends AbstractPdfView {

    @Autowired
    private PdfExportService pdfExportService;

    @Override
    protected void buildPdfDocument(Map<String, Object> map, Document document, PdfWriter pdfWriter, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        pdfExportService.exportPDF(map, document,pdfWriter,httpServletRequest,httpServletResponse);
    }
}

2、實現具體的buildPdfDocument

package com.learn.chapter10.service;

import com.learn.chapter10.domain.User;
import com.lowagie.text.*;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.util.List;
import java.util.Map;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
@Service("pdfExportService")
public class PdfExportServiceImpl implements PdfExportService{

    @Autowired
    private UserService userService;

    @Override
    public void exportPDF(Map<String, Object> map, Document document, PdfWriter pdfWriter, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        try{
            document.setPageSize(PageSize.A4);
            document.addTitle("用戶信息");
            document.add(new Chunk("\n"));
            PdfPTable table = new PdfPTable(3);
            // 字體,定義爲藍色加粗
            Font f8 = new Font();

            f8.setColor(Color.BLUE);
            f8.setStyle(Font.BOLD);
            // 單元格
            PdfPCell cell = null;
            // 標題
            cell = new PdfPCell(new Paragraph("id", f8));
            // 居中對齊
            cell.setHorizontalAlignment(1);
            // 將單元格加入表格
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph("user_name", f8));
            // 居中對齊
            cell.setHorizontalAlignment(1);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph("note", f8));
            cell.setHorizontalAlignment(1);
            table.addCell(cell);

            userService.initUserList();
            List<User> userList = userService.getUserList();

            for (User user : userList) {
                document.add(new Chunk("\n"));
                cell = new PdfPCell(new Paragraph(user.getId() + ""));
                table.addCell(cell);
                cell = new PdfPCell(new Paragraph(user.getUsername()));
                table.addCell(cell);
                String note = user.getNote() == null ? "" : user.getNote();
                cell = new PdfPCell(new Paragraph(note));
                table.addCell(cell);
            }
            // 在文檔中加入表格
            document.add(table);

        }catch (Exception e){
            e.printStackTrace();
        }
//        return document;
    }
}

3、controller中測試

    @Autowired
    private PdfView pdfView;

    @GetMapping("/export/pdf")
    public ModelAndView exportPdf(){
        ModelAndView mv = new ModelAndView();
        mv.setView(pdfView);
        return mv;
    }

最終呈現結果:

文件上傳

DispatcherServlet會使用適配器模式,將HttpServletRequest接口轉換成MultipartHttpServletRequest接口,後者操作文件要方便的多,在spring boot機制中,如果你沒有定義MultipartResolver對象,其會自動爲我們創建一個StandardServletMultipartResolver,對於文件上傳可以使用Servlet API提供的Part接口或者Spring mvc提供的MultipartFile接口作爲參數。其實無論使用哪個類都是允許的。更加推薦用part接口。

實例:

相關配置

#文件上傳的配置
spring.servlet.multipart.location=E:/fileUpload
#限制單個文件上傳最大大小,5M
spring.servlet.multipart.max-file-size=5242880
#限制所有文件最大大小,20M
spring.servlet.multipart.max-request-size=20MB

文件上傳jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上傳</title>
</head>
    <body>
        <form method="post" 
                action="./part" enctype="multipart/form-data">
            <input type="file" name="file" value="請選擇上傳的文件" />
            <input type="submit" value="提交" />
        </form>
    </body>
</html>

後臺對應Controller

package com.learn.chapter10.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import java.io.File;
import java.util.HashMap;
import java.util.Map;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 * 文件上傳Controller
 *
 */
@Controller
@RequestMapping("/file")
public class FileController {

    @GetMapping("/upload/page")
    public String uploadPage(){
        return "/file/upload";
    }

    //使用request對象作爲文件傳遞參數
    @PostMapping("/upload/request")
    @ResponseBody
    public Map<String,Object> uploadRequest(HttpServletRequest request){
        boolean flag = false;
        MultipartHttpServletRequest mreq = null;
        if(request instanceof MultipartHttpServletRequest){
            mreq = (MultipartHttpServletRequest)request;
        }else{
            return dealResultMap(false,"上傳失敗!");
        }

        //從file參數中獲取文件數據
        MultipartFile multipartFile = mreq.getFile("file");
        String fileName = multipartFile.getOriginalFilename();
        File file = new File(fileName);

        try{
            multipartFile.transferTo(file);
        }catch (Exception e){
            e.printStackTrace();
            return dealResultMap(false,"上傳失敗!");
        }
        return dealResultMap(true,"上傳成功!");

    }

    @PostMapping("/upload/multipart")
    @ResponseBody
    public Map<String,Object> uploadMultipartFile(MultipartFile file){
        String fileName = file.getOriginalFilename();
        File dest = new File(fileName);
        try{
            file.transferTo(dest);
        }catch (Exception e){
            e.printStackTrace();
            return dealResultMap(false,"上傳失敗!");
        }
        return dealResultMap(true,"上傳成功!");
    }

    @PostMapping("/upload/part")
    @ResponseBody
    public Map<String,Object> uploadPart(Part file){
        String fileName = file.getSubmittedFileName();
        try{
            file.write(fileName);
        }catch (Exception e){
            e.printStackTrace();
            return dealResultMap(false,"上傳失敗!");
        }
        return dealResultMap(true,"上傳成功!");
    }

    private Map<String,Object> dealResultMap(boolean success,String msg){
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("success",success);
        result.put("msg",msg);
        return result;
    }

}

攔截器

攔截器會在處理器的方法執行之前和之後進行相關邏輯操作,對處理器的功能進行增強。實現一個攔截器,只需要實現HandlerInterceptor接口,並在容器中註冊即可。

單個攔截器

實例:

package com.learn.chapter10.interceptor;

import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
public class InterceptorOne implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("攔截器one,處理器前調用的方法");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("攔截器one,處理器one後的方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("攔截器one,視圖解析完成的方法");
    }
}

攔截器的註冊

package com.learn.chapter10;

import com.learn.chapter10.interceptor.InterceptorOne;
import com.learn.chapter10.interceptor.InterceptorThree;
import com.learn.chapter10.interceptor.InterceptorTwo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication(scanBasePackages = "com.learn.chapter10")
public class Chapter10Application implements WebMvcConfigurer {

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

    //註冊攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration interceptorRegistration01 = registry.addInterceptor(new InterceptorOne());
        interceptorRegistration01.addPathPatterns("/interceptor/*");

    }
}

執行完之後,在controller方法執行,會打出如下日誌

多個攔截器的執行順序

 

但是如果多個攔截器中有一個返回false,後續的都無法執行。

一些補充 

重定向

重定向有兩種方式,其實這兩種方式實質都是一樣的,redirect。

1、通過指定字符串跳轉

    //使用字符串指定跳轉
    @GetMapping("/redirect1")
    public String redirect1(String username,String note){
        User user = userService.getUser(1L);
        return "redirect:/user/show?id=1";
    }

2、通過設置模型和視圖指定跳轉

    @GetMapping("/redirect2")
    public ModelAndView redirect2(String username,String note){
        User user = userService.getUser(1L);
        ModelAndView mv = new ModelAndView();
        mv.setViewName("redirect:/user/show?id=1");
        return mv;
    }

操作會話對象

這部分只需要熟悉兩個註解@SessionAttributes和@SessionAttribute,前者屬於類註解,這個註解指定保存到session中的參數,可以按照屬性名稱或者數據類型進行保存。@SessionAttribute是從HttpSession中取數據

package com.learn.chapter10.controller;

import com.learn.chapter10.domain.User;
import com.learn.chapter10.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
@Controller
//@SessionAttributes註解 指定數據模型或數據類型,這些數據會報錯到session中
@SessionAttributes(names={"user"},types=Long.class)
@RequestMapping("/session")
public class SessionController {

    @Autowired
    private UserService userService;

    @GetMapping("/session")
    public String toSession(){
        return "session/session";
    }

    //@SessionAttribute註解從HTTPSession中獲取數據,填充控制器方法參數
    @GetMapping("/test")
    public String test(@SessionAttribute("id") Long id,Model model){
        model.addAttribute("id_new",id);
        User user = userService.getUser(1L);
        model.addAttribute("user",user);
        return "session/test";
    }
}

測試的jsp:

session.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"  
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>session測試</title>
</head>
<body>

    <%
        session.setAttribute("id",5L);
        response.sendRedirect("./test");
    %>

</body>
</html>

test.jsp

<%@ page import="com.learn.chapter10.domain.User" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>session測試</title>
</head>
<body>

    <%
        User user = (User) session.getAttribute("user");
        Long id = (Long) session.getAttribute("id_new");
        out.print("<br>user_name="+user.getUsername()+"</br>");
        out.print("<br>id_name="+id+"</br>");
    %>

</body>
</html>

給控制器增加通知

這部分主要就是弄清以下幾個註解

controller測試代碼

package com.learn.chapter10.advice;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
@ControllerAdvice(basePackages = {"com.learn.chapter10.advice.target.*"}, annotations = Controller.class)
public class MyControllerAdvice {

    //綁定格式化,增加參數驗證,和參數轉換規則,在控制器方法執行前執行
    @InitBinder
    public void initDataBinder(WebDataBinder webDataBinder){
        CustomDateEditor  dateEditor = new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),false);
        webDataBinder.registerCustomEditor(Date.class,dateEditor);
    }

    //在執行控制器之前執行,可以初始化出具模型
    @ModelAttribute
    public void projectModel(Model model){
        model.addAttribute("project_name","chapter10");
    }

    //出現異常的時候,會執行
    @ExceptionHandler(value=Exception.class)
    public String exception(Model model,Exception ex){
        //給數據模型增加異常消息
        model.addAttribute("exception_message",ex.getMessage());
        System.out.println(ex.getMessage());
        //返回異常視圖
        return "exception";
    }
}

這個參數中對異常處理的測試沒有達到最終的效果

目標controller

@Controller
@RequestMapping("/advice")
public class AdviceController {

    @GetMapping("/test")
    public String test(Date date, ModelMap modelMap) throws Exception {
        System.out.println("從已經初始化的數據模型中獲取數據"+modelMap.get("project_name"));
        System.out.println(DateUtils.format(date,"yyyy-MM-dd"));
        throw new RuntimeException("出現異常,跳轉到控制器異常處理增強邏輯中");
    }
}

總結

針對spring mvc的相應情況作了簡單總結,都是些零碎的東西,這裏只是統一歸檔了一下,主要參考依舊來自《深入淺出spring boot 2.X》一書

相關源代碼地址:https://github.com/liman657/springbootlearn

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章