在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";
}
}