Spring MVC是Spring框架中用於Web應用開發的一個模塊。Spring MVC的MVC是Model-View-Controller的縮寫。它是一個普遍應用於圖像化用戶交互開發中的設計模式,不只常見於Web開發,也普遍應用於如Swing和JavaFX等桌面開發。html
Spring MVC基於Spring框架、Servlet和JSP(JavaServer Page),在掌握這3門技術的基礎上學習Spring MVC將很是容易。java
Spring框架是一個開源的企業應用開發框架,做爲一個輕量級的解決方案,它包含20多個不一樣的解決方法。咱們主要關注Core、Spring Bean、Spring MVC和Spring MVC Test模塊。git
Spring框架下載官網:http://spring.io/projects,具體能夠參考博客Spring框架下載方法。程序員
也能夠直接下載:URL爲最Spring最新5.1.6版本地址,獲取其餘版本只需修改下面連接的5.1.6的版本號信息成想要的版本便可:https://repo.spring.io/webapp/#/artifacts/browse/tree/General/libs-release-local/org/springframework/spring/5.1.6.RELEASE。github
將下載好的zip解壓到任意目錄,在解壓的目錄中,包含相應的文檔和Java源代碼,其中libs文件下爲基於Spring框架開發應用所須要的jar文件。web
關於spring的spring-framework-3.0.2.RELEASE-dependencies.zip包的下載:http://s3.amazonaws.com/dist.springframework.org/release/SPR/spring-framework-3.0.2.RELEASE-dependencies.zipspring
Spring框架是一個開源項目,若是你想要還沒有發佈的最新版本的Spring,可使用在github上下載源代碼:https://github.com/spring-projects/spring-frameworkexpress
Spring框架有兩個重要的概念:控制翻轉、依賴注入。 apache
Ioc—Inversion of Control,即「控制反轉」,不是什麼技術,而是一種設計思想。在Java開發中,Ioc意味着將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。如何理解好Ioc呢?理解好Ioc的關鍵是要明確「誰控制誰,控制什麼,爲什麼是反轉(有反轉就應該有正轉了),哪些方面反轉了」,那咱們來深刻分析一下:編程
用圖例說明一下,傳統程序設計如圖,都是主動去建立相關對象而後再組合起來:
當有了IoC/DI的容器後,在客戶端類中再也不主動去建立這些對象了:
IoC不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導咱們如何設計出鬆耦合、更優良的程序。傳統應用程序都是由咱們在類內部主動建立依賴對象,從而致使類與類之間高耦合,難於測試;有了IoC容器後,把建立和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,因此對象與對象之間是鬆散耦合,這樣也方便測試,利於功能複用,更重要的是使得程序的整個體系結構變得很是靈活。
其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了「主從換位」的變化。應用程序本來是老大,要獲取什麼資源都是主動出擊,可是在IoC/DI思想中,應用程序就變成被動的了,被動的等待IoC容器來建立並注入它所須要的資源了。
IoC很好的體現了面向對象設計法則之一—— 好萊塢法則:「別找咱們,咱們找你」;即由IoC容器幫對象找相應的依賴對象並注入,而不是由對象主動去找。
DI—Dependency Injection,即「依賴注入」:是組件之間依賴關係由容器在運行期決定,形象的說,即由容器動態的將某個依賴關係注入到組件之中。依賴注入的目的並不是爲軟件系統帶來更多功能,而是爲了提高組件重用的頻率,併爲系統搭建一個靈活、可擴展的平臺。經過依賴注入機制,咱們只須要經過簡單的配置,而無需任何代碼就可指定目標須要的資源,完成自身的業務邏輯,而不須要關心具體的資源來自何處,由誰實現。
理解DI的關鍵是:「誰依賴誰,爲何須要依賴,誰注入誰,注入了什麼」,那咱們來深刻分析一下:
IoC和DI由什麼關係呢?其實它們是同一個概念的不一樣角度描述,因爲控制反轉概念比較含糊(可能只是理解爲容器控制對象這一個層面,很難讓人想到誰來維護對象關係),因此2004年大師級人物Martin Fowler又給出了一個新的名字:「依賴注入」,相對IoC 而言,「依賴注入」明確描述了「被注入對象依賴IoC容器配置依賴對象」。
注:若是想要更加深刻的瞭解IoC和DI,請參考大師級人物Martin Fowler的一篇經典文章《Inversion of Control Containers and the Dependency Injection pattern》,原文地址:http://www.martinfowler.com/articles/injection.html。
有兩個組件A和B,A依賴於B。假設A是一個類,且A有一個方法importantMethod用到了B,以下:
class B{ public void usefulMethod() {} } public class A{ public void importantMethod() { //get an instance of B B b = new B(); b.usefulMethod(); } }
要使用B,類A必須先得到組件B的實例引用。若B是一個具體類,則可經過new 關鍵字之間建立組件B的實例。可是,若是B是接口,且有多個實現,則問題就變得複雜了。咱們當然能夠任意選擇接口B的一個實現類,可是這意味着A的可重用性大大下降了,由於沒法採用B的其餘實現。
依賴注入是這樣處理此類情景的:接管對象的建立工做,並將該對象的引用注入到須要該對象的組件中。以上述狀況爲例,依賴注入框架會分別建立對象A和對象B,而後將對象B注入到對象A中。
爲了能讓框架進行依賴注入,程序員須要編寫特定的set方法或者構造方法。例如,爲了能將B注入到A中,類A會被修改爲以下形式:
public class A{ private B b; public void importantMethod() { b.usefulMethod(); } public void setB(B b) { this.b = b; } }
修改後的類A新增了一個set方法,該方法將會被框架調用,以注入B的一個實例。因爲對象依賴由依賴注入,類A的importantMethod()方法再也不須要在調用B的usefulMethod()方法前去建立B的一個實例。
固然,也能夠採用構造器方式注入,以下所示:
public class A{ private B b; public A(B b) { this.b = b; } public void importantMethod() { b.usefulMethod(); } }
本例中,Spring會先建立B的實例,再建立A的實例,而後把B注入到實例中。
注:Spring管理的對象稱爲beans。
經過提供一個Ioc容器(或者說DI容器),Spring爲咱們提供一種能夠「聰明」的管理Java對象依賴關係的方法。其優雅之處在於,程序員無需瞭解Spring框架的存在,更不須要引入任何Spring類型。
使用Spring,程序幾乎將全部重要對象的建立工做移交給Spring,並配置如何注入依賴。Spring支持XML或註解兩種配置方式。此外,還須要建立一個ApplicationContext對象,表明一個Spring IoC容器,org.springframework.context.ApplicationContext接口有不少實現類,包括ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。這兩個實現都須要至少一個包含beans信息的XML文件。
/* * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context; import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.lang.Nullable; /** * Central interface to provide configuration for an application. * This is read-only while the application is running, but may be * reloaded if the implementation supports this. * * <p>An ApplicationContext provides: * <ul> * <li>Bean factory methods for accessing application components. * Inherited from {@link org.springframework.beans.factory.ListableBeanFactory}. * <li>The ability to load file resources in a generic fashion. * Inherited from the {@link org.springframework.core.io.ResourceLoader} interface. * <li>The ability to publish events to registered listeners. * Inherited from the {@link ApplicationEventPublisher} interface. * <li>The ability to resolve messages, supporting internationalization. * Inherited from the {@link MessageSource} interface. * <li>Inheritance from a parent context. Definitions in a descendant context * will always take priority. This means, for example, that a single parent * context can be used by an entire web application, while each servlet has * its own child context that is independent of that of any other servlet. * </ul> * * <p>In addition to standard {@link org.springframework.beans.factory.BeanFactory} * lifecycle capabilities, ApplicationContext implementations detect and invoke * {@link ApplicationContextAware} beans as well as {@link ResourceLoaderAware}, * {@link ApplicationEventPublisherAware} and {@link MessageSourceAware} beans. * * @author Rod Johnson * @author Juergen Hoeller * @see ConfigurableApplicationContext * @see org.springframework.beans.factory.BeanFactory * @see org.springframework.core.io.ResourceLoader */ public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { /** * Return the unique id of this application context. * @return the unique id of the context, or {@code null} if none */ @Nullable String getId(); /** * Return a name for the deployed application that this context belongs to. * @return a name for the deployed application, or the empty String by default */ String getApplicationName(); /** * Return a friendly name for this context. * @return a display name for this context (never {@code null}) */ String getDisplayName(); /** * Return the timestamp when this context was first loaded. * @return the timestamp (ms) when this context was first loaded */ long getStartupDate(); /** * Return the parent context, or {@code null} if there is no parent * and this is the root of the context hierarchy. * @return the parent context, or {@code null} if there is no parent */ @Nullable ApplicationContext getParent(); /** * Expose AutowireCapableBeanFactory functionality for this context. * <p>This is not typically used by application code, except for the purpose of * initializing bean instances that live outside of the application context, * applying the Spring bean lifecycle (fully or partly) to them. * <p>Alternatively, the internal BeanFactory exposed by the * {@link ConfigurableApplicationContext} interface offers access to the * {@link AutowireCapableBeanFactory} interface too. The present method mainly * serves as a convenient, specific facility on the ApplicationContext interface. * <p><b>NOTE: As of 4.2, this method will consistently throw IllegalStateException * after the application context has been closed.</b> In current Spring Framework * versions, only refreshable application contexts behave that way; as of 4.2, * all application context implementations will be required to comply. * @return the AutowireCapableBeanFactory for this context * @throws IllegalStateException if the context does not support the * {@link AutowireCapableBeanFactory} interface, or does not hold an * autowire-capable bean factory yet (e.g. if {@code refresh()} has * never been called), or if the context has been closed already * @see ConfigurableApplicationContext#refresh() * @see ConfigurableApplicationContext#getBeanFactory() */ AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException; }
下面是從類加載路徑中加載config1.xml和config2.xml的ApplicationContext建立的一個代碼示例:
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"config1.xml","config2.xml"});
能夠經過調用ApplicationContext的getBean()方法從IoC容器中得到對象:
Product product = context.getBean("product",Product.class);
genBean()方法會在xml配置文件中查詢name(或id)爲product且類型爲Product的bean對象。
注:理想狀況下,咱們只需在測試代碼中建立一個ApplicationContext,應用程序自己無需處理。對於Spring MVC應用,能夠經過一個Spring Servlet來處理ApplicationContext,而無需直接處理。
從1.0版本開始,Spring就支持基於XML的配置;從2.5版本開始,增長了經過註解的配置文件。下面介紹如何配置XML文件,配置文件的根元素一般爲beans:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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.xsd "> ... </beans>
若是須要更強的Spring配置能力,能夠在schema location屬性中添加相應的schema,也能夠指定schema版本:http://www.springframework.org/schema/beans/spring-beans-5.1.xsd,不過推薦使用默認的schma,以便升級spring庫時無需修改配置文件。
配置文件既能夠是一份,也能夠分解爲多份,以支持模塊化配置。ApplicationContext的實現類支持讀取多份配置文件。另外一種選擇是,經過一份主配置文件,將該文件導入到其餘配置文件。
下面是導入其餘配置文件的一個示例:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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.xsd "> <import resource="config1.xml"/> <import resource="module2/config2.xml"/> <import resource="/resources/config3.xml"/> ... </beans>
bean元素的配置在後面將會詳細介紹。
本節將介紹Spring如何管理bean。
前面已經介紹,經過調用ApplicationContext的getBean()方法能夠獲取一個bean的實例。
下面咱們將建立一個名爲spring-intro的Java Project項目,而後咱們須要導入5個Spring Jar包:
spring-beans-5.1.6.RELEASE.jar
spring-context-5.1.6.RELEASE.jar
spring-core-5.1.6.RELEASE.jar
spring-expression-5.1.6.RELEASE.jar
到當前項目中,右鍵當前項目JRE System Library——>Build Path——>Configure Build Path——>Add External JARs把這四個包導入;
com.springsource.org.apache.commons.logging-1.1.1.jar
到當前項目中,右鍵當前項目JRE System Library——>Build Path——>Configure Build Path——>Add External JARs把這一個包導入;
若是是Dynamic Java Web項目,直接將這5個Jar包導入到WebContent/WEB-INF/lib便可。
下面爲代碼的編寫,咱們先建立一個Product類,位於包springintro.bean中:
package springintro.bean; import java.io.Serializable; public class Product implements Serializable { private static final long serialVersionUID = 748392348L; private String name; private String description; private float price; public Product() { } public Product(String name, String description, float price) { this.name = name; this.description = description; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } }
下面建立一個名爲spring-config.xml的配置文件,其中定義了一個名爲product的bean,該配置文件位於src文件夾下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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.xsd "> <bean name="product" class="springintro.bean.Product"/> </beans>
該bean的定義告訴Spring,經過默認無參的構造器來初始化Product類。若是不存在該構造器則會拋出一個異常。此外,該無參數的構造器並不要求是public簽名。
注意:應採用id或者name屬性標識一個bean。爲了讓Spring建立一個Product實例,應將bean定義的name值"product"和Product類型做爲參數傳給ApplicationContext的getBean()方法。
在包springintro下建立Main.java文件:
package springintro; import java.time.LocalDate; import java.util.Calendar; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import springintro.bean.Employee; import springintro.bean.Product; public class Main { public static void main(String[] args) { //建立IoC容器對象 從類路徑下(/src)加載xml配置文件 容器啓動時就會建立容器中配置的全部對象 ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"spring-config.xml"}); Product product1 = context.getBean("product", Product.class); product1.setName("Excellent snake oil"); System.out.println("product1: " + product1.getName()); } }
輸出以下:
product1: Excellent snake oil
大部分類能夠經過構造器來實例化。然而,Spring還一樣支持經過調用一個工廠的靜態方法來初始化類。
下面的bean定義展現了經過靜態工廠方法來實例化Java.time.LocalData,調用java.time.LocalDate的靜態方法now()建立LocalDate 對象:
<bean id="localDate" class="java.time.LocalDate" factory-method="now"/>
本例中採用了id屬性而非name屬性來標識bean,採用了getBean()方法來獲取LocalData實例:
LocalDate localDate = context.getBean("localDate", java.time.LocalDate.class); System.out.println("today:" + localDate);
輸出以下:
today:2019-04-29
有時,咱們但願一些對象在被銷燬前能執行一些方法。Spring考慮到這樣的需求,能夠在bean定義中配置destroy-method屬性,來指定在銷燬前要執行的方法。
下面的例子中,咱們配置Spring經過java.util.concurrent.Exceutors的靜態方法newCachedThreadPool()來建立一個java.util.concurrent.ExceutorService實例,並指定了destroy-method屬性值爲shutdown()方法。這樣,Spring會在銷燬ExceutorService實例前調用shutdown()方法:
<bean id="executorService" class="java.util.concurrent.Executors" factory-method="newCachedThreadPool" destroy-method="shutdown"> </bean>
ExecutorService executorService = context.getBean("executorService", ExecutorService.class); //強制關閉IoC容器,在容器關閉以前會銷燬容器中全部對象 ((ClassPathXmlApplicationContext)context).close();
在程序中咱們強制關閉IoC容器,這樣就會銷燬ExceutorService實例,從而會觸發executorService.shutdown()方法的執行。
與銷燬方法相對應的還有一個初始化方法,會在對象實例建立以後調用,能夠在bean定義中配置init-method屬性,來指定初始化要執行的方法:
<bean id="executorService" class="java.util.concurrent.Executors" factory-method="newCachedThreadPool" init-method="shutdown"> </bean>
prototype:多例,被標識爲多例的對象,每次在獲取時纔會建立,每次建立都是新的對象,整合struct2時,ActionBean必須配置爲多例的;
以下案例:
<bean name="product" class="springintro.bean.Product" scope="singleton"/>
本節將詳細介紹Spring的依賴注入方式。
前面已經介紹了使用無參構造函數來初始化類,此外,Spring支持經過帶參數的構造器來初始化類。
咱們仍然以Product類爲例,如下的定義展現瞭如何經過Product類構造函數的參數名傳遞參數:
<bean name="featuredProduct" class="springintro.bean.Product"> <constructor-arg name="name" value="Ultimate Olive Oil"/> <constructor-arg name="description" value="The purest olive oil on the market"/> <constructor-arg name="price" value="9.95"/> </bean>
這樣,在IoC容器建立Product實例時,Spring會調用以下構造器:
Product featuredProduct = context.getBean("featuredProduct", Product.class); System.out.println(featuredProduct.getName() + ", " + featuredProduct.getDescription() + ", " + featuredProduct.getPrice());
public Product(String name, String description, float price) { this.name = name; this.description = description; this.price = price; }
輸出以下:
Ultimate Olive Oil, The purest olive oil on the market, 9.95
除了經過參數名稱傳遞參數外,Spring還支持經過指數方式來傳遞參數,具體以下:
<bean name="featuredProduct2" class="springintro.bean.Product"> <constructor-arg index="0" value="Ultimate Olive Oil"/> <constructor-arg index="1" value="The purest olive oil on the market"/> <constructor-arg index="2" value="9.95"/> </bean>
上面index="0",表示第一個參數傳入"Ultimate Olive Oil",同理...
可是若是還存在一個構造函數以下:
public Product(String name, String description, String price) { this.name = name; this.description = description; this.price = Float.parseFloat(price); }
咱們會發現這個參數名和以前的同樣,而且順序也同樣,此時爲了區分構造函數,咱們還須要指定type參數:
<bean name="featuredProduct3" class="springintro.bean.Product"> <constructor-arg name="name" value="Ultimate Olive Oil"/> <constructor-arg name="description" value="The purest olive oil on the market"/> <constructor-arg name="price" value="9.95" type="java.lang.String" index="2"/> </bean>
若是構造函數的參數不是指類型,而是引用類型,則須要將value更改成ref。
下面以Employee類和Address類爲例,介紹setter方式依賴注入。
Employee類代碼以下:
package springintro.bean; public class Employee { private String firstName; private String lastName; private Address homeAddress; public Employee() { } public Employee(String firstName, String lastName, Address homeAddress) { this.firstName = firstName; this.lastName = lastName; this.homeAddress = homeAddress; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Address getHomeAddress() { return homeAddress; } public void setHomeAddress(Address homeAddress) { this.homeAddress = homeAddress; } @Override public String toString() { return firstName + " " + lastName + "\n" + homeAddress; } }
Address類代碼以下:
package springintro.bean; public class Address { private String line1; private String line2; private String city; private String state; private String zipCode; private String country; public Address(String line1, String line2, String city, String state, String zipCode, String country) { this.line1 = line1; this.line2 = line2; this.city = city; this.state = state; this.zipCode = zipCode; this.country = country; } public String getLine1() { return line1; } public void setLine1(String line1) { this.line1 = line1; } public String getLine2() { return line2; } public void setLine2(String line2) { this.line2 = line2; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getState() { return state; } public void setState(String state) { this.state = state; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } @Override public String toString() { return line1 + "\n" + line2 + "\n" + city + "\n" + state + " " + zipCode + "\n" + country; } }
Employee類依賴於Address類,能夠經過以下配置來保證每一個Employee實例都能包含Address實例:
<bean name="simpleAddress" class="springintro.bean.Address"> <constructor-arg name="line1" value="151 Corner Street"/> <constructor-arg name="line2" value=""/> <constructor-arg name="city" value="Albany"/> <constructor-arg name="state" value="NY"/> <constructor-arg name="zipCode" value="99999"/> <constructor-arg name="country" value="US"/> </bean>
simpleAddress對象是Address類的一個實例,它經過構造器方式實例化。
<bean name="employee1" class="springintro.bean.Employee"> <property name="homeAddress" ref="simpleAddress"/> <property name="firstName" value="Junior"/> <property name="lastName" value="Moore"/> </bean>
employee1對象則經過配置property元素來調用setter方法以設置字段值。須要注意的是,homeAddress屬性配置的是simpleAddress對象的引用。
被引用對象的配置定義無需早於引用其對象的定義,在本例中,employee1對象能夠出如今simpleAddress對象定義以前。
這樣,在IoC容器建立employee1實例時,Spring會調用默認構造器,並經過setter方法設置值:
Employee employee1 = context.getBean("employee1", Employee.class); System.out.println(employee1.getFirstName() + " " + employee1.getLastName()); System.out.println(employee1.getHomeAddress());
輸出以下:
Junior Moore 151 Corner Street Albany NY 99999 US
咱們還能夠經過調用有參構造器建立Employee實例,並將Address對象經過構造器注入:
<bean name="employee2" class="springintro.bean.Employee"> <constructor-arg name="firstName" value="Senior"/> <constructor-arg name="lastName" value="Moore"/> <constructor-arg name="homeAddress" ref="simpleAddress"/> </bean>
這樣,在IoC容器建立employee2實例時,Spring會調用有參的構造器,具體是哪一個構造器,由設置的constructor-arg肯定:
Employee employee2 = context.getBean("employee2", Employee.class); System.out.println(employee2.getFirstName() + " " + employee2.getLastName()); System.out.println(employee2.getHomeAddress());
輸出以下:
Senior Moore 151 Corner Street Albany NY 99999 US
p名稱空間方式依賴注入和spel方式依賴注入使用很少,所以不作介紹,有興趣能夠參考其餘博客。
若是類中的字段存在複雜類型,如數組,集合(List,Set,Map),Properties,這時依賴注入的配置文件將會有一些小的變化,具體能夠參考博客:Spring依賴注入之數組,集合(List,Set,Map),Properties的注入。
注:本文所使用的程序來自控制反轉和依賴注入的理解(通俗易懂),代碼下載地址:https://github.com/pauldeck/springmvc-2ed。
參考文獻
[1]Spring框架下載方法
[2]Spring MVC學習指南
[4]Spring MVC(推薦)
[5]Spring裝配Bean---使用xml配置(推薦)