Spring MVC基於模型-視圖-控制器(Model-View-Controller,MVC)模式實現,可以構建像Spring框架那樣靈活和鬆耦合的Web應用程序html
在請求離開瀏覽器時①,會帶有用戶所請求內容的信息,至少會包含請求的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
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上下文之中
在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配置 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{}
<?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>