SpringBoot整合SpringMVC分析

在SpringBoot開發中,我們可以快速的配置實現Spring MVC開發,但是我們了解整個運行流程嗎?Spring Boot為我們配置了什么?

Spring MVC執行流程分析

先看看Spring MVC啟動時默認創建了哪些對象

DispatcherServlet.properties
路徑:spring-webmvc-5.2.1.RELEASE.jar包下org.springframework.web.servlet.DispatcherServlet.properties
這個文件中定義的對象會在Spring MVC開始時就初始化,并且會存放到SpringIoC容器中。

# 看下DispatcherServlet.properties都定義了哪些對象

# 國際化解析器
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

# 主題解析器
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

# HandlerMapping實例
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
    org.springframework.web.servlet.function.support.RouterFunctionMapping

# 處理器適配器
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    org.springframework.web.servlet.function.support.HandlerFunctionAdapter

# 處理器異常解析器
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

# 策略視圖名稱轉換器,當你沒有返回視圖邏輯名稱時,通過它可以生成默認的視圖名稱
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

# 視圖解析器
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

# FlashMap管理器,不常用
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

通過代碼分析運行流程

//表明這是一個控制器
@Controller
//@RequestMapping表示請求路徑和控制器的映射關系
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

    @RequestMapping("/details")
    public ModelAndView details() {
        //1.獲取用戶數據
        User user = userService.findUserById(1L);
        //2.模型和視圖
        ModelAndView mv = new ModelAndView();
        mv.setViewName("/user/details");
        mv.addObject("user", user);
        return mv;
    }

}

1.啟動Spring MVC的時候,UserController控制器就會被掃描到HanderMapping中存儲
2.訪問/user/details,會被DispatcherServlet攔截,然后通過HanderMapping找到對應的控制器并進行響應
3.HanderMapping返回的并不是一個UserController對象,而是一個HandlerExecutionChain對象,可以看下源碼

public class HandlerExecutionChain {
    //日志
    private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
    //處理器
    private final Object handler;
    //攔截器數組
    @Nullable
    private HandlerInterceptor[] interceptors;
    //攔截器列表
    @Nullable
    private List<HandlerInterceptor> interceptorList;
    //攔截器當前下標
    private int interceptorIndex;
    其他的省略.....
}

4.在HandlerExecutionChain對象中包含一個handler處理器,這個處理器是對控制器的包裝,處理器在請求的時候讀取http和上下文的相關參數,然后傳遞給控制器方法,控制器執行完成后,處理器又可以通過配置信息對控制器的返回結果進行處理。
5.在HandlerExecutionChain對象中還定義了攔截器(interceptor),這些攔截器可以通過攔截處理器進一步增強處理器的功能。
6.客戶端有http請求、websocket請求等,所以還需要一個適配器去運行HandlerExecutionChain對象包含的處理器,這個就是處理適配器(HandlerAdapter的實現類),我們最常用的實現類就是HttpRequestHandlerAdapter。
7.處理器調用控制器,并返回模型和視圖(ModeAndView)對象,這個時候就會走到ViewResolver視圖解析器,去解析視圖邏輯名稱。在DispatcherServlet.properties中我們可以看到,InternalResourceViewResolver視圖解析器已經自動初始化了,不過我們可以在配置文件application.properties中進行配置,這樣SpringMVC就會初始化我們自定義的InternalResourceViewResolver視圖解析器了。

# /templates/jsp/user/details.jsp
# 前綴
spring.mvc.view.prefix=/templates/jsp/
# 后綴
spring.mvc.view.suffix=.jsp

8.到這里,視圖解析器就定位到視圖了,視圖的作用就是將數據模型渲染給用戶看。


流程圖

SpringBoot啟動SpringMVC

上面我們講了Spring MVC的執行流程,那SpringMVC是怎么啟動的呢?

在Servlet3.0規范中,web.xml不再是一個必須的配置文件,為了適應這個規范,Spring MVC從3.1版本開始>也開始對這個規范進行了支持,也就是我們不再需要在任何xml中配置Spring MVC運行環境。

為了支持這種規范,Spring提供了WebMvcConfigurer接口,這是一個基于Java8的接口,大部分方法都是>default類型的,但是它們都是空實現,開發者只需要實現這個接口,重寫需要自定義的方法即可。

在Springboot中,自定義類是通過配置WebMvcAutoConfiguration定義的,它有一個靜態的內部類
WebMvcAutoConfigurationAdapter,通過它Spring Boot就自動配置了Spring MVC穿的初始化。

在WebMvcAutoConfigurationAdapter類中,它會讀取Spring配置Spring MVC的屬性來初始化對應組件。

SpringMVC使用

路徑映射
@Controller
@RequestMapping("/user")
public class UserController {
    /**
     * @RequestParam默認參數不能為空,將其required屬性設置為false即可為空
     */
    @RequestMapping(path = "/details", method = RequestMethod.GET)
    public void details() {

    }
}

控制器首先要指定請求url,這里由@RequestMapping來完成,該注解可以標注到類或方法上,配置請求url后,Spring MVC掃描機制就可以將其掃描,并裝載到HandlerMapping。

Spring4.3之后,為了簡化method配置項,新增了@GetMapping、@PostMapping、@PatchMapping、@PutMapping、@DeleteMapping。

請求參數

無注解獲取參數

/**
 * 要求請求參數名稱和http請求參數名稱必須一致
 * 參數值允許為空
 */
@RequestMapping("/details")
public void details(Integer intVal, Long longVal, String str) {

}

使用@RequestParam獲取請求參數

@RequestParam將Http請求的參數和方法的參數進行綁定

/**
 * @RequestParam默認參數不能為空,將其required屬性設置為false即可為空
 */
@RequestMapping("/details")
public void details(@RequestParam(value = "name", required = false) String name) {

}

數組參數

Spring MVC內部支持逗號分隔的數組參數

//http://localhost:8080/user/details?intArr=1,2,3&longArr=4,5,6&strArr=aaa,bbb,ccc
@RequestMapping("/details")
public void details(int[] intArr, Long[] longArr, String[] strArr) {
    
}

JSON格式參數

接收到JSON數據后,@RequestBody會將JSON數據轉換為User對象,前提是JSON數據和User對象的屬性名稱是一致的。

@RequestMapping("/details")
public void details(@RequestBody User user) {

}

通過URL傳遞參數

可以通過@PathVariable通過名稱來獲取URL參數

//http://localhost:8080/user/1
@GetMapping("/{id}")
public void details(@PathVariable("id") Long id) {

}

獲取請求頭參數

通過@RequestHeader接收請求參數

@GetMapping("/details")
public User details(@RequestHeader("id") Long id) {
    return new User();
}

獲取格式化參數

@DateTimeFormat:對日期進行格式化
@NumberFormat:對數字進行格式化

@GetMapping("/details")
public void details(
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date date,
        @NumberFormat(pattern = "#,###.##") Double number) {

}
返回結果

@ResponseBody注解可以把返回結果轉為json格式的數據
原理:當被@ResponseBody標注后,在控制器返回后,處理器會啟動結果解析器(ResultResolver)去解析這個結果,它會去輪詢注冊給SpringMVC的HttpMessageConverter接口的實現類,因為MappingJackson2HttpMessageConverter這個實現類已經被Spring MVC所注冊,通過它在處理器內部就把結果轉換成了JSON。

@GetMapping("/details")
@ResponseBody
public User details() {
    return new User();
}
數據校驗

JSR-303規范驗證參數的合法性

public class User {

    @NotNull(message = "id不能為空")
    private Long id;

    @Future(message = "需要是一個未來的日子")
    private Date date1;

    @Past(message = "需要是一個過去的日子")
    private Date date2;

    @DecimalMin(value = "0.1") //最小值0.1
    @DecimalMax(value = "10000.00") //最大值10000
    private Double money;

    @Min(value = 1, message = "最小值為1")
    @Max(value = 100, message = "最大值為100")
    private Integer num;

    @Range(min = 1, max = 100, message = "范圍為1到100")
    private Long range;

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

    @Size(min = 10, max = 20, message = "備注內容長度要求大于10小于20")
    private String desc;
}

@Valid表示啟動驗證機制,開啟后,會自動將錯誤結果放入到Errors對象中

@GetMapping("/details")
public void details(@Valid @RequestBody User user, Errors errors) {
    List<ObjectError> allErrors = errors.getAllErrors();
}

攔截器

上面我們講到,當請求到達DispatcherServlet時,會根據HandlerMapping返回一個HandlerExecutionChain對象,這個對象包含處理器和攔截器,這里的攔截器會對處理器進行攔截。

自定義攔截器

需要實現HandlerInterceptor接口
執行流程:
1.執行preHandle方法,返回false,結束所有流程,返回true,執行下一步。
2.執行處理器邏輯,包含控制器的功能
3.執行postHandle方法
4.執行視圖解析和渲染
5.執行afterCompletion方法

public class MyHandlerInterceptor implements HandlerInterceptor {

    /**
     * 處理器執行前執行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    /**
     * 處理器執行后執行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 處理器完成后執行
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

}

注冊攔截器

什么我們定義了攔截器,但是Spring MVC并不會知道它的存在,它需要進行注冊才能夠進行攔截,這里我們實現WebMvcConfigurer接口,覆蓋其addInterceptors方法進行注冊攔截器。

@SpringBootApplication
public class SpringmvcDemoApplication implements WebMvcConfigurer {

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

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //1.添加自定義攔截器
        InterceptorRegistration ir = registry.addInterceptor(new MyHandlerInterceptor());
        //攔截模式, 會攔截與正則式"/interceptor/*"匹配的請求
        ir.addPathPatterns("/interceptor/*");
    }
}

異常處理

在SpringAOP中,可以通過通知來增強Bean的功能,同樣,SpringMVC也可以給控制器增加通知,并給我們提供了@ControllerAdvice 、@InitBinder 、@ExceptionHandler 和@ModelAttribute 4個注解。
@ControllerAdvice :定義一個控制器通知類
@InitBinder:定義控制器參數綁定規則
@ExceptionHandler:定義控制器異常后的操作
@ModelAttribute:在控制器方法執行前,對數據模型進行操作

@ControllerAdvice(basePackages = "com._54programer.controller")
public class MyControllerAdvice {

    /**
     * 異常處理, 發生異常時, 返回統一視圖
     */
    @ExceptionHandler(value = Exception.class)
    public String exception(Model model, Exception e){
        //給數據模型增加異常消息
        model.addAttribute("系統異常", e.getMessage());
        //返回異常視圖
        return "exception";
    }

}
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容