本文篇幅較長,建議合理利用右上角目錄進行查看(若是沒有目錄請刷新)。前端
本文是對《SPRING實戰第4版》的總結,你們也能夠去仔細研讀該書java
爲了簡化Java開發,Spring使用瞭如下幾個關鍵策略web
一、基於POJO的輕量級和最小侵入編程正則表達式
二、基於依賴注入和麪向接口實現鬆耦合redis
在傳統編程中,不免會有如下這種編程情景(多個類交互)spring
public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() { this.quest = new RescueDamselQuest(); } public void embarkOnQuest() { quest.embark(); } }
DamselRescuingKnight中,經過new 建立了一個RescueDamselQuest實例,2個類造成了緊耦合。sql
其中的依賴關係是DamselRescuingKnight依賴RescueDamselQuest,並且限制了embarkOnQuest方法的實現數據庫
並且,沒法對DamselRescuingKnight進行單元測試,由於embarkOnQuest方法須要調用RescueDamselQuest的embark方法,而僅在這個方法內,並無這個方法的實現編程
耦合的兩面性:必須的(複雜邏輯必然有多個類進行交互)、過於耦合將致使難以測試、複用、難以理解json
依賴注入:將對象的依賴關係交給第三方來進行建立和管理
public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { this.quest = quest; } public void embarkOnQuest() { quest.embark(); } }
上面使用了依賴注入的一種方式:構造器注入;並且傳入的是一個Quest接口,對象和依賴對象的具體實現沒有耦合關係,就造成了鬆耦合。
並且,此狀況下,可使用mock(一種測試方式,具體自行學習)實現單元測試。
Spring能夠做爲一個依賴注入容器,經過不一樣方式注入,也有相應的配置,裝配策略,留到後面講解。
三、基於切面和慣例進行聲明式編程
DI負責讓互相協做的組件實現鬆耦合,而AOP則容許把遍及程序各處的功能分離出來造成可重用組件。
能夠把切面想象成不少組件上的一個外殼,藉助AOP,可使用各類功能層包裹核心業務層,以聲明的方式靈活應用到系統中,核心應用中與這些功能層沒有耦合,保證了POJO的簡單性
例如,你的代碼中可能會出現如下情景
public class BraveKnight implements Knight { private Quest quest; private Minstrel minstrel; public BraveKnight(Quest quest, Minstrel minstrel) { this.quest = quest; this.minstrel = minstrel; } public void embarkOnQuest() { minstrel.singBeforeQuest(); quest.embark(); minstrel.singAfterQuest(); } }
minstrel類能夠看做一個日誌功能,在某個其它類中,須要調用這個日誌類來記錄日誌,致使這個功能代碼與業務代碼混淆在一塊兒
而Spring提供的AOP方案,能夠經過配置文件等方式,將這個日誌類的相關代碼從這個業務類中去除,從而實現解耦;具體方式後面介紹
四、經過切面和模板減小樣板式代碼
例如:以往使用JDBC進行操做數據庫時,每次操做,都有不少鏈接數據庫,斷開鏈接等代碼和業務代碼交織在一齊
而Spring則提供瞭如jdbcTemplate等類,對這些樣板式代碼進行簡化
Spring框架中對象是由Spring建立和管理的,本節討論Spring如何管理這些Bean
Spring提供一個或多個Spring容器,Spring容器負責建立、裝配、配置、管理對象的整個生命週期
Spring自帶多種應用上下文:
建立Spring容器例子:ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/knight.xml");
經過context.getBean()方法便可獲取容器內的Bean
普通Java應用程序中,bean的生命週期很簡單,就是經過new實例化,就可使用了,當bean再也不被使用,就會被自動回收。
而Spring中的bean的生命週期就很複雜,並且很是重要,由於有時候對這個生命週期的擴展點進行自定義
由前面的內容知道,Spring框架致力於經過DI、AOP和消除模板式代碼來簡化企業級JAVA開發
而在整個Spring框架範疇內,Spring不只提供了多種簡化開發的方式,還構建了一個在覈心框架上的龐大生態圈,將Spring擴展到不一樣領域,如Web服務、REST、移動開發,NoSQL等
如Spring4.0中,Spring框架發佈版本中包含20個不一樣模塊,每一個模塊有3個JAR文件(二進制類庫、源碼、JavaDoc)
Spring發佈版本中lib目錄下的JAR文件
Spring Portfolio包含構建於核心Spring框架之上的框架和類庫,歸納地說,Spring Portfolio爲每個領域的Java開發都提供了Spring編程模型
最佳實踐:建議是儘量使用自動配置,減小顯式配置;當須要顯式配置,推薦使用類型安全的JavaConfig;最後再使用XML配置
標明這個類是一個組建類,告知Spring要爲這個類建立bean
也能夠用@Named註解(Java依賴規範/Java Dependency Injection 提供)代替,但通常不推薦
public interface CompactDisc { void play(); }
@Component public class SgtPeppers implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band"; private String artist = "The Beatles"; public void play() { System.out.println("Playing " + title + " by " + artist); } }
能夠爲bean命名id(默認自動生成id,是類名首字母小寫)
@Component("myName")
Spring組件掃描默認是關閉的,能夠經過Java設置或者是XML設置來開啓
會搜索配置類所在的包以及子包
Java配置形式:
@Configuration @ComponentScan public class CDPlayerConfig { }
XML配置形式:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:c="http://www.springframework.org/schema/c" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="soundsystem" /> </beans>
指定組件掃描的基礎包,能夠經過類名,或者包中的類或接口:
@ComponentScan("soundsystem") @ComponentScan(basePackages = "soundsystem") @ComponentScan(basePackages = { "soundsystem", "video" }) @ComponentScan(basePackages = { CDPlayer.class, MediaPlayer.class })
推薦在包中建立標識接口,這樣在重構系統的時候,不會對這個配置形成影響
能夠用在類中任何方法內(包括構造方法、setter)
這樣,調用這個方法的時候,Spring就會去查找適合方法參數的bean並裝配到方法中
若是找不到,或者有多個符合,則會報錯
能夠經過@Autowired(required=false)使找不到時不報錯,那麼傳入的參數值爲null,須要本身手動處理代碼
可使用@Inject(Java依賴規範/Java Dependency Injection 提供)代替,但通常不推薦
public class CDPlayer implements MediaPlayer { private CompactDisc cd; @Autowired public CDPlayer(CompactDisc cd) { this.cd = cd; } public void play() { cd.play(); } }
例如,須要將第三方庫的組件加載到你的應用中,此時沒法給他的類上添加@Component和@Autowired註解,此時不能使用自動化裝配了。
這種狀況下,就必須使用顯式裝配的形式,能夠選擇Java代碼裝配或Xml裝配
建議:顯式配置是優先使用JavaConfig裝配,由於他強大、類型安全且對重構友好;由於他和業務代碼無關,應放到單獨的包中
告訴Spring,這是一個Spring配置類,用來配置Spring應用上下文如何配置bean
建立一個方法,用來產生類的實例,並告訴Spring,這個實例要註冊爲Spring應用上下文中的bean
@Configuration public class CDPlayerConfig { @Bean public CompactDisc sgtPeppers() { return new SgtPeppers(); } @Bean public CDPlayer cdPlayer1() { return new CDPlayer(sgtPeppers()); } @Bean public CDPlayer cdPlayer2(CompactDisc compactDisc) { return new CDPlayer(compactDisc); } }
Spring剛出現時,用XML描述配置是主要方式;如今有了強大的自動化配置和Java配置,XML配置再也不是首選;可是以往的項目仍然存在大量的XML配置,因此有必要掌握這種方式。
Java配置須要@Configuration註解,而XML配置的配置規範以下
<?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 http://www.springframework.org/schema/context"> <!-- 配置內容 --> </beans>
用來聲明一個簡單的bean
spring檢測到這個設置,會調用該類的默認構造器來建立實例,並註冊爲bean
使用字符串做爲類名設置,會出現寫錯等問題;可使用有Spring感知的IDE來確保XML配置的正確性,如Spring Tool Suite
<bean class="soundsystem.BlankDisc" /> <bean id="compactDisc" class="soundsystem.BlankDisc" />
若是不設置id,會自動給予id:class+#0,#1,#2...
爲了減少XML文件的繁瑣性,建議只對須要按名字引用的bean添加id屬性
2種方式:使用<constructor-arg>元素、使用Spring3.0引入的c-命名空間
區別:<constructor-arg>元素冗長繁瑣;但有些事情<constructor-arg>元素能作到,c-命名空間作不到
使用<constructor-arg>元素:
引用bean注入:
<bean id="compactDisc" class="soundsystem.SgtPeppers" /> <bean id="cdPlayer" class="soundsystem.CDPlayer"> <constructor-arg ref="compactDisc" /> </bean>
用字面量注入:
<bean id="compactDisc" class="soundsystem.BlankDisc"> <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /> <constructor-arg value="The Beatles" /> </bean>
裝配集合:
傳入空值:
<bean id="compactDisc" class="soundsystem.collections.BlankDisc"> <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /> <constructor-arg value="The Beatles" /> <constructor-arg><null/></constructor-arg> </bean>
使用List傳入字面量:
<bean id="compactDisc" class="soundsystem.collections.BlankDisc"> <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /> <constructor-arg value="The Beatles" /> <constructor-arg> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> </list> </constructor-arg> </bean>
使用List傳入引用bean:
<bean id="compactDisc" class="soundsystem.collections.BlankDisc"> <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /> <constructor-arg value="The Beatles" /> <constructor-arg> <list> <ref bean="cd1"/> <ref bean="cd2"/> </list> </constructor-arg> </bean>
List能夠替換成Set,區別是所建立的是List仍是Set
使用c-命名空間:
聲明c-模式:
首先,使用這個命名控件要在beans標籤增長聲明此模式:xmlns:c="http://www.springframework.org/schema/c"
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context"> <!-- 配置內容 --> </beans>
引用bean注入:
<bean id="compactDisc" class="soundsystem.SgtPeppers" /> <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />
其中,cd是指對應構造方法的參數名,按照這種寫法,若是修改了構造方法,可能就會出錯
替代方案:
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc" /> <bean id="cdPlayer" class="soundsystem.CDPlayer" c:_-ref="compactDisc" />
_0,_1:使用參數順序來注入
_:若是隻有一個參數,能夠不用標示參數,用下劃線代替
用字面量注入:
<bean id="compactDisc" class="soundsystem.BlankDisc" c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles" />
裝配集合:
c-的方式沒法實現裝配集合
怎麼選擇構造器注入和屬性注入?建議對強依賴使用構造器注入,對可選依賴使用屬性注入。
假設有一個類CDPlayer:
public class CDPlayer implements MediaPlayer { private CompactDisc compactDisc; @Autowired public void setCompactDisc(CompactDisc compactDisc) { this.compactDisc = compactDisc; } public void play() { compactDisc.play(); } }
使用<property>元素:
引用bean注入:
<bean id="cdPlayer" class="soundsystem.properties.CDPlayer"> <property name="compactDisc" ref="compactDisc" /> </bean>
表明經過setCompactDisc方法把id爲compactDisc的bean注入到compactDisc屬性中
用字面量注入以及裝配集合:
<bean id="compactDisc" class="soundsystem.properties.BlankDisc"> <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" /> <property name="artist" value="The Beatles" /> <property name="tracks"> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> </list> </property> </bean>
聲明p-模式:
首先,使用這個命名控件要在beans標籤增長聲明此模式:xmlns:p="http://www.springframework.org/schema/p"
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context"> <!-- 配置內容 --> </beans>
而後使用p-命名空間來裝配屬性
引用bean注入:
<bean id="cdPlayer" class="soundsystem.properties.CDPlayer" p:compactDisc-ref="compactDisc" />
用字面量注入以及裝配集合:
<bean id="compactDisc" class="soundsystem.properties.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles"> <property name="tracks"> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> </list> </property> </bean>
聲明util-模式:
首先,使用這個命名控件要在beans標籤增長聲明此模式:xmlns:util="http://www.springframework.org/schema/util"和2個http
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context"> <!-- 配置內容 --> </beans>
使用util:list簡化:
<bean id="compactDisc" class="soundsystem.properties.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles" p:tracks-ref="trackList" /> <util:list id="trackList"> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> </util:list> <bean id="cdPlayer" class="soundsystem.properties.CDPlayer" p:compactDisc-ref="compactDisc" />
util-命名控件的所有元素:
其中一個JavaConfig:
@Configuration public class CDConfig { @Bean public CompactDisc compactDisc() { return new SgtPeppers(); } }
使用@Import註解引入另一個JavaConfig:
@Configuration @Import(CDConfig.class) public class CDPlayerConfig { @Bean public CDPlayer cdPlayer(CompactDisc compactDisc) { return new CDPlayer(compactDisc); } }
或使用一個更高級別的JavaConfig引用2個JavaConfig
@Configuration @Import({ CDPlayerConfig.class, CDConfig.class }) public class SoundSystemConfig { }
1中把CDPlayer和CompactDisc分開了,假設出於某些緣由,須要把CompactDisc用XML來配置
<bean id="compactDisc" class="soundsystem.BlankDisc" c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles"> <constructor-arg> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> <value>Lucy in the Sky with Diamonds</value> <value>Getting Better</value> <value>Fixing a Hole</value> <!-- ...other tracks omitted for brevity... --> </list> </constructor-arg> </bean>
JavaConfig引用XML配置
@Configuration @Import(CDPlayerConfig.class) @ImportResource("classpath:cd-config.xml") public class SoundSystemConfig { }
這樣,CDPlayer和BlankDisc都會做爲bean被加載到Spring容器中;而CDPlayer添加了@Bean註解,所需參數CompactDisc也會把BlanDisc加載進來
<import resource="cd-config.xml" /> <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />
<bean class="soundsystem.CDConfig" /> <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />
推薦不管使用JavaConfig仍是XML配置,都加入一個更高層次的配置文件,負責組合這些配置文件
<bean class="soundsystem.CDConfig" /> <import resource="cdplayer-config.xml" />
程序運行有多個環境,好比開發環境、測試環境、生產環境等。
在每一個環境中,不少東西有不一樣的作法,如使用的數據庫等。
爲了不在切換運行環境是須要對程序進行大量修改,Spring提供了profile設置,使程序能對不一樣環境作出對應的處理。
使用profile,可使一個部署單元(如war包)能夠用於不一樣的環境,內部的bean會根據環境所需而建立。
Java配置profile bean:
使用@Profile註解指定bean屬於哪一個profile
針對類,代表這個類下全部的bean都在對應profile激活時才建立:
@Configuration @Profile("dev") public class DataSourceConfig { @Bean(destroyMethod = "shutdown") public DataSource embeddedDataSource() { return new EmbeddedDatabaseBuilder().build(); } }
針對方法,代表只有註解的方法纔會根據profile來建立bean
@Configuration public class DataSourceConfig { @Bean(destroyMethod = "shutdown") @Profile("dev") public DataSource embeddedDataSource() { return new EmbeddedDatabaseBuilder().build(); } @Bean @Profile("prod") public DataSource jndiDataSource() { JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();return (DataSource) jndiObjectFactoryBean.getObject(); } }
XML配置profile:
設置整個XML文件屬於某個profile, 每個環境建立一個XML文件,把這些XML文件都放在部署環境中,根據環境自動調用
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" profile="dev"> <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:schema.sql" /> <jdbc:script location="classpath:test-data.sql" /> </jdbc:embedded-database> </beans>
也能夠經過嵌套<beans>元素,在同一個XML文件下建立不一樣profile bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans profile="dev"> <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:schema.sql" /> <jdbc:script location="classpath:test-data.sql" /> </jdbc:embedded-database> </beans> <beans profile="prod"> <jee:jndi-lookup id="dataSource" lazy-init="true" jndi-name="jdbc/myDatabase" resource-ref="true" proxy-interface="javax.sql.DataSource" /> </beans> </beans>
bean根據profile的建立步驟:
spring.profiles.active,spring.profiles.default2個參數的設置方式:
具體寫法,後面的例子會講到
使用@ActiveProfiles註解:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=DataSourceConfig.class) @ActiveProfiles("prod") public static class ProductionDataSourceTest { @Autowired private DataSource dataSource; @Test public void shouldBeEmbeddedDatasource() { // should be null, because there isn't a datasource configured in JNDI assertNull(dataSource); } }
@Conditional註解中給定一個類,這個類實現Condition接口,其中實現的matches方法的值表明改bean是否生成。
@Configuration public class MagicConfig { @Bean @Conditional(MagicExistsCondition.class) public MagicBean magicBean() { return new MagicBean(); } }
public class MagicExistsCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment env = context.getEnvironment(); return env.containsProperty("magic"); } }
ConditionContext接口:
AnnotatedTypeMetadata接口:
使用自動裝配時,若是有多個bean能匹配上,會產生錯誤
例如:
//某方法 @Autowired public void setDessert(Dessert dessert){ this.dessert = dessert; } //有3個類實現了該接口 @Component public class Cake implements Dessert{...} @Component public class Cookies implements Dessert{...} @Component public class IceCream implements Dessert{...}
Spring沒法從3者中作出選擇,拋出NoUniqueBeanDefinitionException異常。
//使用@Component配置bean時,可使用@Primary註解 @Component @Primary public class Cake implements Dessert{...} //使用@Bean配置bean時,可使用@Primary註解 @Bean @Primary public Dessert iceCream{ return new IceCream(); }
<!-- 使用XML配置時,設置primary屬性 --> <bean id="iceCream" class="com.desserteater.IceCream" primary="true" />
使用@Primary時,也會出現多個匹配的bean都標註了primary屬性,一樣會讓Spring出現沒法選擇的狀況,致使錯誤
一、使用默認限定符:
@Autowired @Qualifier("iceCream") public void setDessert(Dessert dessert){ this.dessert = dessert; }
@Qualifier註解設置的參數是要注入的bean的ID,若是沒有爲bean設定ID,則爲首字母小寫的類名(也稱這個bean的默認限定符)
二、爲bean設置自定義限定符:
@Component @Qualifier("soft") public class Cake implements Dessert{...} @Bean @Qualifier("cold") public Dessert iceCream{ return new IceCream(); }
在bean上使用@Qualifier註解,表示爲bean設置自定義的限定符。那麼自動裝載時,就可使用自定義的限定符進行限定
@Autowired @Qualifier("cold") public void setDessert(Dessert dessert){ this.dessert = dessert; }
三、使用自定義的限定符註解:
假設已經有一個bean使用了限定符cold,結果另一個bean也須要使用限定符cold,這樣也出現了多個匹配的bean也會報錯。
解決這個問題的思路是,再爲這2個bean增長限定符,繼續細化;可是@Qualifier註解並不支持重複註解,不能在一個bean上使用多個@Qualifier註解。
爲了解決這個問題,可使用自定義的限定符註解:
//代替@Qualifier("cold") @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold{} //代替@Qualifier("creamy") @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Creamy{}
這樣,如下代碼,自動裝配式使用了2個自定義限定符註解,也能夠找到惟一匹配的bean。
@Bean @Cold @Creamy public Dessert iceCream{ return new IceCream(); } @Bean @Cold public Dessert ice{ return new Ice(); } @Autowired @Cold @Creamy public void setDessert(Dessert dessert){ this.dessert = dessert; }
默認狀況下,bean是單例的形式建立的,既整個應用程序使用的bean是同一個實例。
有些狀況,若是想重用這個bean,結果這個bean被以前的操做污染了,會使程序發生錯誤。
Spring支持爲bean設置做用域,提供瞭如下幾種做用域:
//可使用組件掃描時,聲明做用域;做用域可使用ConfigurableBeanFactory表示 @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Cake implements Dessert{...} //能夠在Java配置Bean時,聲明做用域;做用域也可使用字符串表示,不過建議使用ConfigurableBeanFactory更不容易出錯 @Bean @Scope("prototype") public Dessert iceCream{ return new IceCream(); }
<!-- 使用XML配置時,設置bean做用域 --> <bean id="iceCream" class="com.desserteater.IceCream" scope="prototype" />
某些情景下,某個bean(例如Web應用中的購物車bean),不該該是對於程序單例的,而是對於每個用戶有對應一個bean。
這種狀況下,適合使用會話做用域的bean
@Component @Scope( value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES) public ShopingCart cart(){...}
一樣,請求做用域也是有同樣的問題,一樣處理便可。
引用aop命名空間
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-beans.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context"> <!-- 配置內容 --> </beans>
使用aop命名控件聲明做用域代理:
<aop:scoped-proxy />是和@Scope註解的proxyMode屬性相同的XML元素
它告訴Spring爲bean建立一個做用域代理,默認狀態下,會使用CGLib建立目標類代理。
<bean id="cart" class="com.myapp.ShoppingCart" scope="session"> <aop:scoped-proxy /> </bean>
設置爲生成基於接口的代理
<bean id="cart" class="com.myapp.ShoppingCart" scope="session"> <aop:scoped-proxy proxy-target-class="false" /> </bean>
以前討論的依賴注入,主要關注將一個bean引用注入到另外一個bean的屬性或構造器參數中。
依賴注入還有另一方面:將值注入到bean的屬性或構造器參數中。
固然可使用以前說過的硬編碼,可是咱們這裏但願這些值在運行時肯定。
Spring提供2種運行時求值方式:屬性佔位符(Property placeholder)、Spring表達式語言(SpEL)
使用@PropertySource註解和Environment:
@Configuration @PropertySource("classpath:/com/soundsystem/app.properties") public class EnvironmentConfig { @Autowired Environment env; @Bean public BlankDisc blankDisc() { return new BlankDisc( env.getProperty("disc.title"), env.getProperty("disc.artist")); } }
經過@PropertySource註解引用一個名爲app.properties的文件,文件內容以下
disc.title=Sgt. Peppers Lonely Hearts Club Band
disc.artist=The Beatles
而後經過Spring的Environment,使用getProperty()方法,進行檢索屬性。
getProperty()方法的重載
String getProperty(String key) String getProperty(String key, String defaultValue) T getProperty(String key, class<T> type) T getProperty(String key, class<T> type,T defaultValue)
檢查profile的激活狀態
解析屬性佔位符「${...}」以及註解@Value:
使用Environment檢索屬性很方便,Spring同時也提供了佔位符裝配屬性的方法
a、配置PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean(Spring3.1開始推薦用這個) ,這個bean能夠基於Spring Environment以及其屬性源來解析佔位符
Java配置
@Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){ return new PropertySourcesPlaceholderConfigurer(); }
XML配置,引用Spring context命名空間中的<context:property-placeholder />便可自動生成PropertySourcesPlaceholderConfigurer bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-beans.xsd"> <context:property-placeholder /> </beans>
b、注入
Java注入
public BlandKisc( @Value("${disc.title}") String title, @Value("${disc.artist}") String artist){ this.title = title; this.artist = artist; }
XML注入
<bean id="sgtPeppers" class="soundsystem.BlandDisc" c:_title="${disc.title}" c:_artist="${disc.artist}" />
Spring 3引入了Spring表達式語言(Spring Expression Language,SpEL),可以強大和簡潔地把值裝配到bean屬性和構造器參數中。
SpEL的一些特性:
2.一、表示字面值:
#{1}//整型 #{3.14159}//浮點數 #{9.87E4}//科學計數法 #{'Hello'}//String類型字面值 #{false}//Boolean類型
2.二、引用bean、屬性和方法:
#{sgtPeppers}//引用ID爲sgtPeppers的bean #{sgtPeppers.artist}//引用bean的屬性 #{sgtPeppers.selectArtist()}//調用bean的方法 #{sgtPeppers.selectArtist().toUpperCase()}//調用bean的方法的值的方法,例如方法值是String類型,則能夠調用String對象的toUpperCase方法 #{sgtPeppers.selectArtist()?.toUpperCase()}//類型安全的運算符「?.」,當值爲null時返回null,不然猜執行toUpperCase方法
2.三、表達式中使用類型:
主要做用是調用類的靜態方法和變量。使用T()運算符,得到Class對象。
#{T(java.lang.Math)}//獲得一個class對象 #{T(java.lang.Math).PI}//類的常量 #{T(java.lang.Math).random()}//類的靜態方法
2.四、SpEL運算符:
用於在SpEL表達式上作運算
#{2 * T(java.lang.Math).PI * circle.radius}//計算周長 #{T(java.lang.Math).PI * circle.radius ^ 2}//計算面積 #{disc.title + ' by ' + disc.artist}//拼接字符串 #{counter.total == 100}//比較運算符 結果爲布爾值 #{counter.total eq 100}//比較運算符 結果爲布爾值 #{scoreboard.score ? 1000 ? "Winner!" : "Loser"}//三元運算符 #{disc.title ?: 'Rattle and Hum'}//Elvis運算符(Elvis是貓王的名字,?:符號像貓王的頭髮),判斷是否爲null,是null則給默認值
2.五、計算正則表達式:
使用matches運算符,返回Boolean類型值
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}//驗證有效郵件
2.六、計算集合:
#{jukebox.songs[4].title}//songs集合第5個元素的title屬性 #{'this is a test'[3]}//String中的一個字符 #{jukebox.songs.?[artist eq 'Jay']}//使用查詢運算符(.?[])過濾子集 #{jukebox.songs.^[artist eq 'Jay']}//使用第一個匹配符(.^[])找到第一個匹配的項 #{jukebox.songs.$[artist eq 'Jay']}//使用最後一個匹配符(.$[])找到最後一個匹配的項 #{jukebox.songs.![title]}//投影運算符(.![])選擇屬性投影到一個新的集合
2.七、這裏介紹的SpEL只是冰山一角,有須要的去查閱相關資料
爲何須要面向切面編程(AOP)技術:
什麼是面向切面編程(AOP)技術:
如上所述,切面能夠幫助咱們模塊化橫切關注點。
通常若是咱們須要重用通用功能的話,常見的面向對象技術是繼承或委託。
可是若是整個應用中都使用一樣的基類,繼承每每會致使一個脆弱的對象體系;
而使用委託可能須要對委託對象進行復雜的調用。
而切面提供了另外一種可選方案:在一個獨立的地方定義通用功能,經過聲明的方式定義此功能用何種方式在何處應用,而無需修改受影響的類。橫切關注點能夠被模塊化特殊的類,這些類稱爲切面。
這樣作有2個好處:
通知(Advice):
通知定義了切面是什麼以及什麼時候使用。除了描述切面要完成的工做,通知還解決了什麼時候執行這個工做的問題。
Spring切面有5種類型的通知:
鏈接點(Join point):
鏈接點是咱們的程序能夠應用通知的實際,是應用執行過程當中可以插入切面的一個點。
切面代碼能夠利用這些點插入到應用的正常流程之中,並添加新的行爲。
切點(Poincut):
切點指切面所通知的鏈接點的範圍。
一個切面不須要通知整個程序的鏈接點,經過切點,定義了切面所應用的範圍。
通知定義了切面是「什麼」以及「什麼時候」執行,切點定義了「何處」須要執行切面。
切面(Aspect):
切面是通知和切點的結合,定義了須要作什麼,在什麼時候,何處完成該功能。
引入(Introduction):
引入容許咱們想現有的類添加新的方法或屬性。
能夠在不修改現有類的狀況下,將新的方法和實例變量引入到該類中。
織入(Weaving):
織入是把切面應用到目標對象並建立新的代理對象的過程。
切面在指定的鏈接點被織入到目標對象中。
在目標對象的聲明週期有幾個點能夠進行織入:
Spring支持4種類型的AOP支持:
Spring通知是Java編寫的:
Spring所建立的通知是標準的Java類編寫的,咱們可使用Java開發IDE來開發切面。
並且定義通知所應用的切點一般會用註解或Spring XML編寫,Java開發者都很是熟悉。
AspectJ與之相反,AspectJ最初是以Java語言擴展的方式實現的。
優勢是經過特有的AOP語言,咱們能夠得到更強大和細粒度的控制,以及更豐富的AOP工具集。
缺點是須要額外學習新的工具和語法。
Spring在運行時通知對象:
經過在代理類中包裹切面,Spring在運行期把切面織入到Spring管理的bean中。
代理類封裝目標類,並攔截被通知方法的調用,再把調用轉發給真正的目標bean。
代理攔截到方法調用時,會在調用目標bean方法以前執行切面邏輯。
知道應用須要被代理的bean時,Spring纔會建立代理對象。若是使用的是ApplicationContext的話,在ApplicationContext從BeanFactory中加載全部bean的時候,Spring纔會建立被代理的對象。
由於Spring運行時才建立代理對象,因此咱們不須要特殊的編譯器來織入Spring AOP的切面。
Spring只支持方法級別的鏈接點:
由於Spring是基於動態代理,因此只支持方法鏈接點;但也足以知足絕大部分需求。
若是須要攔截字段和構造器,可使用AspectJ和JBoss
Spring AOP中,要使用AspectJ的切點表達式語言來定義切點。
Spring僅支持AspectJ切點指示器的一個子集。
Spring是基於代理的,而某些切點表達式是與基於代理的AOP無關的。
其中,只有execution指示器是實際執行匹配的,其它指示器都是用來限制匹配的。
Spring中嘗試使用AspectJ其它指示器時,會拋出IllegalArgument-Exception異常
假設有一個接口
public interface Performance{ public void perform(); }
使用切點表達式設置當perform()方法執行時觸發通知的調用:
使用within()指示器限制僅匹配concert包
支持的關係操做符有且(&&),或(||),非(!)
若是用XML配置,由於&在XML中有特殊含義,因此可使用and,or,not來做爲關係操做符
Spring引入了一個新的bean()指示器,經過bean的ID來限制bean
限制id爲woodstock的bean才應用通知
限制id非woodstock的bean才應用通知
AspectJ5以前,編寫AspectJ切面須要學習一種Java語言的擴展。
AspectJ5引入了使用註解來建立切面的關鍵特性,AspectJ面向註解的模型能夠很是簡便地經過註解把任意類轉變爲切面。
@Aspect public class Audience{ //表演前 @Before("execution(** concert.Performance.perform(..))") public void silenceCellPhones(){ System.out.println("Silencing cell phones"); } //表演前 @Before("execution(** concert.Performance.perform(..))") public void takeSeats(){ System.out.println("Taking seats"); } //表演後 @AfterReturning("execution(** concert.Performance.perform(..))") public void applause(){ System.out.println("Clap!!"); } //表演失敗後 @AfterThrowing("execution(** concert.Performance.perform(..))") public void demandRefund(){ System.out.println("Refund!!"); } }
@Aspect註解表示Audience不只是一個POJO,仍是一個切面。
@Before,@AfterReturning等註解,用來聲明通知方法。
使用@Pointcut註解定義可重用切點:
@Aspect public class Audience{ //可重用切點 @Pointcut("excution(** concert.performance.perform(..))") public void performance(){} //表演前 @Before("performance()") public void silenceCellPhones(){ System.out.println("Silencing cell phones"); } //表演前 @Before("performance()") public void takeSeats(){ System.out.println("Taking seats"); }
使用一個空的方法,做爲標記,使用@Pointcut註解定義成一個可重用的節點,而後經過「方法名()」來進行引用
啓用自動代理:
以上僅僅是定義了切面,要啓動切面功能,須要啓動切面的代理
@Configuration @EnableAspectJAutoProxy//啓動AspectJ自動代理 @ComponentScan public class ConcertConfig{ @Bean Public Audience audience(){//聲明Audience bean return new Audience(); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-beans.xsd"> <context:conponent-scan base-package="concert" /> <!-- 啓用AspectJ自動代理 --> <aop:aspectj-autoproxy /> <!-- 聲明Audience bean --> <bean class="concert.Audience" /> </beans>
使用上述2種方法,會給Concert bean建立一個代理,Audence類中的通知方法會在perform()調用先後執行。
注意!Spring的AspectJ自動代理僅僅使用@AspectJ做爲建立切面的知道,切面本質上仍是Spring基於代理的切面,僅侷限於代理方法的調用。
若是想要使用AspectJ的全部能力,必須運行時使用AspectJ而且不依賴Spring。
@Aspect public class Audience{ //可重用切點 @Pointcut("excution(** concert.performance.perform(..))") public void performance(){} //環繞通知方法 @Around("performance()") public void watchPerformance(ProceedingJoinPoint jp){ try{ System.out.println("Silencing cell phones"); System.out.println("Taking seats"); jp.proceed(); System.out.println("CLAP!!"); }catch(Throwable e){ System.out.println("refund!!"); } } }
能夠將前置和後置通知寫在一個方法中,並使用ProceedingJoinPoint對象來進行對目標方法的調用。
@Aspect public class TrackCounter{ private Map<Interger,Integer> trackCounts = new HashMap<Integer,Integer>(); @Pointcut( "execution(* soundsystem.CompactDisc.playTrack(int))" + "&& args(trackNumber)") public void trackPlayed(int trackNumber){} @Before("trackPlayed(trackNumber)") public void countTrrack(int trackNumber){ int currentCount = getPlayCount(trackNumber); trackCounts.put(trackNumber,currentCount + 1); } public int getPlayCount(int trackNumber){ return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0; } }
假設情景:有一個類,但願讓其以及其實例實現某個接口,但這個類是不能夠修改的(如沒有源碼)。咱們能夠經過AOP爲這個類引入新的方法,實現該接口。
例如
咱們有一個類concert.Performance
但願能經過AOP實現接口
public interface Encoreable{ void performEncore(); }
切面
@Aspect public class EncoreableIntroducer{ @DeclareParents(value="concert.performance+",defaultImpl=DefaultEncoreable.class) public static Encoreable encoreable; }
經過聲明一個切面,使用@DeclareParents註解,講Encoreable接口引入到Performance bean中。
@DeclareParents的組成部分
一、Value屬性指定哪一種類型bean要引入該接口。本例中,指全部實現Performance的類型。(加號表示是Performance的全部子類型,而不是Performance自己。)
二、defaultImpl屬性指定爲引入功能提供實現的類。在這裏咱們指定的是DefaultEncoreable提供實現。
三、@DeclareParents註解所標註的驚天屬性知名了要引入的接口。在這裏咱們引入的是Encoreable接口。
在基於HTTP協議的Web應用中,Spring MVC將用戶請求在調度Servlet、處理器映射、控制器以及視圖解析器之間移動,再將用戶結果返回給用戶。
咱們將介紹請求如何從客戶端發起,通過Spring MVC中的組件,最終回到客戶端。
請求:請求離開瀏覽器時,帶有用戶請求內容的信息。通常包含請求的URL、用戶提交的表單信息等。
2.一、配置DispatcherServlet:
傳統方式:配置在web.xml中,這個文件放到應用的WAR包中。
如今方法:使用Java將DispatcherServlet配置到Servlet容器中。
public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { WebConfig.class };//指定配置類 } @Override protected String[] getServletMappings() { return new String[] { "/" };//將DispatcherServlet映射到「/」 } }
擴展AbstractAnnotationConfigDispatcherServletInitializer的任意類都會自動配置DipatcherServlet和Spring應用上下文,Spring的應用上下文會位於應用程序的Servlet上下文之中。
getServletMappings()方法將一個或多個路徑映射到DispatcherServlet上。本例中「/」,表明是應用的默認Servlet,會處理進入應用的全部請求。
要理解另外2個方法,須要先理解DispatcherServlet和一個Servlet監聽器(ContextLoaderListener)的關係。
2.二、兩個應用上下文之間的關係
DispatcherServlet啓動時,建立Spring應用上下文,加載配置文件或配置類中聲明的bean。
getServletConfigClasses()方法要求DispatcherServlet加載應用上下文時,使用定義在WebConfig配置類中的bean。
ContextLoaderListener會建立另一個應用上下文
咱們但願DispatcherServlet記載包含Web組件的bean,如控制器、視圖解析器以及處理器映射;而ContextLoaderListener要加載應用中其它bean,一般是驅動應用後端的中間層和數據層組件。
AbstractAnnotationConfigDispatcherServletInitializer 會同時建立DispatcherServlet和ContextLoaderListener。
getServletConfigClasses()方法返回的帶有@Configuration註解的類會用來定義DispatcherServlet應用上下文的bean。
getRootConfigClasses()方法返回的帶有@Configuration註解的類將會用來配置ContextLoaderListener建立的應用上下文中的bean。
Servlet3.0以前的服務器,只能支持web.xml的配置方式;Servlet3.0及之後版本才支持AbstractAnnotationConfigDispatcherServletInitializer 的配置方式。
2.三、啓動Spring MVC
傳統方法:使用XML配置Spring MVC組件
如今方法:使用Java配置Spring MVC組件
最簡單的Spring MVC配置
@Configuration @EnableWebMvc public class WebConfig { }
這樣就能啓動Spring MVC,可是有如下問題:
如下配置便可解決上述問題
@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"); return resolver; } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) {//配置靜態資源處理 // TODO Auto-generated method stub super.addResourceHandlers(registry); } }
RootConfig的配置
@Configration @ComponentScan(basePackages={"spitter"}, excludeFilters={ @filter{type=FilterType.ANNOTATION, value=EnableWebMvc.class) }) public class RootConfig{ }
使用@ComponentScan註解,後期可使用不少非Web組件來完善RootConfig。
@RequestMapping註解:
聲明這個控制器所要處理的請求
@Controller public class HomeController{ @RequestMapping(value="/", method=GET) public String home(){ return "home"; } }
1.一、聲明控制器
2種方式讓控制器類能被掃描稱爲組件:
1.二、指定處理請求路徑
@RequestMapping(value="/",method=GET)
value表明要處理的請求路徑,method屬性指定所處理的HTTP方法
1.三、返回視圖名稱
return "home";
返回一個字符串,表明須要渲染的視圖名稱。DispatcherServlet會要求視圖解析器將這個邏輯名稱解析爲實際的視圖。
基於咱們在InternalResourceViewResolver的配置,視圖名「home」將被解析爲「/WEB-INF/views/home.jsp」路徑的JSP。
控制器自己也是一個POJO,可用普通POJO的測試方法測試,可是沒有太大的意義。
Public class HomeControllerTest{ @Test public void testHomePage() throws Exception{ HomeController controller = new HomeController(); assertEquals("home", controller.home()); } }
這個測試只是測試home()方法的返回值,沒有站在SpringMVC控制器的角度進行測試。
Spring 3.2開始能夠按照控制器的方式來測試控制器。
Spring 3.2開始包含一種mock Spring MVC並針對控制器執行HTTP請求的機制,這樣測試控制器就不用啓動Web服務器和Web瀏覽器了。
Public class HomeControllerTest{ @Test public void testHomePage() throws Exception{ HomeController controller = new HomeController(); MockMvc mockMvc = standaloneSetup(controller).build();//搭建MockMvc mockMvc.perform(get("/"))//對"/"執行GET請求 .andExpect(view().name("home"));//預期獲得home視圖 } }
先傳遞一個HomeController實例到standaloneSetup()並調用build()來構建MockMvc實例,而後用這個實例來執行鍼對「/」的GET請求並設置指望獲得的視圖名稱。
對類使用@RequestMapping註解,那麼這個註解會應用到全部處理器方法中。
@Controller @RequestMapping("/") public class HomeController{ @RequestMapping( method=GET) public String home(){ return "home"; } }
路徑還能夠是一個數組,下面例子表明home()方法能夠映射到對「/」和「homepage」的GET請求。
@Controller @RequestMapping({"/", "/homepage"}) public class HomeController{ @RequestMapping( method=GET) public String home(){ return "home"; } }
使用Model傳遞數據
@RequestMapping(method=RequestMethod.GET) public String spittles(Model model){ model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE,20)); return "spittles"; }
經過Model參數,能夠將控制器裏面的值,傳遞到視圖中,渲染到客戶端。
Model其實是一個Map(Key-Value對集合)
使用addAttribute而且不指定key的時候,Model會根據類型自動生成key,例如上面是一個List<Spittle>,那麼key值就是spittleList
最後控制器返回視圖邏輯名,標明須要渲染的視圖。
改用Map傳遞數據
若是不想使用Spring類型,把Model改爲Map類型也是能夠的
@RequestMapping(method=RequestMethod.GET) public String spittles(Map model){ model.put("spittleList",spittleRepository.findSpittles(Long.MAX_VALUE,20)); return "spittles"; }
直接返回數據
@RequestMapping(method=RequestMethod.GET) public List<Spittle> spittles(){ return spittleRepository.findSpittles(Long.MAX_VALUE,20); }
這種寫法,沒有返回視圖名稱,也沒有顯式設定模型。
模型:當處理器方法直接返回對象或集合時,這個值會放進模型中,模型的key由類型推斷出來。
視圖:而視圖的邏輯名稱會根據請求路徑推斷得出,如/spittles的GET請求,邏輯視圖名稱就是spittles(去掉開頭斜線)。
視圖的渲染
不管使用哪一種方法,結果是同樣的:
在控制器中,將數據定義爲模型,併發送到指定的視圖,根據視圖的邏輯名稱,按照咱們配置的InternalResourceViewResolver視圖解析器,找到對應的視圖文件(如"/WEB-INF/views/spittles.jsp")。
當視圖是JSP的時候,模型數據會做爲請求屬性放到請求(request)之中。
所以,在jsp文件中可使用JSTL(JavaServer Pages Standard Tag Library)的<c:forEach>標籤進行渲染。
測試控制器視圖名以及傳遞的模型數據
@Test public void houldShowRecentSpittles() throws Exception { List<Spittle> expectedSpittles = createSpittleList(20); SpittleRepository mockRepository = mock(SpittleRepository.class);//使用mock,利用接口建立一個實現,並建立一個實例對象 when(mockRepository.findSpittles(Long.MAX_VALUE, 20)) .thenReturn(expectedSpittles);//調用mock實現,建立20個Spittle對象 SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = standaloneSetup(controller)//搭建MockMvc .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp")) .build(); mockMvc.perform(get("/spittles"))//對/spittles發起GET請求 .andExpect(view().name("spittles"))//斷言視圖名爲spittles .andExpect(model().attributeExists("spittleList")) .andExpect(model().attribute("spittleList", hasItems(expectedSpittles.toArray()))); }
場景:咱們須要分頁查找Spittle列表,但願傳入2個參數:上一頁最後項的id、每頁的數據數量,從而找到下一頁應該讀取的數據。
獲取參數
使用@RequestParam註解獲取參數
@RequestMapping(method=RequestMethod.GET) public List<Spittle> spittles( @RequestParam("max") long max, @RequestParam("count") int count){ return spittleRepository.findSpittles(max,count); }
給定默認值
private static final String MAX_LONG_AS_STRING = Long.toString(Long.MAX_VALUE); @RequestMapping(method=RequestMethod.GET) public List<Spittle> spittles( @RequestParam(value="max",defaultValue=MAX_LONG_AS_STRING) long max, @RequestParam(value="count",defaultValue="20") int count){ return spittleRepository.findSpittles(max,count); }
注意,查詢參數都是String類型,因此須要把Long.MAX_VALUE轉換爲String類型才能制定默認值。
當綁定到方法的參數時,纔會轉換爲對應的參數的類型。
測試方法
@Test public void shouldShowPagedSpittles() throws Exception { List<Spittle> expectedSpittles = createSpittleList(50); SpittleRepository mockRepository = mock(SpittleRepository.class); when(mockRepository.findSpittles(238900, 50)).thenReturn(expectedSpittles);//預期的max和count參數 SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = standaloneSetup(controller) .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp")).build(); mockMvc.perform(get("/spittles?max=238900&count=50")).andExpect(view().name("spittles"))//傳入max和count參數 .andExpect(model().attributeExists("spittleList")) .andExpect(model().attribute("spittleList", hasItems(expectedSpittles.toArray()))); }
按上面講解的寫法:
@RequestMapping(value = "/show", method = RequestMethod.GET) public String spittle(@RequestParam("spittleId") long spittleId, Model model) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
這個控制器,能夠處理請求是這樣的:"/spittle/show/?spittle_id=12345"
可是如今流行面向資源的方式,咱們查找一個spittle的行爲,至關於獲取一個spittle資源,更但願URL的方式是:對"/spittle/12345"這樣的URL進行GET請求。
使用@RequestMapping的佔位符與@PathVariable註解
@RequestMapping(value = "/{spittleId}", method = RequestMethod.GET) public String spittle(@PathVariable("spittleId") long spittleId, Model model) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
這樣在"/spittle/"後面的參數將會傳遞到spittleId變量中
若是變量名和佔位符名稱相同,能夠省掉@PathVariable中的變量
@RequestMapping(value = "/{spittleId}", method = RequestMethod.GET) public String spittle(@PathVariable("spittleId") long spittleId, Model model) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
測試方法
@Test public void testSpittle() throws Exception { Spittle expectedSpittle = new Spittle("Hello", new Date()); SpittleRepository mockRepository = mock(SpittleRepository.class); when(mockRepository.findOne(12345)).thenReturn(expectedSpittle); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = standaloneSetup(controller).build(); mockMvc.perform(get("/spittles/12345")).andExpect(view().name("spittle"))//經過路徑請求資源 .andExpect(model().attributeExists("spittle")).andExpect(model().attribute("spittle", expectedSpittle)); }
使用路徑傳參只使用於少許參數的狀況,若是須要傳遞大量數據,則須要使用表單。
Spring Security是一種基於Spring AOP和Servlet規範中filter實現的安全框架
Spring Security從2個角度解決安全問題
Spring Security分爲11個模塊
要使用Spring Security,至少要引入Core和Configuration2個模塊
簡化:使用一個特殊的filter:DelegatingFilterProxy,這個filter是一個代理類,會把工做交給Spring上下文中的javax.servlet.filter實現類
在實現了WebSecurityConfigurer的bean中使用註解@EnableWebSecurity
更簡化:在擴展類WebSecurityConfigurerAdapter中使用註解@EnableWebSecurity;若是使用Spring MVC,則須要使用註解@EnableWebMvcSecurity
能夠經過重寫如下3個方法,對Spring Security的行爲進行配置
Spring Security須要配置用戶信息服務,代表哪些用戶能夠進行訪問
待補充
待補充
Redis是一種基於key-value存儲的數據庫,Spring Data沒有把Repository生成功能應用到Redis中,而是使用面向模版的數據訪問的方式來支持Redis的訪問。
Spring Data Redis經過提供一個鏈接工廠來建立模版
建立鏈接工廠:
爲了鏈接和訪問Redis,有不少Redis客戶端,如Jedis、JRedis等。
Spring Data Redis爲4種Redis客戶端實現提供了鏈接工廠:
選擇哪一個客戶端實現取決於你,對於Spring Data Redis,這些鏈接工廠的適用性是同樣的
咱們使用一個bean來建立這個工廠
@Bean public RedisConnectionFactory redisCF() { JedisConnectionFactory cf = new JedisConnectionFactory(); cf.setHostName("redis-server"); cf.setPort(7379); cf.setPassword("123456"); return cf; }
對於不一樣的客戶端實現,他的工廠的方法(如setHostName)也是相同的,因此他們在配置方面是相同的操做。
獲取RedisTemplate
其中一種訪問Redis的方式是,從鏈接工廠中獲取鏈接RedisConnection,使用字節碼存取數據(不推薦):
RedisConnectionFactory cf = ...; RedisConnection conn = cf.getConnection(); conn.set("greeting".getBytes(),"Hello World".getBytes()); byte[] greetingBytes = conn.get("greeting".getBytes()); String greeting = new String(greetingBytes);
Spring Date Redis提供了基於模版的較高等級的數據訪問方案,其中提供了2個模版:
RedisConnectionFactory cf = ..; RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>(); redis.setConnectionFactory(cf); RedisConnectionFactory cf = ..; StringRedisTemplate redis = new StringRedisTemplate(cf); //設置爲bean @Bean public RedisTemplate<String, Product> redisTemplate(RedisConnectionFactory cf) { RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>(); redis.setConnectionFactory(cf); return redis; } @Bean public StringRedisTemplate redisTemplate(RedisConnectionFactory cf) { return new StringRedisTemplate(cf); }
使用RedisTemplate存取數據
//redis是一個RedisTemplate<String,Product>類型的bean //【使用簡單的值】 //經過product的sku進行存儲和讀取product對象 redis.opsForValue().set(product.getSku(),product); Product product = redis.opsForValue().get("123456"); //【使用List類型的值】 //在List類型條目尾部/頭部添加一個值,若是沒有cart這個key的列表,則會建立一個 redis.opsForList().rightPush("cart",product); redis.opsForList().leftPush("cart",product); //彈出一個元素,會移除該元素 Product product = redis.opsForList().rightPop("cart"); Product product = redis.opsForList().leftPop("cart"); //只是想獲取值 //獲取索引2到12的元素;若是超出範圍,則只返回範圍內的;若是範圍內沒有,則返回空值 List<Product> products = redis.opsForList().range("cart",2,12); //在Set上執行操做 //添加一個元素 redis.opsForSet().add("cart", product); //求差別、交集、並集 Set<Product> diff = redis.opsForSet().difference("cart1", "cart2"); Set<Product> union = redis.opsForSet().union("cart1", "cart2"); Set<Product> isect = redis.opsForSet().intersect("cart1", "cart2"); //移除元素 redis.opsForSet().remove(product); //隨機元素 Product random = redis.opsForSet().randomMember("cart"); //綁定到某個key上,至關於建立一個變量,引用這個變量時都是針對這個key來進行的 BoundListOperations<String, Product> cart = redis.boundListOps("cart"); Product popped = cart.rightPop(); cart.rightPush(product); cart.rightPush(product2); cart.rightPush(product3);
當某個條目保存到Redis key-value存儲的時候,key和value都會使用Redis的序列化器進行序列化。
Spring Data Redis提供了多個這樣的序列化器,包括:
這些序列化器都實現了RedisSerializer接口,若是其中沒有符合需求的序列化器,能夠自行建立。
RedisTemplate默認使用JdkSerializationRedisSerializer,那麼key和value都會經過Java進行序列化。
StringRedisTemplate默認使用StringRedisSerializer,實際就是實現String和byte數組之間的轉化。
例子:
使用RedisTemplate,key是String類型,咱們但願使用StringRedisSerializer進行序列化;value是Product類型,咱們但願使用JacksonJsonRedisSerializer序列化爲JSON
@Bean public void RedisTemplate<String,Product> redisTemplate(RedisConnectionFactory cf) { RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>(); redis.setConnectionFactory(cf); redis.setKeySerializer(new StringRedisSerializer()); redis.setValueSerializer(new Jackson2JsonRedisSerializer<Product>(Product.class)); return redis; }
一些變更不頻繁或者不變更的數據,當每次獲取的時候,都須要從數據庫中提取或者計算,每次都須要消耗資源。
咱們能夠把這些計算後的結果,在某個地方存放起來,當下次訪問的時候直接返回,就能夠避免了屢次的資源消耗,這就叫緩存技術。
@EnableCaching註解方式:
@Configuration @EnableCaching public class CachingConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); } }
XML方式:
使用Spring cache命名空間中的<cache:annotation-driven>元素啓動註解驅動的緩存
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <!-- 啓用緩存--> <cache:annotation-driven /> <!-- 聲明緩存管理器--> <bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" /> <beans>
代碼解析:
2種方式本質上是同樣的,都建立一個切面並觸發Spring緩存註解的切點
根據所使用的註解和緩存狀態,切面會從緩存中獲取數據,並將數據添加到緩存中或者從緩存刪除某個值。
其中不只啓用了註解驅動的緩存,還聲明瞭一個緩存管理器(cache manager)的bean。
緩存管理器是SPring緩存抽象的狠心,能夠和不一樣的緩存實現進行集成。
2個例子都是用ConcurrentHashMap做爲緩存存儲,是基於內存的,用在開發或者測試能夠,可是在大型企業級應用程序就有其餘更好的選擇了。
Spring3.1內置五個緩存管理器實現:
Spring3.2引入了另一個緩存管理器實現
除了核心Spring框架,Spring Data提供了2個緩存管理器實現
咱們選擇一個緩存管理器,而後在Spring應用上下文中,以bean的形式進行設置。使用不一樣的緩存管理器會影響數據如何存儲,可是不會影響Spring如何聲明緩存。
例子——配置EhCache緩存管理器:
此例子暫時省略。。。
例子——配置Redis緩存管理器:
使用RedisCacheManager,它會與一個Redis服務器協做,並經過RedisTemplate存取條目
RedisCacheManager要求:
@Configuration @EnableCaching public class CachingConfig { //Redis緩存管理器bean @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { return new RedisCacheManager(redisTemplate); } //Redis鏈接工廠bean @Bean public JedisConnectionFactory redisConnectionFactory() { JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); jedisConnectionFactory.afterPropertiesSet(); return jedisConnectionFactory; } //RedisTemplate bean @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisCF) { RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>(); redisTemplate.setConnectionFactory(redisCF); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
例子——使用多個緩存管理器:
使用Spring的CompositeCacheManager
如下例子建立一個CompositeCacheManager的bean,裏面配置了多個緩存管理器,會按順序迭代查找這些緩存管理器裏面是否存在值
@Bean public CacheManager cacheManager(net.sf.ehcache.CacheManager cm,javax.cache.CacheManager jcm){ CompositeCacheManager cacheManager = new CompositeCacheManager(); List<CacheManager> managers = new ArrayList<CacheManager>(); managers.add(new JCacheCacheManager(jcm)); managers.add(new EhcacheCacheManager(cm)); manager.add(new RedisCacheManager(reisTemplate())); cacheManager.setCacheManagers(managers); return cacheManager; }
設置好了鏈接工廠和緩存管理器,咱們就可使用註解來設置緩存了。
Spring的緩存抽象很大程度是基於切面構建的,啓用了緩存,就會建立一個切面,觸發Spring的緩存註解。
這些註解能夠用在方法或類上,用在類上,則類的全部方法都會應用該緩存行爲。
@Cacheable和@CachePut
最簡單的狀況下只須要使用value屬性便可
@Cacheable("spittleCache") public Spittle findOne(long id){ return new Spittle ();//能夠是具體的邏輯 }
緩存流程:
緩存註解也能夠寫在接口上,那麼它的全部實現類都會應用這個緩存規則。
將值放到緩存中:
一個使用情景:咱們保存一個數據,而後前臺刷新或者其餘用戶獲取,咱們能夠保存時直接更新緩存
@CachePut("spittleCache")
Spittle save(Spittle spittle);
自定義緩存key:
上面例子中,默認把Spittle參數做爲緩存的key,這樣並不合適,咱們但願用Spittle的ID做爲key值
可是Spittle未保存狀況下,又未有ID,這樣咱們能夠經過SpEL表達式解決這個問題
@CachePut(value="spittleCache",key="#result.id")
Spittle save(Spittle spittle);
條件化緩存:
使用unless和condition屬性,接受一個SpEL表達式
unless:若是爲false,調用方法時依然會在緩存中查找,只是阻止結果放進緩存;
condition:若是是false,則禁用整個緩存,包括方法調用前的緩存查找和方法調用後把結果放進緩存都被禁用
@Cacheable(value="spittleCache"
unless="#result.message.contains('NoCache')") Spittle findOne(long id);
@Cacheable(value="spittleCache"
unless="#result.message.contains('NoCache')"
condition="#id >= 10") Spittle findOne(long id);
@CacheEvict註解
調用方法時,會刪除該緩存記錄,
@CacheEvict("spittleCache") void remove(long spittleId);
有時候沒法是註解,或者不想使用註解,可使用XML聲明,這裏暫不介紹。
REST的名稱解釋:
SOAP:簡單對象訪問協議(英文:Simple Object Access Protocol,簡稱SOAP)。
REST:表述性狀態傳遞(英文:Representational State Transfer,簡稱REST)。
REST是比SOAP更簡單的一個Web應用可選方案。
REST是一種面向資源的架構風格,強調描述應用程序的事物和名詞。
簡潔地說,REST就是將資源的狀態,以最合適客戶端或服務器的表述方式,在服務器與客戶端之間轉移。
REST與HTTP方法:
URL:REST中,資源經過URL定位和識別。雖然沒有嚴格的URL格式定義,可是一個URL應該能識別資源,而不是簡單的一個命令。由於REST的核心是資源,而不是行爲。
行爲:REST中也有行爲,可是不是在URL中體現,通常經過HTTP行爲來定義,例如CRUD
須要實現RESTful功能的Spring MVC控制器
@Controller @RequestMapping("/spittle") public class SpittleApiController { private static final String MAX_LONG_AS_STRING = "9223372036854775807"; private SpittleRepository spittleRepository; @Autowired public SpittleApiController(SpittleRepository spittleRepository) { this.spittleRepository = spittleRepository; } @RequestMapping(method = RequestMethod.GET) public List<Spittle> spittles(@RequestParam(value = "max", defaultValue = MAX_LONG_AS_STRING) long max, @RequestParam(value = "count", defaultValue = "20") int count) { return spittleRepository.findSpittles(max, count); } }
內容協商
選擇一個視圖,可以將模型渲染爲呈現給客戶端的表述形式
這種方式通常不用
消息轉換器
使用HTTP信息轉換器
這是一種直接的方式,將控制器的數據轉換爲服務於客戶端的表述形式。
當使用消息轉換功能時, DispatcherServlet不用麻煩地將模型傳遞給視圖中。
這裏沒有視圖,甚至沒有模型,只有控制器產生的數據,而後通過消息轉換器,產生資源表述,傳到客戶端。
Spring中自帶了各類各樣的轉換器,可以知足常見的對象轉換爲表述的需求。
例如,客戶端經過請求的Accept頭信息代表它能接受"application/json",而且Jackson JSON在類路徑下,那麼處理方法返回的對象將交給MappingJacksonHttpMessageConverter,由他轉換爲返回客戶端的JSON表述形式。
或者,若是請求的頭信息代表客戶端想要"text/xml"格式,那麼Jaxb2RootElementHttpMessageConverter將會爲客戶端產生XML表述形式。
除了其中5個外,其它都是自動註冊的,不須要Spring配置;可是爲了支持他們,須要把對應的庫添加到類路徑中。
在響應體中返回資源狀態
正常狀況,若是控制器方法返回Java對象,這個對象會放到模型中,並在視圖中渲染。
爲了使用消息轉換功能,咱們須要告訴Spring跳過正常的模型/視圖流程,並使用消息轉換器。
最簡單的方式:使用@ResponseBody
@RequestMapping(method = RequestMethod.GET, produces = "application/json") public @ResponseBody List<Spittle> spittles(@RequestParam(value = "max", defaultValue = MAX_LONG_AS_STRING) long max, @RequestParam(value = "count", defaultValue = "20") int count) { return spittleRepository.findSpittles(max, count); }
@ResponseBody會告訴Spring,咱們要將返回的對象做爲資源發送給客戶端,並轉換爲客戶端要求的表述形式。
DispatcherServlet會根據請求中Accept頭部信息,找到對應的消息轉換器,而後把Java對象轉換爲客戶端須要的表述形式。
例如客戶端請求的Accept頭部信息代表它接收"application/json",且Jackson JSON庫位於應用的類路徑下,那麼將選擇MappingJacksonHttpMessageConverter或MappingJackson2HttpMessageConverter(取決於類路徑下是哪一個版本的Jackson)做爲消息轉換器,並將Java對象轉換爲JSON文檔,寫入到相應體中。
@ RequestMapping中produces屬性,表明該控制器只處理預期輸出爲JSON的請求,也就是Accept頭信息包含"application/json"的請求。
其它類型請求,即便URL匹配且爲GET請求,也不會被處理。
這樣的請求會被其它的方法進行處理(有適當方法的狀況下),或者返回HTTP 406(Not Acceptable)響應。
請求體中接收資源狀態
上面只討論瞭如何將一個REST資源轉換爲客戶端所須要的表述,這裏討論如何將客戶端發送過來的資源狀態表述轉換爲JAVA對象。
使用@RequestBody註解
@RequestMapping(method = RequestMethod.POST, consumes = "application/json") public @ResponseBody Spittle saveSpittle(@RequestBody Spittle spittle) { return spittleRepository.save(spittle); }
@RequestBody代表
爲控制器默認設置消息轉換
@RestController註解(Spring 4.0及以上)
使用@RestController代替@Controller標註控制器,Spring會爲全部方法應用消息轉換功能,咱們就不用每一個方法添加@ResponseBody,固然@RequestBody若是須要使用到,是不能省略的
@RequestMapping(value = "/{id}", method = RequestMethod.GET) public @ResponseBody Spittle spittleById(@PathVariable Long id) { return spittleRepository.findOne(id); }
上述方法是查找一個Spittle對象。
假設找不到,則返回null,這時候,返回給客戶端的HTTP狀態碼是200(OK),表明事情正常運行,可是數據是空的。
而咱們但願這種狀況下或許可使狀態碼未404(Not Found),這樣客戶端就知道發生了什麼錯誤了。
要實現這種功能,Spring提供了一下幾種方式:
使用ResponseEntity
使用ResponseEntity對象替代@ResponseBody
@RequestMapping(value = "/{id}", method = RequestMethod.GET) public ResponseEntity<Spittle> spittleById(@PathVariable Long id) { return spittleRepository.findOne(id); HttpStatus status = spittle != null ? HttpStatus.OK : HttpStatus.NOT_FOUND; return new ResponseEntity<Spittle>(spittle, status); }
使用ResponseEntity,能夠指定HTTP狀態碼,並且自己也包含@ResponseBody的定義,至關於使用了@ResponseBody。
接下來,咱們但願若是找不到Spittle對象時,返回錯誤信息
能夠建立一個Error類,而後使用泛型,在找不到Spittle對象時,返回一個Error對象
@RequestMapping(value = "/{id}", method = RequestMethod.GET) public ResponseEntity<?> spittleById(@PathVariable Long id) { return spittleRepository.findOne(id); if (spittle == null){ Error error = new Error(4, "Spittle [" + id + "] not found"); return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND); } return new ResponseEntity<Spittle>(spittle, HttpStatus.OK); }
可是,這種寫法貌似使代碼變得有點複雜,咱們能夠考慮使用錯誤處理器。
處理錯誤
步驟1:建立一個異常類
public class SpittleNotFoundException extends RuntimeException { private static final long serialVersionUID = 1L; private long spittleId; public SpittleNotFoundException(long spittleId) { this.spittleId = spittleId; } public long getSpittleId() { return spittleId; } }
步驟2:在控制器下建立一個錯誤處理器的處理方法
@ExceptionHandler(SpittleNotFoundException.class) public ResopnseEntity<Error> spittleNotFound(SpittleNotFoundException e) { long spittleId = e.getSpittleId(); Error error = new Error(4, "Spittle [" + spittleId + "] not found"); return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND); }
@ExceptionHandler註解加到控制器方法中,能夠處理對應的異常。
若是請求A發生異常,被這個方法捕獲到該異常, 那麼請求的返回值就是這個異常處理器的返回值。
步驟3:原來的業務控制器方法能夠獲得簡化
@RequestMapping(value = "/{id}", method = RequestMethod.GET) public ResponseEntity<Spittle> spittleById(@PathVariable Long id) { return spittleRepository.findOne(id); if (spittle == null) { throw new SpittleNotFoundException(id); } return new ResponseEntity<Spittle>(spittle, HttpStatus.OK); }
又由於此時任什麼時候候,這個控制方法都有數據返回,因此HTTP狀態碼始終是200,因此能夠不使用ResponseEntity,二使用@ResponseBody;若是控制器上面還使用了@RestController,咱們又能夠把@ResponseBody省掉, 最後獲得代碼
@RequestMapping(value = "/{id}", method = RequestMethod.GET) public Spittle spittleById(@PathVariable Long id) { return spittleRepository.findOne(id); if (spittle == null) { throw new SpittleNotFoundException(id); } return Spittle; }
步驟4:同理,能夠簡化一下錯誤處理器
@ExceptionHandler(SpittleNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public Error spittleNotFound(SpittleNotFoundException e) { long spittleId = e.getSpittleId(); return new Error(4, "Spittle [" + spittleId + "] not found"); }
在簡化過程當中,咱們都但願避免使用ResponseEntity,由於他會致使代碼看上來很複雜。
可是有的狀況必須使用ResponseEntity才能實現某些功能。
在響應中設置頭部信息
要使用到ResponseEntity,好比說我新建一個Spittle後,系統經過HTTP的Location頭部信息,返回這個Spittle的URL資源地址。
這裏暫時不討論
【暫略】16.四、編寫REST客戶端
SpringMVC中默認的序列化器只支持子集傳入,能夠注入bean,使用fastJson做爲MVC使用的序列化和反序列化器
@Bean public HttpMessageConverters fastJsonHttpMessageConverters() { FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); fastConverter.setFastJsonConfig(fastJsonConfig); HttpMessageConverter<?> converter = fastConverter; return new HttpMessageConverters(converter); }