1. 程式人生 > 其它 >淺析 SpringMVC 原理和配置.

淺析 SpringMVC 原理和配置.

一、原理

      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[]{"/"};
    }
}
@Configuration
@ComponentScan(basePackages = {"com"},
        excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)
        })
public class RootConfig {
}
@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();
    }
}

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

常用註解:

@RequestMapping(value = {"/","/home"},method = RequestMethod.GET) //請求對映 @RequestParam(required = true,value = "size") int size //普通請求引數接收 @RequestMapping(value = "/list/{size}",method = RequestMethod.GET) //路徑請求引數 @PathVariable(value = "size") int size //路徑請求引數接收 tips: 當@PathVariable 沒有指定value的時候,SpringMVC 會假設方法引數名作為value @responsebody //返回結果不會被解析為跳轉路徑,而是直接寫入HTTP response body中。比如非同步獲取json資料,加上@responsebody後,會直接返回json資料。

(1) SpringMVC 在 處理表單的時候,可以接受一個POJO物件(不用新增任何註解)作為引數。物件中的屬性會使用請求中同名的引數進行補充。 (2) 當InternalResourceViewResolver看到檢視格式中的“redirect:”字首時,它就知道要將其解析為重定向的規則,而不是檢視的名稱。InternalResourceViewResolver還能識別“forward:”字首。當它發現檢視格式中以“forward:”作為字首時,請求將會前往(forward)指定的URL路徑,而不再是重定向。

3、新增自定義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");
    }
}
public class MyServletInitializer implements WebApplicationInitializer {
    public void onStartup(ServletContext servletContext) throws ServletException {
        ServletRegistration.Dynamic myServlet = servletContext.addServlet("MyServlet", MyServlet.class);
        myServlet.addMapping("/myServlet");
    }
}

    註冊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 {

    }

}
    protected Filter[] getServletFilters() {
        return new Filter[]{new MyFilter()};
    }

三、使用 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!我來了......