Spring MVC概念

Spring MVC基於模型-視圖-控制器(Model-View-Controller,MVC)模式實現,可以構建像Spring框架那樣靈活和鬆耦合的Web應用程序html

跟蹤Spring MVC的請求

圖片描述

在請求離開瀏覽器時①,會帶有用戶所請求內容的信息,至少會包含請求的URL前端

請求旅程的第一站是Spring的DispatcherServlet。Spring MVC全部的請求都會經過一個前端控制器(front controller)Servlet。前端控制器是經常使用的Web應用程序模式,在這裏一個單實例的Servlet將請求委託給應用程序的其餘組件來執行實際的處理。在Spring MVC中,DispatcherServlet就是前端控制器java

DispatcherServlet的任務是將請求發送給Spring MVC控制器(controller)。控制器是一個用於處理請求的Spring組件。在典型的應用程序中可能會有多個控制器,DispatcherServlet須要知道應該將請求發送給哪一個控制器。因此DispatcherServlet以會查詢一個或多個處理器映射(handler mapping)②來肯定請求的下一站在哪裏。處理器映射會根據請求所攜帶的URL信息來進行決策jquery

一旦選擇了合適的控制器,DispatcherServlet會將請求發送給選中的控制器③。到了控制器,請求會卸下其負載(用戶提交的信息)並耐心等待控制器處理這些信息。(實際上,設計良好的控制器自己只處理不多甚至不處理工做,而是將業務邏輯委託給一個或多個服務對象進行處理。)web

控制器在完成邏輯處理後,一般會產生一些信息,這些信息須要返回給用戶並在瀏覽器上顯示。這些信息被稱爲模型(model)。不過僅僅給用戶返回原始的信息是不夠的——這些信息須要以用戶友好的方式進行格式化,通常會是HTML。因此,信息須要發送給一個視圖(view),一般會是JSPspring

控制器所作的最後一件事就是將模型數據打包,而且標示出用於渲染輸出的視圖名。它接下來會將請求連同模型和視圖名發送回DispatcherServlet④數據庫

這樣,控制器就不會與特定的視圖相耦合,傳遞給DispatcherServlet的視圖名並不直接表示某個特定的JSP。實際上,它甚至並不能肯定視圖就是JSP。相反,它僅僅傳遞了一個邏輯名稱,這個名字將會用來查找產生結果的真正視圖。DispatcherServlet將會使用視圖解析器(view resolver)⑤來將邏輯視圖名匹配爲一個特定的視圖實現,它多是也可能不是JSPexpress

既然DispatcherServlet已經知道由哪一個視圖渲染結果,那請求的任務基本上也就完成了。它的最後一站是視圖的實現(多是JSP)⑥,在這裏它交付模型數據。請求的任務就完成了。視圖將使用模型數據渲染輸出,這個輸出會經過響應對象傳遞給客戶端⑦json

搭建Spring MVC

配置DispatcherServlet

DispatcherServlet是Spring MVC的核心。在這裏請求會第一次接觸到框架,它要負責將請求路由到其餘的組件之中後端

使用Java將DispatcherServlet配置在Servlet容器中,而不使用web.xml文件。以下的程序清單展現了所需的Java類:

// 配置DispatcherServlet
package spittr.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispactcherServletInitializer
{
    @Override
    protected String[] getServletMappings()        // 將DispatcherServlet映射到「/」
    {
        return new String[] {"/"};
    }
    
    @Override
    protected class<?>[] getRootConfigClasses()
    {
        return new class<?>[] {RootConfig.class};
    }
    
    @Override
    protected class<?>[] getServletConfigClasses()        // 指定配置類
    {
        return new class<?>[] {WebConfig.class};
    }
}

擴展AbstractAnnotationConfigDispatcherServletInitializer的任意類都會自動地配置Dispatcher-Servlet和Spring應用上下文,Spring的應用上下文會位於應用程序的Servlet上下文之中

AbstractAnnotationConfigDispatcherServletInitializer剖析

在Servlet 3.0環境中,容器會在類路徑中查找實現javax.servlet.ServletContainerInitializer接口的類,若是能發現的話,就會用它來配置Servlet容器

Spring提供了這個接口的實現,名爲SpringServletContainerInitializer,這個類反過來又會查找實現WebApplicationInitializer的類並將配置的任務交給它們來完成。Spring 3.2引入了一個便利的WebApplicationInitializer基礎實現,也就是AbstractAnnotationConfigDispatcherServletInitializer。由於咱們的Spittr-WebAppInitializer擴展了AbstractAnnotationConfig DispatcherServlet-Initializer(同時也就實現了WebApplicationInitializer),所以當部署到Servlet 3.0容器中的時候,容器會自動發現它,並用它來配置Servlet上下文

在上述程序中,SpittrWebAppInitializer重寫了三個方法:
第一個方法getServletMappings(),它會將一個或多個路徑映射到DispatcherServlet上。在本例中,它映射的是「/」,這表示它會是應用的默認Servlet。它會處理進入應用的全部請求

爲了理解其餘的兩個方法,首先要理解DispatcherServlet和一個Servlet監聽器(也就是ContextLoaderListener)的關係

兩個應用上下文之間的故事

啓動DispatcherServlet時,建立Spring應用上下文,並加載配置文件或配置類中所聲明的bean。在上述程序的getServletConfigClasses()方法中,DispatcherServlet加載應用上下文時,使用定義在WebConfig配置類(使用Java配置)中的bean

可是在Spring Web應用中,一般還會有另一個應用上下文。另外的這個應用上下文是由ContextLoaderListener建立的

DispatcherServlet會加載包含Web組件的bean,如控制器、視圖解析器以及處理器映射,而ContextLoaderListener加載應用中的其餘bean。一般是驅動應用後端的中間層和數據層組件

AbstractAnnotationConfigDispatcherServletInitializer會同時建立DispatcherServlet和ContextLoaderListener。GetServletConfigClasses()方法返回的帶有@Configuration註解的類將會用來定義DispatcherServlet應用上下文中的bean。getRootConfigClasses()方法返回的帶有@Configuration註解的類將會用來配置ContextLoaderListener建立的應用上下文中的bean

在本例中,根配置定義在RootConfig中,DispatcherServlet
的配置聲明在WebConfig中

經過AbstractAnnotationConfigDispatcherServletInitializer來配置DispatcherServlet是傳統web.xml方式的替代方案

使用這種方式配置DispatcherServlet,而不使用web.xml,則只能部署到支持Servlet 3.0的服務器中才能正常工做,如Tomcat 7或更高版本

啓動Spring MVC

// 最小但可用的Spring MVC配置
package spittr.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc        // 啓用 Spring MVC
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter 
{
    @Bean
    public ViewResolver viewResolver()        // 配置JSP視圖解析器 
    {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
    {
        configurer.enable();        // 配置靜態資源的處理
    }
}

上述程序中WebConfig如今添加了@ComponentScan註解,所以將會掃描spitter.web包來查找組件。編寫的控制器將帶有@Controller註解,這會使其成爲組件掃描時的候選bean。所以無需在配置類中顯式聲明任何的控制器

ViewResolver bean具體來說是InternalResourceViewResolver(試圖解析器)。它會查找JSP文件,查找時會在視圖名稱上加一個特定的前綴和後綴(如名爲home的視圖將會解析爲/WEB-INF/views/home.jsp)

擴展了WebMvcConfigurerAdapter並重寫了其configureDefaultServletHandling()方法。經過調用DefaultServletHandlerConfigurer的enable()方法,要求DispatcherServlet將對靜態資源的請求轉發到Servlet容器中默認的Servlet上,而不是使用DispatcherServlet自己來處理此類請求

package spittr.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages={"spitter", excludeFilters{@Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)}})
public class RootConfig{}

Spring MVC配置文件dispatcher-servlet.xml詳解

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.2.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
    <!-- 使用spring提供的PropertyPlaceholderConfigurer讀取數據庫配置信息.properties
     一、這裏的classpath能夠認爲是項目中的src-
     二、屬性名是 locations,使用子標籤<list></list>能夠指定多個數據庫的配置文件,這裏指定了一個
     其中order屬性表明其加載順序,而ignoreUnresolvablePlaceholders爲是否忽略不可解析的 Placeholder,
     如配置了多個PropertyPlaceholderConfigurer,則需設置爲true
     <bean id="propertyConfigurerForProject2" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="order" value="2" />
    <property name="ignoreUnresolvablePlaceholders" value="true" />
    <property name="locations">
      <list>
        <value>classpath:/spring/include/jdbc-parms.properties</value>
        <value>classpath:/spring/include/base-config.properties</value>
      </list>
    </property>
    </bean>-->
    <bean id="propertyConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="ignoreUnresolvablePlaceholders" value="true"/>
        <property name="location" value="classpath:/application.properties"/>
    </bean>
    <!--註解探測器,在xml配置了這個標籤後,spring能夠自動去掃描base-pack下面或者子包下面的java文件,
    若是掃描到有@Component @Controller@Service等這些註解的類,則把這些類註冊爲bean
    注意:若是配置了<context:component-scan>那麼<context:annotation-config/>標籤就能夠不用再xml中配置了,由於前者包含了後者。
    另外<context:annotation-config/>還提供了兩個子標籤
    1. <context:include-filter> 2.<context:exclude-filter>
    <context:component-scan>有一個use-default-filters屬性,改屬性默認爲true,這就意味着會掃描指定包下的所有的標有@Component的類,
    並註冊成bean.也就是@Component的子註解@Service,@Reposity等。因此若是僅僅是在配置文件中這麼寫
    <context:component-scan base-package="com.test.myapp.web"/>
     Use-default-filter此時爲true,那麼會對base-package包或者子包下的jun全部的進行java類進行掃描,並把匹配的java類註冊成bean。
    能夠發現這種掃描的粒度有點太大,若是你只想掃描指定包下面的Controller,該怎麼辦?此時子標籤<context:incluce-filter>就起到了勇武之地。以下所示
    <context:component-scan base-package="com.test.myapp.web.Controller">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    這樣就會只掃描base-package指定下的有@Controller下的java類,並註冊成bean.
    可是由於use-dafault-filter在上面並無指定,默認就爲true,因此當把上面的配置改爲以下所示的時候,就會產生與你指望相悖的結果(注意base-package包值得變化)
    <context:component-scan base-package="com.test.myapp.web ">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    此時,spring不只掃描了@Controller,還掃描了指定包所在的子包service包下註解@Service的java類
    此時指定的include-filter沒有起到做用,只要把use-default-filter設置成false就能夠了。這樣就能夠避免在base-packeage配置多個包名這種不是很優雅的方法來解決這個問題了。
    另外在我參與的項目中能夠發如今base-package指定的包中有的子包是不含有註解了,因此不用掃描,此時能夠指定<context:exclude-filter>來進行過濾,說明此包不須要被掃描。綜合以上說明
    Use-dafault-filters=」false」的狀況下:<context:exclude-filter>指定的不掃描,<context:include-filter>指定的掃描-->
    <context:component-scan base-package="com.test.myapp">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 視圖解析器,根據視圖的名稱new ModelAndView(name),在配置文件查找對應的bean配置
     這個視圖解析器跟XmlViewResolver有點相似,也是經過把返回的邏輯視圖名稱去匹配定義好的視圖bean對象。
     不一樣點有二,一是BeanNameViewResolver要求視圖bean對象都定義在Spring的application context中,
     而XmlViewResolver是在指定的配置文件中尋找視圖bean對象,二是BeanNameViewResolver不會進行視圖緩存。
     若是沒有設置viewResolver,spring使用InternalResourceViewResolver進行解析。
     Spring實現ViewResolver的非抽象類且咱們常用的viewResolver有如下四種:
     一、InternalResourceViewResolver  將邏輯視圖名字解析爲一個路徑
     二、BeanNameViewResolver  將邏輯視圖名字解析爲bean的Name屬性,從而根據name屬性,找定義View的bean
     三、ResourceBundleResolver   和BeanNameViewResolver同樣,只不過定義的view-bean都在一個properties文件中,用這個類進行加載這個properties文件
     四、XmlViewResolver  和ResourceBundleResolver同樣,只不過定義的view-bean在一個xml文件中,用這個類來加載xml文件
     DispatcherServlet會加載全部的viewResolver到一個list中,並按照優先級進行解析。
     咱們不想只使用一種視圖解析器的話,能夠在[spring-dispatcher-name]-servlet.xml定義多個viewResolver:
     注意order中的值越小,優先級越高。而id爲viewResolver 的viewResolver的優先級是最低的。
     -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="1"/>
    </bean>
    <!--<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">-->
        <!--<property name="prefix" value="/WEB-INF/"/>-->
        <!--<property name="suffix" value=".html"/>-->
    <!--</bean>-->
    <!--基於json格式的mvc交互-->
    <bean name="jsonView" class="com.test.myapp.MappingFastJsonJsonView">
        <property name="contentType" value="application/json;charset=UTF-8"/>
    </bean>

    <!-- spring mvc +servlet3.0上傳文件配置,文件上傳插件uploadify的應用
     1)  在Web.xml的配置
     須要在web.xml添加multipart-config,以下所示
     <servlet>
        <servlet-name>AcrWeb</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
        <multipart-config>
            <max-file-size>52428800</max-file-size>
            <max-request-size>52428800</max-request-size>
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>
    </servlet>
    2)  在spring的application.xml(名字不必定是application)的配置,須要在該配置文件下添加一個以下的bean
       spring mvc +servlet3.0上傳文件配置
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
    </bean>
    3)  在jsp頁面中須要引入一些相關的該插件的包
     <script src="<c:url value="/asset/admin/js/uploadify/jquery.uploadify.min.js"/>"></script>
    4)   定義一個選擇文件的input框
     <div class="box-body">
       <span class="label input g1">上傳apk</span>
       <input id="apk_upload" name="apk_upload" type="file"/>
       <input id="apkUrl" type="hidden" name="apkUrl"/>
     </div>
    5)  Input file與插件進行綁定
    $("#apk_upload").uploadify({
            swf: "<c:url value='/asset/admin/js/uploadify/uploadify.swf'/>",
            //cancelImg    : "<c:url value='/asset/admin/js/uploadify/uploadify-cancel.png'/>",
            uploader: "/acr/admin/app/apkupload",
            fileObjName: "file",//對應着文件輸入框
            width:300,
            buttonText: '<img src="/acr/asset/admin/js/uploadify/upload.png" />',
            // onInit: function () { $(".uploadify-queue").hide();  },
            //removeCompleted : false,
            onUploadSuccess : function(file, data, response) {
                $("#apkUrl").val(data);
            },
            onUploadError : function(file, errorCode, errorMsg, errorString) {
                alert('文件 ' + file.name + ' 上傳失敗: ' + errorString);
            }
        });
         注意:該插件的uploadify.swf文件時放入到項目的某一個文件下面
         Uploader的值對應的是url,該值映射到了springmvc的一個方法,該方法是文件上傳的核心,
         負責把文件寫到指定位置的地方去。
         6)  Spring 後臺代碼的實現
         @RequestMapping(value = "/apkupload", method=RequestMethod.POST)
    public @ResponseBody String apkUpload(
            @RequestParam MultipartFile file,
            Model model,
            HttpServletRequest request) throws IOException {
        InputStream input = null;
        OutputStream output = null;
        String root = "H:/file";
        //生成了文件名字
        String filename = file.getOriginalFilename();
        //文件要上傳的位置
        String fileFullName = buildUpPath(root, filename);
        try {
            File dir = new File(root);
            if(!dir.exists()){
                dir.mkdirs();
            }
            input = file.getInputStream();
            output = new FileOutputStream(new File(fileFullName));
            //保存文件
            IOUtils.copy(input, output);
        } catch (Throwable e) {
            throw e;
        }finally{
            IOUtils.closeQuietly(input);
            IOUtils.closeQuietly(output);
        }
        return root+"/"+filename;
    }
       其中filename對應着步驟5的onUploadSuccess中的data
    -->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
    </bean>
相關文章
相關標籤/搜索