- Bean
- 默認情況下,Spring application context所有的bean都是singleton(只有一個instance),也就是不管一個bean被injects多少次,都是同一個instance
- 問題:
- 有些application中,object使用stateless可能不太合理
- 有些時候某些object是mutable(狀態會變),所以會有一些state,因此reuse不安全,這個時候class若是singleton bean就會有問題
- Spring定義了bean的scope:
- Singleton: 整個application只會建立一個instance (default)
- Prototype: 每次injects或是透過Spring application context get時都會建一個新的bean instance
- Session: Web application中,為每個session創建一個bean instance
- Request: Web application中,為每個request創建一個bean instance
- 如果使用除了singleton以外的scope,就會需要使用@Scope annotation
- @Scope可以和@Component及@Bean一起使用
- 例如:
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //也可以寫作@Scope("prototype") public class Notepad { ... }
- XML:
<bean id="notepad" class="com.myapp.Notepad" scope="prototype" />
- 使用request及session scope
- 假設做一個購物車系統,需要有session判斷是同一個人,這時候就可以使用sessionScope,如下
@Component @Scope( value=WebApplicationContext.SCOPE_SESSION, // value=session proxyMode=ScopedProxyMode.INTERFACES) // 解決了session or request scope的bean injects到singleton bean的問題 public ShoppingCart cart() { ... }
- 假設要將SoppingCart bean injects至singleton StoreService bean的setter:
@Component public class StoreService { @Autowired public void setShoppingCart(ShoppingCart shoppingCart) { // shoppingCart會直到某個用戶進入系統,create session後才會存在 this.shoppingCart = shoppingCart; } ... }
- 因此會希望能夠使用當下session的shoppingCart injects至setShoppingCat
- 方法:
- Spring並不會真的將實際的(session的) ShoppingCart injects至setShoppingCat,而是會injects一個shoppingCart的proxy
- 對StoreServic而言,此proxy就是一個ShoppingCart
- 當StoreServic需要使用ShoppingCart的method的時候,proxy會進行 lazily resolve(慵懶解譯),並且會將invoke delegate至實際的(session的) ShoppingCart
- proxyMode
- proxyMode=ScopedProxyMode.INTERFACE表示proxy要implements shoppingCart interface,並可以將invoke delegate至實際的(session的) ShoppingCart (只有當ShoppingCart是interface才能這樣做)
- 如果ShoppingCart是一個class,則必須使用CGLib產生class的proxy:
proxyMode=ScopedProxyMode.TARGET_CLASS - 在XML中宣告scoped proxies
- 要使用XML宣告,就不能使用@Scope及proxyMode
- <bean> 的scope可以設定scope,proxy mode需要使用aop namespace
<bean id="cart" class="com.myapp.ShoppingCart" scope="session"> <aop:scoped-proxy /> </bean>
- <aop:scoped-proxy>與@Scope annotation的proxyMode功能相同
- <aop:scoped-proxy>default會使用CGLib建立的class proxy
- 以下範例可以設置為interface的proxy
.. xmlns:aop="http://www.springframework.org/schema/aop" // 需要設定aop namesapce
- Runtime時injects value
- 當討論DI 時,通常指的是一個bean ref injects到另一個bean property或constructor parameter中,也就是一個object和另一個object的關聯
- 當討論wire的的時候,指的是一個value injects到另一個bean property或constructor parameter中,這種時候可能會使用hard-code
@Bean public CompactDisc sgtPeppers() { return new BlankDisc( "Sgt. Pepper's Lonely Hearts Club Band", //hard-code "The Beatles"); }
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="Sgt. Pepper's Lonely Hearts Club Band" c:_artist="The Beatles" /> //hard-code
- 應盡力避免hard-code
- Spring提供兩種runtime injects value的方式
- Property placeholder
- SpEL(Spring expression language)
- Injects external values
- 方式:@propertySource
@Configuration @PropertySource("classpath:/com/soundsystem/app.properties") public class ExpressiveConfig { @Autowired Environment env; @Bean public BlankDisc disc() { return new BlankDisc( env.getProperty("disc.title"), env.getProperty("disc.artist")); } }
- @PropertySource會load到Spring environment中,這樣的話就可以藉由getProperty讀取資訊
- Spring environment
- getProperty()是overloading method,共有四種
- String getProperty(String key)
- String getProperty(String key, String defaultValue) //key不存在時返回defaultValue
- T getProperty(String key, Class<T> type)
- T getProperty(String key, Class<T> type, T defaultValue)
- 3及4是為了轉型,如欲取得連接數量int時,不用在取得後再轉型,可以直接指定型態
int connectionCount = env.getProperty("db.connection.count", Integer.class, 30);
- getRequiredProperty("disc.title") 可以讓沒有disc.title property的時候throw IllegalStateException(getProperty會回傳null)
- containsProperty()確認property是否存在
- getPropertyAsClass()可以將property解析為class type
Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);
- String[] getActiveProfiles(): return active profiles
- String[] getDefaultProfiles(): return default profiles
- boolean acceptsProfiles(String... profiles): 如果profiles在active狀態則回傳true
- 可以在bean create之前,使用acceptsProfiles()確保bean所需的profiles處於active狀態
- 解析Property placeholder
- 可以使用語法"${}"
- 如XML內
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}" />
- 避免hard-code
- 若使用component-scanning and autowiring,就沒有config file,這時候可以使用@Value如下
public BlankDisc( @Value("${disc.title}") String title, @Value("${disc.artist}") String artist) { this.title = title; this.artist = artist; }
- 配置placeholder config:
- @Bean
- PropertySourcesPlaceholderConfigurer:可基於Spring env及其property解析placeholder
@Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); }
- XML:會協助生成PropertySourcesPlaceholderConfigurer class
<beans .. <context:property-placeholder /> </beans>
- 優點:解析外部property,讓value可以在runtime的時候生成,
- 缺點:會需要使用Spring env和property resource。
- 解決:使用Spring expression language
- Spring expression language (SpEL)
- 簡潔強大的方式將value wire到bean的property及constructor parameters中
- runtime時取得value
- 特性:
- 使用bean ID引用bean
- 使用object的method及取用其properties
- 對value進行mathematical, relational, and logical operations
- Regular expression matching
- Collections的操作
- SpEL 範例
- "#{...}" (placeholder是"$(...)"
- T(將裡面的東西)視為Java中對應的type
#{T(System).currentTimeMillis()} // 取得當下計算的那刻時間
- 引用其他bean或其他bean的property
#{sgtPeppers.artist}
- 也可以使用systemProperties取用系統屬性
#{systemProperties['disc.title']}
- 應用在constructor上
public BlankDisc( @Value("#{systemProperties['disc.title']}") String title, @Value("#{systemProperties['disc.artist']}") String artist) { this.title = title; this.artist = artist; }
- 應用在XML上
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="#{systemProperties['disc.title']}" c:_artist="#{systemProperties['disc.artist']}" />
- Literal
- #{3.14159}: floating
- #{9.87E4}: 98,700
- #{'Hello'}: String
- #{false}: boolean
- 引用bean
- 透過ID引用其他的bean,假設是sgtPeppers bean
- 假設需要引用sgtPeppers的property (#{id.property})
- 也能夠使用bean的method
- method回傳的值可以繼續做invoke chain
- 如果要避免取到null,可以使用type-safe operator(?),能夠在執行問號右邊前確保左邊返回的值不是null。如果是null,就不會執行問號右邊的method
- 在expression中使用type
- 使用T(),能夠使用static method及variable
- 例如使用Java Math class
- 使用PI
- 使用static method
- SpEL operators
- SpEL可以組合出複雜的expression
- 也可以使用eq表達相等 (比較運算gt, lt, le, eq, ge皆可使用)
- 可以檢查是否為null,如下判斷disc.title是否為null,是的話expression結果就是Rattle and Hum(此種表達式稱為Elvis operator)
- matches (正規表達式)
- 範例:判斷是否符合有效mail address
- Collections
- 隨機取得id=jukebox的bean中的songs property的一首歌,可使用[]
- 從corrections或array中取得item
- 取得歌曲內符合'Aerosmith'的歌曲,使用(.?[])進行過濾,得到collection的subCollection,也就是在[]內在寫一個expression language
- 第一個符合(.^[]),最後一個符合(.$[])
- 使用(.![])可以從子合中選特定的property放入。比如下例,將所有歌曲名稱的title property放入新的String type collection中
- 也可以組合一起用
- 注意:不要把SpEL寫得太過複雜,寫得越複雜,對它的測試也就越多,盡可能保持簡潔
#{sgtPeppers}
#{sgtPeppers.artist}
#{artistSelector.selectArtist()}
#{artistSelector.selectArtist().toUpperCase()}
#{artistSelector.selectArtist()?.toUpperCase()}
T(java.lang.Math)
T(java.lang.Math).PI
T(java.lang.Math).random()
#{2 * T(java.lang.Math).PI * circle.radius}
#{counter.total eq 100}
#{disc.title ?: 'Rattle and Hum'}
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}
#{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title}
#{'This is a test'[3]} // return 's'
#{jukebox.songs.?[artist eq 'Aerosmith']}
#{jukebox.songs.^[artist eq 'Aerosmith']}
#{jukebox.songs.![title]}
#{jukebox.songs.?[artist eq 'Aerosmith'].![title]}
沒有留言:
張貼留言