Core Spring

Spring in Action》讀書筆記和總結浓若。Spring官網(wǎng): spring.io

Spring的底層功能依賴于兩個核心特性,依賴注入(dependency injection, DI)和面向切面編程(aspect-oriented programming, AOP)炒刁。Spring簡化了Java開發(fā),提供了輕量級的編程模型苦银,增強了POJO(plain old Java object)的功能红碑。DI和AOP都是為了實現(xiàn)接口和功能的松耦合(Loose Coupling),并且在實現(xiàn)上最大化的采用最小侵入性編程绰上。


The Spring Framework is made up of six well-defined module categories

DI

實現(xiàn)接口上的松耦合旨怠。為了關聯(lián)各接口之間的調(diào)用和依賴,Spring采用裝配bean方式蜈块。建立應用接口間依賴關系的行為稱為裝配(Wiring)鉴腻。

Spring應用上下文(Application Context)負責對象的創(chuàng)建和組裝。

Spring提供三種主要的裝配注入機制:

  1. 隱式的bean自動裝配機制疯趟;
  2. 顯示Java配置拘哨;
  3. 顯示XML配置。

自動化裝配bean

先定義需要的組件component信峻,其次開啟組件掃描(Component scanning),最后調(diào)用其他組件瓮床。Spring會自動發(fā)現(xiàn)應用上下文中創(chuàng)建的bean盹舞。

定義組件

@Component("beanName")  //beanName不填寫产镐,默認為首字母小寫的類名
public class Impl implements Interface {
}

開啟組件掃描

@Configuration
@ComponentScan
public class AutoConfig { 
}

默認以配置類所在的包作為基礎包(base package)掃描組件。@ComponentScan(basePackages="pkgName")一個包踢步、@ComponentScan(basePackages={"pkgName1", "pkgName2"})多個包癣亚、@ComponentScan(basePackageClasses={Interface1.class, Impl2.class})類或接口所在的包作為組件掃描的基礎包。

<context:component-scan base-package="pkg" />

調(diào)用其他組件

@Component
public class Impl implements Interface {
    @Autowired
    private InvokedClass ref;
}

Java Config

添加配置并聲明各接口調(diào)用依賴關系即可获印。

@Configuration
public class EatConfig {

  @Bean
  public Fruit fruit() {
    return new Apple(System.out);
  }
  
  @Bean
  public Person person() {
    return new Man(fruit());
  }

}

XML Config

構造器注入

可以使用全稱<constructor-arg />c-標簽

<bean id="person" class="eat.Person">
    <constructor-arg ref="fruit" />    <!-- bean引用注入 -->
</bean>

<bean id="fruit" class="eat.Fruit">
    <constructor-arg value="#{T(System).out}" />    <!-- 字面量注入 -->
</bean>

<bean id="list" class="eat.Collection">
    <constructor-arg>
      <list>    <!-- 集合注入 -->
        <ref bean="one" />
        <ref bean="two" />
        <ref bean="three" />
      </list>
    </constructor-arg>
</bean>

<bean id="list" class="eat.Collection">
    <constructor-arg>
      <list>    <!-- 集合注入 -->
        <value>one</value>
        <value>two</value>
        <value>three</value>
      </list>
    </constructor-arg>
</bean>

作為一個通用規(guī)則述雾,對強依賴使用構造器注入,對弱依賴使用屬性注入兼丰。

屬性注入

<bean id="person" class="eat.Person">
    <property name="fruit" ref="fruit" />   <!-- 引用注入 -->
</bean>

<bean id="fruit" class="eat.Fruit">
  <property name="apple" value="I eating an apple" />   <!-- 字面量注入 -->
  <property name="num">
    <list>  <!-- 集合注入 -->
      <value>one</value>
      <value>two</value>
      <value>three</value>
    </list>
  </property>
</bean>

導入和混合配置

Java Config

@Configuration
@Import({OneConfig.class, TwoConfig.class})
@ImportResource("classpath:three-config.xml")
public class RootConfig {

}

XML Config

<bean class="package.OneConfig" />
<bean resource="three-config.xml" />

高級裝配

Profile

應用程序在不同環(huán)境的遷移玻孟,如數(shù)據(jù)庫配置、加密算法以及與外部系統(tǒng)集成是否Mock是跨環(huán)境部署時會發(fā)生變化的幾個典型例子鳍征。
如果在XML配置文件中配置(Maven的profiles)黍翎,在構建階段確定將配置編譯部署,問題在于為每種環(huán)境重新構建應用艳丛,而Spring的profile是在運行時確定配置源匣掸。

定義profile

@Configuration
public class DataSourceConfig {
  
  @Bean(destroyMethod = "shutdown")
  @Profile("dev")
  public DataSource embeddedDataSource() {

  }

  @Bean
  @Profile("prod")
  public DataSource jndiDataSource() {

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

激活profile

有多種方式設置這兩個屬性,spring.profiles.active優(yōu)先spring.profiles.default氮双。

  • As initialization parameters on DispatcherServlet
  • As context parameters of a web application
  • As JNDI entries
  • As environment variables
  • As JVM system properties
  • Using the @ActiveProfiles annotation on an integration test class

例如在Web應用中碰酝,設置spring.profiles.default的web.xml文件

<web-app ...>
    <!-- 為上下文設置默認的profile -->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </context-param>
    <!-- 為Servlet設置默認的profile -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>spring.profiles.default</param-name>
            <param-value>dev</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
</web-app>

Conditional beans

如果希望某個特定的bean創(chuàng)建后或環(huán)境變量設置后才會創(chuàng)建這個bean。Spring4引入了@Conditional注解戴差。

@Configuration
public class MagicConfig {

  @Bean
  @Conditional(MagicExistsCondition.class)
  public MagicBean magicBean() {
    return new MagicBean();
  }
  
}

設置給@Conditional的類需實現(xiàn)Condition接口砰粹,它會通過該接口進行對比。

public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

matches()返回true造挽,會創(chuàng)建帶有@Conditional注解的類碱璃。否則反之。
PS: @Profile本身也使用了@Conditional注解饭入,并引用ProfileCondition作為Condition實現(xiàn)嵌器。可查看Spring4以上源碼谐丢。

處理自動裝配歧義性

場景: 一個接口多個實現(xiàn)爽航。

  1. 定義限定符
@Component    //也可以是所有使用了@Component的注解,如: @Service乾忱、@Controller讥珍、@Ropository等
@Qualifier("one")   //該注解可省略,默認bean ID為首字母為小寫的實現(xiàn)類名字窄瘟。
public class One implements Number {
    ...
}
  1. 使用限定符衷佃。@Qualifier搭配@Autowired,@Autowired默認按類型裝配
@Autowired
@Qualifier("one")
private Number num;

或者直接使用J2EE自帶的@Resource蹄葱,默認按名稱進行裝配氏义,減少了與spring的耦合(推薦)锄列。

@Resource(name = "one")
private Number num;

bean的作用域

默認情況下,Spring Application Context中所有bean都是以單例(singleton)創(chuàng)建的惯悠。也就是說邻邮,不管特定的bean被注入到其他bean多少次,每次注入的都是同一個實例克婶。大多數(shù)情況下筒严,單例bean是很理想的狀態(tài)。但有時候所用類是mutable情萤,重用是不安全的鸭蛙。
Spring定義了四種作用域:

  • 單例(singleton):在整個應用中,只創(chuàng)建bean的一個實例紫岩。
  • 原型(Prototype):每次注入或通過Spring應用上下文獲取的時候规惰,都會創(chuàng)建一個新的bean實例。
  • 會話(Session):在Web應用中歇万,為每個會話創(chuàng)建一個bean實例勋陪。
  • 請求(Request):在Web應用中,為每次請求創(chuàng)建一個bean實例诅愚。

聲明bean為原型作用域

JavaConfig@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);XMLConfigscope="prototype"寒锚。

聲明bean為會話和請求作用域

在Web應用中违孝,以購物車bean為例,單例和原型作用域就不適用雌桑,會話作用域是最合適的喇喉,因為它與特定用戶關聯(lián)性最大。

@Component
@Scope(
    value=WebApplicationContext.SCOPE_SESSION,
    proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() { ... }

在每個用戶購物完成后會調(diào)用保存訂單service拣技,也就是會將會話級別的 ShoppingCart bean注入到單例級別的 StoreService bean中膏斤。proxyMode屬性解決了將會話或請求作用域的bean注入到單例bean的問題邪驮。

作用域代理能夠延遲注入請求和會話作用域的bean

基于接口代理:proxyMode=ScopedProxyMode.INTERFACES or <aop:scoped-proxy proxy-target-class="false" />
基于實現(xiàn)類代理:proxyMode=ScopedProxyMode.TARGET_CLASS or <aop:scoped-proxy />

運行時值注入

避免將值硬編碼在配置類中,使其在運行時確定衔掸。

聲明屬性源并通過Spring的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"));    //檢索屬性值
  }
  
}

屬性占位符(Property placeholders)

為了使用占位符敞映,需要配置一個 PropertySourcesPlaceholderConfigurer bean振愿,它能夠基于Spring Environment及其屬性源來解析占位符弛饭。

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
  return new PropertySourcesPlaceholderConfigurer();
}
<beans>
    <context:property-placeholder />    <!-- 自動生成PropertySourcesPlaceholderConfigurer -->
</beans>

使用解析參數(shù):

public BlankDisc(
      @Value("${disc.title}") String title,
      @Value("${disc.artist}") String artist) {
  this.title = title;
  this.artist = artist;
}

Spring表達式語言(The Spring Expression Language, SpEL)

SpEL表達式要放在"#{ ... }"之中侣颂。

  • 引用bean、屬性和方法藻肄。例如:#{sgtPeppers}拒担、#{sgtPeppers.artist}从撼、#{artistSelector.selectArtist()}#{artistSelector.selectArtist().toUpperCase()}
  • 訪問Java類婆翔。'T()'運算符結果是一個class對象啃奴,能夠訪問目標類型的靜態(tài)方法和常量气堕。#{T(System).currentTimeMillis()}T(java.lang.Math).PI揖膜、T(java.lang.Math).random()
  • 對值進行算術壹粟、關系和邏輯運算。#{T(java.lang.Math).PI * circle.radius ^ 2}洪添、#{disc.title + ' by ' + disc.artist}干奢、#{scoreboard.score > 1000 ? "Winner!" : "Loser"}
  • 匹配正則表達式(Regular Expression, regex)盏袄。matches運算符對String類型的文本(左邊參數(shù))應用正則(右邊參數(shù))辕羽。#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}
  • 計算集合。#{jukebox.songs[4].title}绰寞;查詢運算符".?[]"用來對集合進行過濾得到集合子集滤钱,#{jukebox.songs.?[artist eq 'Aerosmith']}枷踏;查詢第一個匹配項".^[]"和查詢最后一個匹配項".$[]";投影運算符".![]"停团,如將title屬性投影到一個新的String類型集合中#{jukebox.songs.![title]}

PS: 盡可能讓表達式保持簡潔佑稠,不要讓表達式太智能復雜旗芬。

AOP

實現(xiàn)功能上的松耦合疮丛。把系統(tǒng)核心業(yè)務邏輯組件和額外功能如日志、事務管理和安全這樣的服務組件分離開來履恩。

<aop:config>
  <aop:aspect ref="asp">
    <aop:pointcut id="pc"
        expression="execution(* *.method(..))"/>
      
    <aop:before pointcut-ref="pc" 
        method="doBeforePc"/>

    <aop:after pointcut-ref="pc" 
        method="doAfterPc"/>
  </aop:aspect>
</aop:config>

Spring容器(container)負責創(chuàng)建切心、裝配、配置并管理對象的整個生命周期协屡,從生存到死亡肤晓。Spring容器分為兩種類型: BeanFactory(bean工廠)是最簡單的容器啼县,提供基本的DI支持季眷;Application Context(應用上下文)基于BeanFactory構建卷胯,提供應用框架級別的服務窑睁。(推薦使用)

通知(Advice)

通知定義了切面是什么以及何時使用。Spring切面可以應用5種類型的通知橱赠,并使用AspectJ注解來聲明通知方法:

  • 前置通知-@Before: The advice functionality takes place before the advised method is invoked.
  • 后置通知-@After: The advice functionality takes place after the advised method completes, regardless of the outcome.
  • 返回通知-@AfterReturning: The advice functionality takes place after the advised method successfully completes.
  • 異常通知-@AfterThrowing: The advice functionality takes place after the advised method throws an exception.
  • 環(huán)繞通知-@Around: The advice wraps the advised method, providing some functionality before and after the advised method is invoked.

切點(Pointcut)

切點定義在何處執(zhí)行動作狭姨。Spring AOP所支持的AspectJ切點指示器: args()饼拍、@args()田炭、execution()教硫、this()target()茶鉴、@target()丧鸯、within()@within()剿干、@annotation穆刻。只有execution指示器是實際執(zhí)行匹配的氢伟,其他都是用來限制匹配的,所以execution指示器是編寫切點定義時最主要的指示器谬盐。

使用AspectJ切點表達式定義切點
使用within()限制切點范圍

bean()指示器使用bean ID或name作為參數(shù)來限制切點只匹配特定的bean。execution(* concert.Performance.perform()) and bean('woodstock')诬烹。

基本流程

  1. 定義切面
JavaAdvice

XML Config:

XMLAdvice
  1. 啟用AspectJ注解的自動代理
JavaConfig
XMLConfig

創(chuàng)建環(huán)繞通知

JavaAround
XMLAround

通知中增加參數(shù)

JavaArgument
XMLArgument

切點表達式中的args(trackNumber)限定符表明傳遞給 playTrack() 方法的int類型參數(shù)也會傳遞到通知中去颜说,參數(shù)的名稱trackNumber也與切點方法簽名中的參數(shù)相匹配员舵,這樣就完成了從命名切點到通知方法的參數(shù)轉(zhuǎn)移马僻。

通過注解引入新功能

不用直接修改對象或類的定義就能夠為對象或類增加新的方法。一種情況是設計上在原接口上增加通用方法對所有的實現(xiàn)并不適用措近,一種情況是使用第三方實現(xiàn)沒有源碼的時候女淑。借助于AOP的引入功能鸭你,不必在設計上妥協(xié)或者侵入性地改變現(xiàn)有的實現(xiàn)。

代理攔截調(diào)用并委托給實現(xiàn)該方法的其他對象
@Aspect
public class EncoreableIntroducer {

  @DeclareParents(value="concert.Performance+",
                  defaultImpl=DefaultEncoreable.class)
  public static Encoreable encoreable;

}
<aop:aspect>
  <aop:declare-parents
    types-matching="concert.Performance+"
    implement-interface="concert.Encoreable"
    default-impl="concert.DefaultEncoreable"
    />
</aop:aspect>

通過@DeclareParents注解將新接口引入到現(xiàn)有的bean中碳抄。value屬性指定引入到哪個接口bean上剖效,加號'+'表示該對象的子類型焰盗,而不是其本身;defaultImpl屬性指定了引入的功能爷光;

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞎颗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子引有,更是在濱河造成了極大的恐慌譬正,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粉怕,死亡現(xiàn)場離奇詭異贫贝,居然都是意外死亡蛉谜,警方通過查閱死者的電腦和手機型诚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門狰贯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赏廓,“玉大人楚昭,你說我怎么就攤上這事拍顷∥舭福” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵庆亡,是天一觀的道長又谋。 經(jīng)常有香客問我娱局,道長衰齐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任废酷,我火速辦了婚禮澈蟆,結果婚禮上卓研,老公的妹妹穿的比我還像新娘鉴分。我一直安慰自己,他們只是感情好橙垢,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布柜某。 她就那樣靜靜地躺著,像睡著了一般剂癌。 火紅的嫁衣襯著肌膚如雪佩谷。 梳的紋絲不亂的頭發(fā)上监嗜,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天裁奇,我揣著相機與錄音,去河邊找鬼溃肪。 笑死惫撰,一個胖子當著我的面吹牛放仗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼其障!你這毒婦竟也來了励翼?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎造烁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體药磺,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡癌佩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年围辙,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸿秆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贩虾,靈堂內(nèi)的尸體忽然破棺而出沥阱,到底是詐尸還是另有隱情,我是刑警寧澤策精,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布咽袜,位于F島的核電站询刹,受9級特大地震影響萎坷,放射性物質(zhì)發(fā)生泄漏哆档。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一象泵、第九天 我趴在偏房一處隱蔽的房頂上張望偶惠。 院中可真熱鬧,春花似錦绑改、人聲如沸厘线。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至旨枯,卻和暖如春攀隔,著一層夾襖步出監(jiān)牢的瞬間栖榨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留居夹,地道東北人准脂。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓狸膏,卻偏偏與公主長得像湾戳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子幼驶,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容