C 前端控制器 ——> DispatcherServlethtml
M 數據對象前端
V 視圖處理器ViewResvorjava
發起請求到前端控制器 DispatcherServlet
web
而後這個控制器會調用 HandlerMapping
查找對應的 Controller
或者說 Handler
spring
找到了對應的 Controller
就讓 HandlerAdaptor
去執行 handler
數據庫
執行了 handler
之後返回的就是 ModelAndView
對象。數組
對象返回給前端控制器,而後前端控制器會丟給 ViewResovr
去解析spring-mvc
而後繼續返回給前端控制器並返回給用戶。markdown
在 web.xml 中咱們須要配一個 Servlet 和一個 Listener ,這個 Servlet 其實就是咱們的路由調度器,而後 Listener 則是上下文監聽器。還有一個初始化參數就是指定 spring 的配置文件的位置。session
其實這些配置基本都是固定的,在使用 idea 創建 SpringMVC 項目的時候他會自動的幫咱們配置好,可是咱們仍是須要在進行一些配置。主要的配置以下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置 spring 的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!--1. / 全部的都進行解析,可是無論 jsp
2. *.from
3. /* 不能用 全部的都攔截
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>HttpHiddenMethods</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpHiddenMethods</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
複製代碼
接着就是配置 spring 配置文件,能夠看到在上面的 web.xml 中咱們在初始化參數中指定了 spring 的配置文件就是 applicationContext.xml 放在了 WEB-INF 路徑下面。
而後須要配置一些核心的 bean 讓 spring 進行自動加載,以須要配置掃描的包。
<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--<!–處理器映射器–>-->
<!--<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>-->
<!--<!–處理器適配器–>-->
<!--<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>-->
<!--視圖解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB_INF/templates/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置須要掃描的包-->
<context:component-scan base-package="com.mvc"/>
<!--必需要的,配置了 springMVC 的註解生效,否則的話咱們的 url 映射仍是經過配置文件的方式生效的
否則一直處於 404 狀態,這也就是下面的 dispatcher-servlet.xml 的做用配置 url 映射
-->
<mvc:annotation-driven />
</beans>
複製代碼
@Controller
public class Test {
@RequestMapping("/helloTest")
public String hello(){
return "hello";
}
}
複製代碼
這樣咱們的程序就可以跑起來了。
通常的咱們沒法直接使用 RESTful 風格的請求,可是在 SpringMVC 中有一個過濾器能夠幫咱們把一個 post 請求轉化成爲 PUT
或者 DELETE
請求。具體的作法以下:
<filter>
<filter-name>HttpHiddenMethods</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpHiddenMethods</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
複製代碼
<form method="post">
<input type="hidden" name="_method" value="PUT">
</form>
<form method="post">
<input type="hidden" name="_method" value="DELETE">
</form>
複製代碼
這個註解主要就是用來作方法和類的 url 映射的,也就是至關於一個路由映射文件。這個註解可使用在類上面也可使用在方法上面,在類上面的話就是咱們訪問每個方法的時候都須要加上類的 url 前綴。
他有幾個比較重要的屬性,用來管理 url 的:
value: 這個是默認的,是 url 地址
method:這個是用來指定請求方式的 RequestMethod.GET/POST/DELETE/PUT ….
params:這個是用來規定咱們的 url 中攜帶的參數的他是一個數組,能夠放多個值
params = {"username","age!=10"} params 是包含 username age!=10 headers 也是如此只是規定了請求頭而已
這個註解的做用是用來傳遞參數的,咱們不只僅可以使用 ?
來傳遞參數,還可使用更優雅的 /paramName/value
的方式來傳遞參數,而且可以直接在方法中綁定這些參數。
@RequestMapping(value = "/helloworld/{id}")
public String hello(@PathVariable("id") String id){
System.out.println("hello controller");
return "hello";
}
複製代碼
這裏的 id 必須和 url 中的保持一致,可是無需和方法的參數保持一致。
可是有一個問題,當咱們傳遞過來的參數是經過 ?
的形式傳遞過來的,那麼咱們又該怎麼去獲取他呢?是的這裏咱們可使用 @RequestParam
方法來獲取這些值,當咱們有些參數不是必須傳遞的咱們就可使用 require=false
來規定不用必傳,這個時候若是咱們沒有傳值,這個參數恰好是一個引用類型的就會是 null
可是若是是一個基本數據類型,就會報錯。咱們必須手動的設置一個默認值。
@RequestParam(value = "age",required = false,defaultValue = "0") Integer age
複製代碼
上面的參數就能夠獲取到 http://localhost/hello?age=10
這種的 url 的參數了、
用法同上!
@RequestHeader("Content-Type") String content
複製代碼
這個使用同上,獲取 Cookie 的值。
咱們的表單提交過來的數據會自動的被封裝到 POJO 對象中,咱們只須要配置好表單的 name 值和 Bean 的屬性值一致便可,若是說裏面有級聯的屬性,咱們就使用 proA.proB
來封裝。例如:
<form method="post">
<input type="text" name="name"/>
<input type="text" name="age"/>
<input type="text" name="address.code">
<input type="text" name="address.name">
</form>
複製代碼
這個表單就會被封裝成一個 POJO 對象,這個對象裏面有另一個類的引用就致使了級聯屬性的出現,咱們是就是使用了點的方式完成的封裝。
它支持比較多的原生的 Servlet 的 API ,實際上是在他內部調用了 request 對象的一些方法獲取到的。
HttpServletRequest
HTTPServletResponse
HttpSession
Locale
InputStream
OutputStream
Reader
Writer
Principal
通常咱們須要在控制器裏面綁定一些數據到視圖中,而後咱們能夠在視圖裏面採用標籤來獲取 Controller 裏面的數據從而展現這些數據,在 SpringMVC 中有幾種方法能夠達到這個目的。
這個東西其實就像他的名字同樣,他是 Model 數據和模型的結合體,咱們能夠往裏面添加數據(Model),也能夠把要轉發的頁面放在裏面讓視圖解析器去渲染。因此說這個對象裏面有一個 Model 屬性,這個屬性就是用來存放數據的,其實就是一個 Map 。Map 裏面的這些數據都會被遍歷而後放到 request 域對象之中,咱們只須要在請求域中獲取就好。
/**
* ModelAndView 來用做 Controller 與 View 之間的數據交互的介質
* 也就是 Model 的載體
* @return
*/
public ModelAndView modelAndViewTest(){
ModelAndView modelAndView = new ModelAndView("hello");
modelAndView.addObject("time", new Date());
return modelAndView;
}
複製代碼
其實三個東西類型都是 Map 類型的,而後SpringMVC 在真正的傳入的對象顯然就是他們的實現類,這裏咱們不過度糾結,基層確定是一個 Map 。Map 裏面的這些數據都會被遍歷而後放到 request 域對象之中,咱們只須要在請求域中獲取就好。
這個用起來也比較簡單,就是在方法的入參裏面傳入這個一個東西就好了,而不是採用的返回值。Map 的具體的泛型就是 string 和 object。看下面的例子。
public String modelMap(Map<String,Object> map){
map.put("time", new Date());
return "hello";
}
複製代碼
這個註解只能放在類上面,而後咱們使用 value 屬性或者 types 屬性來規定哪些屬性須要被放在 Session 域中,這個兩個屬性其實都是一個數組,因此咱們能夠方多個值。
value 這個屬性,就是當咱們在放入 map 中的一個鍵名的時候咱們就能夠把它放到 session 域中。而 types 屬性則是當咱們放一個 class 的時候他會自動抓取處於 map 中的同類型的數據。
@SessionAttributes(value = {"time","username"},types = {String.class,Integer.class})
複製代碼
這個註解是標識在方法上的,這個註解標識的方法會在全部的方法調用前被調用。在這個被標識的方法裏面咱們須要從數據庫中獲取對應的對象,而後把這個對象放到 map 裏面,可是注意 map 中的鍵必需要是咱們的 Model 類對應的小寫的一個字符串才能起做用。當咱們使用其餘的方法來進行某個 model 的修改動做的時候咱們某個字段不傳的話這時候在 map 中的那個對象的對應字段揮起一個補充做用,把對應字段填上。
執行流程:
首先是執行了被這個註解標識的方法,將數據庫中獲取到的值放到了 map 裏面,而後這個 map 是被放到了一個implicitModel 裏面
而後在咱們提交一個表單的時候,咱們對應的方法的參數會到 implicitModel 裏面查找對應的對象,查找的 key 就是先看看咱們的這個方法的參數是否被 ``@ModelAttribute(value="...")` 修飾。若是是的話咱們採用的是直接使用它的 value 屬性做爲 key 去查找。
若是沒有這個註解修飾參數,則採用這個 POJO 的類名第一個字母小寫做爲 key 查找。
若是沒有則看看是否這個類被 @SessionAttributes 註解 註釋,若是是則是去 session 中查找,若是沒有找到拋異常。
若是上面的狀況都沒找到,則是使用 POJO 反射建立一個新的對象把表單數據封裝進去,而若是上面有找到的話咱們就使用那個 Model 而後設置對應的屬性值。
接着把這個修改後的 model 放到 implicitModel 進而放到 request 域中。
視圖的解析步驟:
首先咱們是訪問了咱們的控制器。
而後咱們的控制器會返回 string 或者 VIewAndModel 對象。
他們都會被視圖解析器(咱們在 spring 中配置的 bean)包裝成一個 ModelAndView 對象。
最後渲染視圖,並轉發到對應的視圖。
能夠手動配置路由,不通過 controller 就能夠訪問到對應的視圖。在 spring 配置文件裏寫上以下內容:
<mvc:annotation-driven />
<!--手動配置路由 直接路由到視圖無需通過 controller-->
<mvc:view-controller path="/success" view-name="hello"/>
複製代碼
那麼咱們訪問 http://localhost:8080/success
就被轉發到 WEB_INF/templates/hello.jsp 具體的目錄取決於咱們配置的視圖解析器的前綴和後綴。
咱們通常採用的就是 InternalResourceViewResolver
這個視圖解析器,咱們也能夠自定義視圖。自定義視圖則須要一個特殊的視圖解析器完成解析視圖的工做,就是 BeanNameViewResolver
就是經過視圖的 bean 的 name 來獲取視圖的。因爲咱們配置了多個視圖解析器則須要定義一個優先級,哪一個視圖解析器先工做,使用 order 屬性。
<!--自定義的 Bean視圖解析器 直接經過 bean 的 name 獲取視圖-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="100"/>
</bean>
複製代碼
下面是咱們使用 bean 定義的一個視圖。
@Component
public class MyViewRevsor implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("hello");
}
}
複製代碼
咱們通常在 controller 中寫的東西默認都會轉發的,咱們須要重定向的話咱們只須要在人繪製前面加上 redirect 就能夠。
@RequestMapping("/beanView")
public String beanView(){
return "redirect:/myViewRevsor";
}
複製代碼
對於靜態資源咱們須要直接獲取而不須要進行映射,因此說咱們在獲取靜態資源的時候回出現 404 ,咱們就配置一個
<mvc:default-servlet-handler/>
這個就會自動的處理沒有映射的 url 。
表單數據在提交之後其實是依賴於 SpringMVC 裏面一個 WebDataBinder 類進行的數據綁定,這個 WebDataBinder 裏面有不少其餘的對象的引用其中就有數據格式化、數據校驗、數據轉換的對象,也就是說在這個數據轉換的過程咱們是能夠添加一些對象來手動的控制數據的綁定的。
converter 這個是用來數據轉換的,具體的參照文檔,就好比咱們把前端的一個字符串轉成方法入參的一個 bean 對象,就通過這個 converter 來完成。
@initBinder 被這個註解表示的方法,會在數據綁定以前進行運行,其功能就是對 binder 進行一些設置好比忽略一些字段。修改字段,拒絕字段等等。
@InitBinder public void initBinder(WebDataBinder binder) { // 拒絕 name 字段 binder.setDisallowedFields("name"); }
數據的格式化,採用註解註解在對應的 bean 的字段上。經常使用的有時間還有數子。
JSR303 校驗規範,這只是 JavaEE 的規範,真正的實現類是 Hibernate ValidData ,而後咱們進行數據校驗也是使用註解的方式,具體的註解在規範裏面都有,都比較簡單。而且咱們須要在方法的入參的 bean 上加上 @Valid 註解。
錯誤消息的回顯,顯然若是咱們的校驗生效而且有錯誤的話咱們須要回顯到表單。咱們就須要在方法的參數裏面加上一個 BindResult 對象,而後錯誤的數據都會被放到這個東西里面,同時 BindResult 是一個 Error 類型的對象,因此咱們亦能夠放這個對象。最後放到 map 裏面在前端回顯便可。
只須要在方法上加上 @ResponseBody 就能夠,返回值是一個 List 或數組。
<!--配置攔截器-->
<mvc:interceptors>
<!--自定義的攔截器組件-->
<bean class="com.mvc.MyInterceptor"/>
<!--更詳細的配置能夠針對 url-->
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<mvc:exclude-mapping path="/hah"/>
<bean class="com.mvc.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
@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 {
}
}
複製代碼
統一異常處理就採用對應的 handler 來處理異常,使用 @ExceptionHandler 註解,而後標註要處理的異常類型,可是若是說咱們須要把異常帶到錯誤頁面咱們不能使用 map 而只能使用 ModelAndView 否則那就會報錯。
@ExceptionHandler({ArithmeticException.class})
public ModelAndView error(Exception ex){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("ex", ex);
return modelAndView;
}
複製代碼
若是他在當前的 controller 中找不到對應的 ExceptionHandler 就去查找對應的 @ControllerAdvise 註解表示的類,中的ExceptionHandler 註解方法。也就是默認的處理器。