目錄
- MVC核心組件
- @RequestMapping注解
- 域對象共享數據
- 視圖
- RESTful
- 請求與響應
- HttpMessageConverter
- 請求
- 響應
- 攔截器配置
- 異常處理
- 基于配置的異常處理
- 基于注解的異常處理
- 配置類與注解配置
- MVC執行流程
Spring MVC是Spring Framework提供的Web組件,全稱是Spring Web MVC,是目前主流的實現MVC設計模式的框架,提供前端路由映射、視圖解析等功能。
MVC是一種軟件架構思想,把軟件按照模型,視圖,控制器來劃分。
View
:視圖層,指工程中的html,jsp等頁面,作用是和用戶進行交互,展示數據。
Controler
:控制層,指工程中的Servlet,作用是接收請求和響應瀏覽器,調用業務邏輯。
Model
:模型層,指工程中的JavaBean,進行數據交互,用來處理數據。
JavaBean分成兩類:
一實體類Bean:專門用來存儲業務數據的實體類。
一業務處理Bean: 指Servlet或Dao對象,專門用來處理業務邏輯和數據訪問。
服務端三層架構:
表現層 業務邏輯層 數據訪問層
Spring MVC Spring MyBatis
MVC核心組件
DispatcherServlet
:前置控制器,負責調度其他組件的執行,可以降低不同組件之間的耦合性。
Handler
:處理器,完成具體的業務邏輯,相當于Servlet。
HandlerMapping
:DispatcherServlet是通過 HandlerMapping把請求映射到不同的Handler。
HandlerInterceptor
:處理器攔截器,如果我們需要進行一些攔截處理,可以通過實現該接口完成。
HandlerExecutionChain
:處理器執行鏈。
HandlerAdapter
:處理器適配器,Handler執行業務方法之前,需要進行一系列的操作包括數據類型轉換、把表單數據封裝等,這些一系列的操作都是由HandlerAdapter完成,DispatcherServlet通過HandlerAdapter執行不同的Handler。
ModelAndView
:封裝了模型數據和視圖信息,作為Handler的處理結果,返回給DispatcherServlet。
ViewResolver
:視圖解析器,DispatcherServlet通過它把邏輯視圖解析為物理視圖,最終把渲染的結果響應給客戶端
mvc響應流程:
客戶端請求被DispatcherServlet接收,根據HandlerMapping映射到Handler,生成Handler和HandlerInterceptor,Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回給DispatcherServlet,DispatcherServlet通過HandlerAdapter調用Handler的方法完成業務邏輯處理,返回一個ModelAndView對象給DispatcherServlet,DispatcherServlet把獲取的ModelAndView對象傳給ViewResolver視圖解析器,把邏輯視圖解析成物理視圖。ViewResolver返回一個View,把模型填充到視圖中,DispatcherServlet把渲染后的視圖響應給客戶端。
@RequestMapping注解
底層是@Maping
注解,作用在類、接口和方法上,主要屬性有name、value、method和params,其他屬性如下。
String name() default "";
//path,和value一樣
@AliasFor("path")
String[] value() default {};
//value,一個字符串數組
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
value
屬性必須配置,當有多個時能夠匹配多個請求。支持ant風格路徑,?
表示匹配任意單個字符(不為空、?和斜線),*
表示匹配任意的0或多個字符(不為?和斜線),**
表示匹配任意一層或多層目錄或路徑(0層也匹配),必須為/**/test
,左右不添任何字符。
@RequestMapping("/a?c/test")
public String test(){return "test";
}
//?,<a th:href="@{/abc/test}"></a>
//*,<a th:href="@{/ac/test}"></a>
//**,<a th:href="@{/test}"></a>
Spring MVC支持路徑中的占位符,原始方式:/test?id=1
,Rest風格:/test/1
。
@RequestMapping("/test/{id}")
public String test(@PathVariable("id")Integer Id){System.out.println("id="+ID);return "test";
}
//<a th:href="@{/test/1}"></a>
好處是可以在路徑中傳入參數。
method
屬性可以通過請求方式匹配請求,比如get(參數放請求頭、無請求體、速度快)、post(安全、參數放請求體,傳數據量大),沒有配置則匹配任意方式請求,不支持的請求方式瀏覽器報405。
@RequestMapping(value={"/user","/admin"},method={RequestMethod.get,RequestMethod.post})
派生注解:GetMapping
、@PostMapping
、@DeleteMapping
、@PutMapping
對應不同的請求方式。
而目前瀏覽器只支持get、post兩種請求方式,其他請求方式默認通過get請求,設置hidden隱藏域完成。這一點在spring boot原理解析有講。
<form action="/user" method="post"><input name="_method" type="hidden" value="delete"/><input type="submit" value="delete提交"/>
</form>
SpringBoot中手動開啟。
spring:mvc:hiddenmethod:filter:enabled: true #開啟頁面表單的Rest功能
設置自定義的methodFilter,編寫webconfig配置類,創建filter對象,調用HiddenHttpMethodFilter的setMethodParam方法。
params
屬性同樣也是一個數組,參數設置為多個時請求參數必須全部滿足才能匹配并執行方法,不滿足則報400錯誤。
@RequestMapping(value={"/user","/admin"},params={"username=admin","pass"})
//<a th:href="@{/admin(username='admin',pass=123456)}"></a>
//<a th:href="@{/admin?username=admin&pass=123456}"></a>
也可以不用params屬性獲取參數,只要方法中的形參與請求中的參數名保持一致,便能獲取參數值。
其他方式獲取請求參數:
(1)ServletAPI獲取請求參數:
@RequestMapping("/test")
public String test(HttpServletRequest r){String username = r.getParameter("username");String password = r.getParameter("pass");System.out.println(username+":"+password);return "test";
}
//<a th:href="@{/admin?username=admin&pass=123456}"></a>
(2)springMVC獲取多個請求參數
hobby為一個參數數組或集合,若請求參數中出現多個同名的參數,可以在方法中設置形參為字符串類型或字符串數組接收參數,若為字符串則參數值用逗號拼接。
@RequestMapping("/test")
public String test(String username,String password,String hobby){System.out.println(username+";"+password+";"+hobby);return "test";
}
(3) 通過@RequestParam
注解
@RequestParam
將建立請求參數與控制器方法中的形參之間的映射關系,含有屬性value(請求參數),布爾屬性required,默認true,必須傳入該請求參數。默認值defaultValue屬性,為該參數設置一個默認值。
@RequestMapping("/test")
public String test(@RequestParam("user_name")String username,String password,String[] hobby){System.out.println(username+";"+password+";"+Arrays.toString(hobby));return "test";
}
(4)通過實體類對象接收請求參數
這種方式要求實體類的對象屬性與請求參數對應并一致,名字必須相同。
@RequestMapping("/test")
public String test(User user){System.out.println(user);return "test";
}
Headers
屬性同樣也是一個數組,設置header請求頭參數信息,比如Accept、Host、User-Agent等,不符合Header參數則瀏覽器報404錯誤。
@RequestMapping(value={"/user","/admin"},header={"Host=localhost:8080"})
@RequestHeader
注解建立請求頭與方法中形參之間的映射,他與@RequestParam
一樣具有相同的屬性。
@CookieValue
注解將Cookie數據與控制器方法的形參建立映射關系,屬性與上述一樣,用法也一樣。
CharacterEncodingFilter處理請求參數亂碼問題:
<!--web.xml中配置過濾器-->
<filter><filter-name>encoding</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>utf-8</param-value></init-param><init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param>
</filter>
域對象共享數據
域對象:request、session、applicationcontext
(1)通過Servlet在request共享數據
@RequestMapping("/test")
public String test(HttpServletRequest r){//name=valuer.setAttribute("testScope","helloworld");return "test";
}
//獲取<a th:text="${testScope}"/>
(2)通過ModelAndView向request共享數據
@RequestMapping("/test")
public ModelAndView test(){ModelAndView mav = new ModelAndView();mav.addObject("testScope","helloworld");mav.setViewName("test");return mav;
}
//獲取<a th:text="${testScope}"/>
(3)通過Model向request共享數據
@RequestMapping("/test")
public String test(Model model){model.addAttribute("testScope","helloworld")return "test";
}
//獲取<a th:text="${testScope}"/>
(4)通過Map向request共享數據
@RequestMapping("/test")
public String test(Map<String,Object> map){map.put("testScope","helloworld")return "test";
}
//獲取<a th:text="${testScope}"/>
(5)通過ModelMap向request共享數據
@RequestMapping("/test")
public String test(ModelMap modelMap){modelMap.addAttribute("testScope","helloworld")return "test";
}
不管使用何種方式共享數據,返回頁面,最終將通過DispatcherServlet封裝在一個ModelAndView
中,并通過視圖解析器解析。
向session中共享數據:
@RequestMapping("/test")
public String test(HttpSession httpSession){//name=valuehttpSession.setAttribute("testScope","helloworld");return "test";
}
//獲取<a th:text="${session.testScope}"/>
向application中共享數據:
@RequestMapping("/test")
public String test(HttpSession httpSession){//request也可以獲取applicationServletContext application = httpSession.getServletContext();application.setAttribute("testScope","helloworld");return "test";
}
//獲取<a th:text="${application.testScope}"/>
視圖
springMVC的視圖是View接口,用于渲染數據,把model中的數據放入view中展示。默認視圖種類有重定向Redirect和轉發Forward。當控制器中的視圖名稱無任何前綴,此時視圖會被配置的視圖解析器解析,最終通過轉發出去,比如ThymeleafResolver。
@RequestMapping("/test")
public String test(){return "test";
}
轉發視圖InternalResourceViewResolver:
@RequestMapping("/test")
public String test(){return "forward:/test2";
}
@RequestMapping("/test2")
public String test(){return "test";
}
重定向視圖RedirectViewResolver:
此時視圖名不會被配置的視圖解析器解析,而是創建一個重定向視圖解析器,去掉redirect:重定向到另一個請求。
@RequestMapping("/test")
public String test(){return "redirect:/test2";
}
使用視圖控制器view-controller
在類路徑下SpringMVC.xml配置文件中使用view-controller標簽,配置請求路徑與視圖名稱的對應關系。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"><context:component-scan base-package="com.example.mvc.controller"/><bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"><property name="order" value="1" /><property name="characterEncoding" value="UTF-8" /><property name="templateEngine"><bean class="org.thymeleaf.spring5.SpringTemplateEngine"><property name="templateResolver"><bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"><property name="prefix" value="/WEB-INF/templates/" /><property name="suffix" value=".html" /><property name="templateMode" value="HTML5" /><property name="characterEncoding" value="UTF-8" /></bean></property></bean></property></bean><mvc:view-controller path="/" view-name="index"></mvc:view-controller>
</beans>
如果既使用了視圖控制器,又使用了控制器方法,需要開啟注解。
<mvc:annotation-driven></mvc:annotation-driven>
開啟對靜態資源的訪問:
<mvc:default-servlet-handler/>
RESTful
REST: Representational State Transfer表現層資源狀態轉移。
特點:
1)不同的請求方式對應不同的控制器處理方法,get,post,delete,put
2)請求參數融入URL中成為一個完整URL,/user/1,而不是用問號開頭
3)雖然請求方式不同,但請求地址的路徑大致相同,比如以同樣的開頭/user,/user/{id}
由于瀏覽器普通只支持get和post請求,所以這里只介紹通過表單隱藏域方式實現put和delete請求處理。
上述條件:請求方式為post(Ajax除外);請求參數中有_method,用于替換請求方式,type為hidden隱藏。
(1)delete刪除請求
@DeleteMapping("/user/{id}")
public String deleteUserById(@PathVariable("id")Integer Id){System.out.println("刪除用戶id="+ID+"的用戶");return "redirect:/user";
}
刪除用戶:
<a th:href="@{/user/}+${user.id}">刪除</a>
或
<a th:href="@{'/user/'+${user.id} }">刪除</a>
(1)put修改請求
put請求處理
@PutMapping("/user/{id}")
public String updateUserById(@PathVariable("id")Integer Id){System.out.println("修改用戶id="+ID+"的用戶");return "redirect:/user";
}
修改用戶表單:
<form action="/user" method="post"><input name="_method" type="hidden" value="PUT"/><input name="id" type="hidden" th:value="${user.id}"/>用戶名:<input type="text" value="username"/>密碼:<input type="password" value="password"/><input type="submit" value="修改"/>
</form>
請求與響應
HttpMessageConverter
http消息轉換器,將請求報文信息轉換為java對象,或將java對象轉換為響應報文。它提供了兩個注解和兩個類型:@RequestBody
、@ResponseBody
、RequestEntity
、ResponseEntity
。
請求
1)@RequestBody
獲取請求體,需要在控制器方法中設置形參,并用該注解標識。
@RequestMapping("/test")
public String test(@RequestBody String requestBody){System.out.println("RequestBody="+requestBody);return "test";
}
2)RequestEntity
封裝請求報文的java類型,在控制器方法中的參數前設置,可以獲取請求頭、請求體等信息。
@RequestMapping("/test")
public String test(RequestEntity<String> requestEntity){System.out.println("請求頭:"+requestEntity.getHeaders());System.out.println("請求體:"+requestEntity.getBody());return "test";
}
響應
使用HttpServletResponse實現響應數據:
@RequestMapping("/test")
public String test(HttpServletResponse response) throws IOException{//將helloworld作為響應體返回response.getWriter().print("helloworld");
}
3)@ResponseBody
在控制器方法上標識,將該方法的返回值作為響應報文的響應體返回給瀏覽器。
@RequestMapping("/test")
@ResponseBody
public String test(RequestEntity<String> requestEntity){String test = "abc";return test;
}
@ResponseBody處理json對象:
springboot自動配置了默認的jackson依賴,只需要在核心配置文件中進行配置
spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: Asia/Shanghai
如何使用springMVC則需要導入依賴,在springmvc的核心配置文件中配置注解驅動,在處理器方法上使用@ResponseBody
注解標識,并將Java對象作為方法返回值返回,就會自動轉換為json格式的字符串。
@RestController注解
@RestController
是一個復合注解,標識在類上,相當于為類添加@Controller注解,為類中的所有方法添加@ResponseBody注解。
4)ResponseEntity
將該類型的數據作為控制器方法的返回值,該返回值就是響應報文,即可以自定義響應報文。
MultiValueMap<String,String> headers = new HttpHeaders();
headers.add("Content-Disposition","attchment;filename=xx.jpg");
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes,headers,statuscode) ;
使用ResponseEntity文件下載:使用ServletContext獲取下載文件真實路徑并創建輸入流對象,創建字節數組,輸入流讀入字節數組,創建響應頭對象HttpHeaders和狀態碼HttpStatus,設置下載方式和文件名,最后根據響應頭、狀態碼、讀入流創建ResponseEntity對象并返回。
文件上傳:控制器方法接收MultipartFile
類型的文件數據,先通過session獲得ServletContext對象,查看文件名是否在對應路徑中存在,若存在使用UUID作為文件名拼接文件名后綴上傳,若文件不存在創建文件上傳路徑,MultipartFile
上傳對象調用tranferTo方法上傳即可。
<form th:action="@{/fileupload}" method="post" enctype="multipart/formdata">文件:<input type="file" value="photo"/><input type="submit" value="上傳"/>
</form>
攔截器配置
SpringMVC中攔截器用于攔截控制器方法,攔截器必須實現HandlerInterceptor
接口(ctrl±o)重寫preHandle
方法或者繼承HandlerInterceptorAdapter
類。攔截器在配置文件或配置類中配置,config配置類須實現WebMvcConfigurer
的配置接口,重寫addInterceptor
方法,注冊攔截器。
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//獲取sessionHttpSession httpSession = request.getSession();if(httpSession.getAttribute("loginUser")==null){response.sendRedirect("/f/toLogin");return false;//攔截}//放行return true;}
}
攔截器配置:
<mvc:interception><mvc:mapping path="/**"/><mvc:exclude-mapping path="/login"/><ref bean="LoginInterceptor" />
</mvc:interception>
三個抽象方法:
preHandle
執行控制器方法前執行該方法,postHandle
執行控制器方法后執行該方法,afterComplation
渲染視圖后執行該方法。
多個攔截器執行順序:與配置的先后順序或@Order注解的優先級順序有關,執行流程具體如下。
先順序執行所有攔截器的preHandle方法
- 如果當前攔截器prehandler返回為true,則執行下一個攔截器的preHandle。
- 如果當前攔截器返回為false,直接倒序執行所有已經成功執行(返回true)的攔截器的afterCompletion。
如果任何一個攔截器返回false,直接跳出不執行目標方法,postHandle都不會執行,倒序執行所有已經成功執行(返回true)的攔截器的afterCompletion方法。當所有攔截器preHandle都返回True,執行目標方法,倒序執行所有攔截器的postHandle方法。前面的步驟有任何異常都會直接倒序觸發afterCompletion。頁面成功渲染完成以后,也會倒序觸發 afterCompletion。
異常處理
對于控制器方法執行所出現的異常,SpingMVC提供了HandlerExceptionResolver
接口,其實現類有默認異常處理解析器DefaultHandlerExceptionResolver
和自定義的SimpleMappingExceptionResolver
簡單映射異常處理解析器。
基于配置的異常處理
Spring MVC配置文件:
- properties的鍵表示處理器方法執行過程中出現的異常
- properties的值表示若出現指定異常時,設置一個新的視圖名稱,跳轉到指定頁面
- exceptionAttribute屬性設置一個屬性名,將出現的異常信息在請求域中進行共享
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="exceptionMappings"><!--設置異常映射,算術運算異常:例如1/0,跳轉到error.html--><props><prop key="java.lang.ArithmeticException">error</prop></props></property><!--設置在請求域中共享的異常屬性信息,th:text="${e}"--><property name="exceptionAttribute" value="e"/>
</bean>
基于注解的異常處理
@ControllerAdvice
:將當前類標識為異常處理的組件,對控制器Controller的增強,可對 controller中被 @RequestMapping注解的方法加一些邏輯處理。
@ExceptionHandler
:用于設置所標識的方法處理的異常,@ExceptionHandler加在ControllerAdvice中,處理全局異常。@ExceptionHandler的value值可以是數組,可以添加許多可能出現的異常,在該方法中當出現算數運算異常或空指針異常就會跳轉到設置的異常頁面,并且展示異常信息。
@ControllerAdvice
public class ExceptionController {@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})public String exception(Exception e, Model model){model.addAttribute("e",e);return "error";}
}
//<p th:text="${e}"/>
配置類與注解配置
在配置中,使用@Configuration
注解標識配置類,使用 @EnableWebMvc
注解來啟用MVC配置,使用@ComponentScan
開啟組件掃描。
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {//攔截器配置@Overridepublic void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/","/login.html","/f/toLogin","/user/login","/img/*","/layui/**");}//文件上傳解析器@Beanpublic MultipartResolver multipartResolver(){CommonsMultipartResolver cmr = new CommonsMultipartResolver();return cmr;}//異常處理@Overridepublic void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers){SimpleMappingExceptionResolver eResolver = new SimpleMappingExceptionResolver();Properties prop = new Properties();prop.setProperty("java.lang.ArithmeticException","errpr");eResolver.setExceptionMapping(prop);eResolver.setExceptionAttribute("e");resolvers.add(eResolver);}}
其他組件配置如請求媒體內容協商器配置ContentNegotiationConfigurer
、信息轉換器配置configureMessageConverters
、視圖控制器addViewControllers
、configureViewResolvers
視圖解析器、addResourceHandlers
靜態資源、configureDefaultServletHandling
請求處理,可以參考以下文章:webconfig配置 Spring MVC 配置詳解 WebMvcConfigurer
MVC執行流程
DispatcherServlet.doService()->DispatcherServlet.doDispatch()
繼承FrameworkServlet.service()–>super.service()->doGet()…
? >processRequest()->抽象doService()
1)前端控制器DispatcherServlet捕獲請求,解析請求URL得到URI,判斷URI對應映射,若沒有相應的映射處理則交給default-servlet-handler處理,訪問靜態資源,沒有找到同樣返回404。
2)若找到了對應的映射,則尋找到合適的Handler、HandlerAdapter、相應攔截器鏈。
3)執行攔截器preHandler,進入一個異常處理流程,preHandle執行成功將執行Handle控制器方法。
4)Handler執行完后返回一個ModelAndView對象。
5)倒序執行攔截器postHandle和afterCompeletion方法。
6)根據ModelAndView對象,選擇合適的ViewResolver進行視圖解析渲染視圖,如有異常,異常處理解析器HandlerExceptionResolver會處理。