一、原理
Spring MVC基于模型-視圖-控制器(Model-View-Controller,MVC)模式實作,它能夠幫你建構像Spring架構那樣靈活和松耦合的Web應用程式,将請求處理的邏輯和視圖中的渲染實作解耦。

1、DispatcherServlet是Spring MVC的核心 。Spring MVC 中的請求頁面都會委托給DispatcherServlet來執行處理。
2、DispatcherServlet需要知道将請求發送給哪個控制器,是以DispatcherServlet會查詢一個或多個處理器映射(handler mapping) 來确定請求的下一站在哪裡。
3、到了控制器(controller),請求會卸下其負載(使用者送出的資訊)并耐心等待控制器處理這些資訊。
4、控制器在處理完成後,通常會産生一些資訊,這些資訊稱為模型(model)。但是這個模型到底是渲染哪個頁面的呢?是以控制器還會傳回視圖相關的東西。Spring 有個思想就是前後端分離,為了和視圖解耦,是以控制器隻傳回了視圖名。即,這裡控制器傳回了模型和視圖名(modelAndViews)。
tips:Model 實際上就是一個Map(也就是key-value對的集合),它會傳遞給視圖,這樣資料就能渲染到用戶端了,當調用addAttribute()方法并且不指定key的時候,那麼key會根據值的對象類型推斷确定,比如 List<Spittle>,那麼推斷他的 key 就是 spittleList。如果你希望使用非Spring類型的話,那麼可以用java.util.Map來代替Model。
5、MVC 要怎麼依靠一個視圖名找到對應的視圖呢?答案就是 視圖解析器(view resolver)。
6、視圖解析器(ViewResolver )接口會根據試圖名和Locale對象傳回一個View執行個體。View 接口的任務就是接受Model 以及Servlet的request和response對象,并将輸出結果渲染到response中。
7、視圖 (比如 JSP)。最終會被相應的容器(比如Tomcat)解析成 HTML 頁面,并響應使用者的請求。
tips:實際上,設計良好的控制器本身隻處理很少甚至不處理工作,而是将業務邏輯委托給一個或多個服務對象進行處理。
二、使用 Java 配置
按照傳統的方式,像 DispatcherServlet 這樣的Servlet會配置在web.xml檔案中 ,但是,借助于Servlet 3規範和Spring 3.1的功能增強,這種方式已經不是唯一的方案了 。我們會使用Java将DispatcherServlet配置在Servlet容器中。開始前,我們先來了解下 DispatcherServlet 和 Servlet 監聽器(也就是ContextLoaderListener) 這兩個應用上下文 。
DispatcherServlet 上下文:當DispatcherServlet啟動的時候,它會建立Spring應用上下文,并加載配置檔案或配置類(即帶有@configuration注解的配置類)中所聲明的bean,主要是Web 元件中的 bean, 包括 控制器(controller)、映射器(handler mapping)、視圖解析器(view resolver)等。
ContextLoaderListener 上下文:這個上下文 由 ContextLoaderListener 建立,主要負責加載應用中的其他 bean 。這些bean通常是驅動應用後端的中間層和資料層元件。
1、實作:
我們通過繼承 AbstractAnnotationConfigDispatcherServletInitializer 類來配置SpringMVC,以作為傳統 XML 配置的替代方案。實際上,AbstractAnnotationConfigDispatcherServletInitializer 會 同時建立 DispatcherServlet 和 ContextLoaderListener 。當然,我們需要手動配置我們的映射路徑、視圖解析器 并啟用元件掃描 以及一系列我們可以自定義的配置。當然,如果我們沒有配置視圖解析器,SpringMVC 會啟用預設的視圖解析器(通過查找 ID 與視圖名稱相比對的Bean,并且這個Bena 要實作View 接口)。如果沒有配置路徑映射,DispatcherServlet會映射為應用的預設Servlet,是以它會處理所有的請求,包括對靜态資源的請求,如圖檔和樣式表等。
public class SplittrWebAppInitailzer extends AbstractAnnotationConfigDispatcherServletInitializer {
/*傳回會建立ContextLoaderListener 上下文*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
/*傳回會建立 DispatcherServlet 上下文*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
/*配置路徑映射*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
最小但可用的SpringMVC配置
@Configuration
@ComponentScan(basePackages = {"com"},
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)
})
public class RootConfig {
}
RootConfig.java
@Configuration
@EnableWebMvc //啟用SpringMVC,當然也可以使用 <mvc:annotation-driven /> 注解驅動
@ComponentScan(basePackages = "com.controller")
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* 在查找的時候,它會在視圖名稱上加一個特定的字首和字尾
* (例如,名為home的視圖将會解析為/WEB-INF/pages/home.jsp)。
*
* @return
*/
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/pages/");
resolver.setSuffix(".jsp");
/*設定是否把所有在上下文中定義的bean作為request屬性可公開通路。
這樣在JSP 2.0中可使用${}來存取,JSTL中使用c:out。
預設為false。*/
resolver.setExposeContextBeansAsAttributes(true);
resolver.setViewClass(org.springframework.web.servlet.view.JstlView.class); //設定解析JSTL
return resolver;
}
/**
* 通過調用DefaultServlet-HandlerConfigurer的enable()方法,
* 我們要求DispatcherServlet将對靜态資源的請求轉發到Servlet容器
* 中預設的Servlet上,而不是使用DispatcherServlet本身來處理此類請求
*
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
WebConfig.java
InternalResourceViewResolver所采取的方式并不那麼直接。它遵循一種約定,會在視圖名上添加字首和字尾,進而确定一個Web應用中視圖資源的實體路徑。當邏輯視圖中包含斜線時,這個斜線也會帶到資源的路徑名中。
通過 resolver.setViewClass(org.springframework.web.servlet.view.JstlView.class) 配置了視圖解析器的ViewClass後,可以保證 JSTL的格式化和資訊标簽能夠獲得Locale對象以及Spring中配置的資訊資源。
2、測試:
@RequestMapping(value = {"/","/home"},method = RequestMethod.GET)
public String getHome(Model model){
return "home";
}
controller
3、請求參數說明
A、處理requet URL 部分(不含queryString)的注解: @PathVariable;
B、處理request header部分的注解: @RequestHeader, @CookieValue;
C、處理request body部分的注解:@RequestParam, @RequestBody;
D、處理attribute類型是注解: @SessionAttributes, @ModelAttribute;
@RequestParam:可以處理get方式中的queryString的值,也可以處理post方式的body data 的值。用來處理Content-Type 為 application/x-www-form-urlencoded 編碼的内容,送出方式GET、POST。
@RequestBody:
該注解常用來處理Content-Type: 不是application/x-www-form-urlencoded編碼的内容,例如application/json, application/xml等;
特殊情況下,也可以用來處理 application/x-www-form-urlencoded的内容,處理完的結果放在一個MultiValueMap<String, String>裡。
傳回結果不會被解析為跳轉路徑,而是直接寫入HTTP response body中。比如異步擷取json資料,加上@responsebody後,會直接傳回json資料
@ModelAttribute
該注解有兩個用法,一個是用于方法上,一個是用于參數上;
用于方法上時: 通常用來在處理@RequestMapping之前,為請求綁定需要從背景查詢的model;
用于參數上時: 用來通過名稱對應,把相應名稱的值綁定到注解的參數bean上;要綁定的值來源于:
A) @SessionAttributes 啟用的attribute 對象上;
B) @ModelAttribute 用于方法上時指定的model對象;(會綁定需要的對象,比如model.addAttribute("pet", pet);)
C) 上述兩種情況都沒有時,new一個需要綁定的bean對象,然後把request中按名稱對應的方式把值綁定到bean 對象的各個屬性中。
(1) SpringMVC 在 處理表單的時候,可以接受一個POJO對象(不用添加任何注解)作為參數。對象中的屬性會使用請求中同名的參數進行補充,預設調用@ModelAttribute。
可以接受一個基本資料類型(不用添加任何注解)作為參數。會使用請求中同名的參數進行補充,預設調用@RequestParam。
(2) 當InternalResourceViewResolver看到視圖格式中的“redirect:”字首時,它就知道要将其解析為重定向的規則,而不是視圖的名稱。InternalResourceViewResolver還能識别“forward:”字首。當它發現視圖格式中以“forward:”作為字首時,請求将會前往(forward)指定的URL路徑,而不再是重定向。
分享一篇這方面講得特别好的部落格:
http://blog.csdn.net/truong/article/details/280978374、添加自定義Servlet、Filter、Listener
public class MyServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("這是建立的Servlet");
}
}
自定義Servlet類
public class MyServletInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic myServlet = servletContext.addServlet("MyServlet", MyServlet.class);
myServlet.addMapping("/myServlet");
}
}
注冊Servlet
注冊Filter、Listener 也可以用類似的方式。但是,如果你隻是注冊Filter,并且該Filter隻會映射到DispatcherServlet上的話,那麼在AbstractAnnotationConfigDispatcherServletInitializer中還有一種快捷方式。
public class MyFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("過濾器的工作");
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
自定義Filter類
protected Filter[] getServletFilters() {
return new Filter[]{new MyFilter()};
}
在AbstractAnnotationConfigDispatcherServletInitializer的繼承上添加...
三、使用 XML 配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app
version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<!--Web應用圖示:指出IDE和GUI工具用來表示Web應用的大圖示和小圖示-->
<icon>
<small-icon>/images/small.gif</small-icon>
<large-icon>/images/large.gif</large-icon>
</icon>
<!--定義了WEB應用的名字-->
<display-name>mvc</display-name>
<!--聲明WEB應用的描述資訊-->
<description>mvc test</description>
<!--上下文參數:在servlet裡面可以通過 getServletContext().getInitParameter("name")得到-->
<!--設定根上下文配置檔案位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--配置過濾器-->
<filter>
<filter-name>encoding-filter</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>
</filter>
<filter-mapping>
<filter-name>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置監聽器 注冊ContextLoaderListener-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--配置Servlet 注冊DispatcherServlet-->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--會話逾時配置(機關為分鐘)-->
<session-config>
<session-timeout>120</session-timeout>
</session-config>
<!--mime類型配置,用來指定對應的格式的浏覽器處理方式-->
<!--配置靜态頁面的打開編碼-->
<mime-mapping>
<extension>htm</extension>
<mime-type>text/html;charset=gb2312</mime-type>
</mime-mapping>
<mime-mapping>
<extension>html</extension>
<mime-type>text/html;charset=gb2312</mime-type>
</mime-mapping>
<!--歡迎檔案頁配置-->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!--錯誤頁面配置-->
<!--配置了當系統發生404錯誤時,跳轉到錯誤處理頁面NotFound.jsp-->
<error-page>
<error-code>404</error-code>
<location>/NotFound.jsp</location>
</error-page>
<!--配置了當系統發生java.lang.NullException(即空指針異常)時,跳轉到錯誤處理頁面error.jsp-->
<error-page>
<exception-type>java.lang.NullException</exception-type>
<location>/error.jsp</location>
</error-page>
<!--以上是常見的配置,以下的東西也沒搞懂怎麼用,特别是 security-role 的含義指的是?-->
<!--安全限制配置-->
<!--與login-config元素聯合使用,指定伺服器應該怎樣給試圖通路受保護頁面的使用者授權-->
<security-constraint>
<web-resource-collection>
<web-resource-name>ProtectedArea</web-resource-name>
<url-pattern>/resources/*</url-pattern>
<!--如果沒有<http-method>方法,表示禁止所有的HTTP方法通路對應的資源-->
<http-method>GET</http-method>
</web-resource-collection>
<!--哪些使用者應該具有受保護資源的通路權
如果沒有 <auth-constraint> ,配置實際上是不起作用的。
如果内容為空,表示所有的身份都被禁止通路-->
<auth-constraint>
<role-name>ALL Role</role-name>
</auth-constraint>
</security-constraint>
<!--登入驗證配置四種認證類型 -->
<!-- BASIC:HTTP規範,Base64 這種方式被認為是最不安全的認證,因為它沒有提供強烈的加密措施 -->
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
<!-- DIGEST:HTTP規範,資料完整性強一些,但不是SSL 相比于BASIC認證,它是種比較安全的認證,它在認證時将請求資料 通過MD5的加密方式進行認證 -->
<login-config>
<auth-method>DIGEST</auth-method>
</login-config>
<!-- CLIENT-CERT:J2EE規範,資料完整性很強,公共鑰匙(PKC) 這是一種基于用戶端證書的認證方式,比較安全。但缺陷是在沒有安全證書的用戶端無法使用 -->
<login-config>
<auth-method>CLIENT-CERT</auth-method>
</login-config>
<!-- FORM:J2EE規範,資料完整性非常弱,沒有加密,允許有定制的登入界面 這是種基礎自定義表單的認證,你可以指定登入時的驗證表單 -->
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
<!--安全角色-->
<!--這些角色将出現在servlet元素内的security-role-ref元素的role-name子元素中。分别地聲明角色可使進階IDE處理安全資訊更為容易(沒看懂這句話)-->
<security-role>
<role-name>ALL Role</role-name>
</security-role>
</web-app>
tips:web.xml 的加載順序是:ServletContext -> context-param -> listener -> filter -> servlet ,而同個類型之間的實際程式調用的時候的順序是根據對應的 mapping 的順序進行調用的。
四、結語
2017年最後一篇博文了,堅持在2017年的最後一個晚上寫完。畢竟2017的事總不好意思拖一年呀!堅持寫部落格真是個好習慣,好記性畢竟不如白紙黑字來的牢靠啊,如果把記性比作網上搜尋的話,部落格就是自己的一份離線存儲。
本來想好好回顧下2017,打一大堆滿滿的文字,裝一個文藝的青年。真到落筆的時候,什麼都不想寫。敬往事一杯酒,悠悠歲月不回頭!
祝大家新年快樂!2018!我來了......