spring(3)高级装配
生活随笔
收集整理的這篇文章主要介紹了
spring(3)高级装配
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【0】README
0)本文部分文字描述轉自:“Spring In Action(中/英文版)”,旨在review ?spring(3)高級裝配?的相關知識;
【1】環境與profile(考慮數據庫配置) 1)使用嵌入式數據庫 @Bean(destroyMethod="shutdown") public DataSource dataSource() {return new EmbeddedDatabaseBuilder().addScript("classpath:schema.sql").addScript("classpath:test-data.sql").build(); }2)使用JNDI從容器中獲取DataSource @Bean public DataSource dataSource() {JndiObjectFactoryBean jndiObjectFactoryBean =new JndiObjectFactoryBean();jndiObjectFactoryBean.setJndiName("jdbc/myDS");jndiObjectFactoryBean.setResourceRef(true);jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);return (DataSource) jndiObjectFactoryBean.getObject(); }3)選擇不同的DataSource配置 @Bean(destroyMethod="close") public DataSource dataSource() {BasicDataSource dataSource = new BasicDataSource();dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");dataSource.setDriverClassName("org.h2.Driver");dataSource.setUsername("sa");dataSource.setPassword("password");dataSource.setInitialSize(20);dataSource.setMaxActive(30);return dataSource; }Attention)我們必須要有一種方法來配置DataSource,使其在每種環境下都會選擇最為合適的配置;其中一種方式是在單獨的配置類(或XML 文件)中配置每個bean,然后在構建階段(可能會使用Maven的profiles)確定要將哪一個配置編譯到可部署的應用中;
problem+solution) problem)從開發階段遷移到QA 階段,重新構建可能不會出大問題,但從QA階段遷移到 生產階段,重新構建可能會引入bug; solution)Spring所提供 的 解決方案并不需要重新構建;
【1.1】配置 profile bean 1)intro:spring 在重新構建的過程中需要根據環境決定該創建那個 bean 和 不創建那個bean;不過spring 并不是在構建時做出決策,而是等待運行時再來確定;這樣的結果就是同一個部署單元能夠適用于所有的環境,沒有必要進行重新構建; 2)spring 引入了bean profile:要使用profile,首先要將所有不同的 bean 定義整理到一個或多個profile中,在將應用部署到每個環境時,要確保對應的 profile 處于激活狀態 ; 3)在javaConfig中,可以使用 @Profile 注解指定某個bean屬于哪一個profile;
看個荔枝)注解@Profile(干貨——注解@Profile的作用) 荔枝1)嵌入式數據庫可能會配置為如下形式: @Configuration @Profile("dev") public class DevelopmentProfileConfig { @Bean(destroyMethod="shutdown") public DataSource dataSource() {return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).addScript("classpath:schema.sql").addScript("classpath:test-data.sql").build();} }荔枝2)使用JNDI從容器中獲取DataSource 的 Profile配置 @Configuration @Profile("prod") public class ProductionProfileConfig {@Beanpublic DataSource dataSource() {JndiObjectFactoryBean jndiObjectFactoryBean =new JndiObjectFactoryBean();jndiObjectFactoryBean.setJndiName("jdbc/myDS");jndiObjectFactoryBean.setResourceRef(true);jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);return (DataSource) jndiObjectFactoryBean.getObject();} } 荔枝3)從spring3.2開始,@Profile注解既可以在方法級別上使用,也可以在類級別上使用;(還可以和 @Bean 注解一起使用)
Attention) A1)沒有定義在profile中的bean 都會被創建,無論profile 激活與否; A2)而定義在 profile中的bean,當且僅當對應的 profile 被激活時才可以創建;
4)在XML中配置 profile 4.1)通過<beans>元素的profile屬性,在XML 中配置profile bean; 4.2)還可以在根<beans>元素中嵌套定義 <beans> 元素,而不是為每個環境都創建一個 profile 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"> <beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:schema.sql" /> <jdbc:script location="classpath:test-data.sql" /> </jdbc:embedded-database> </beans><beans profile="qa"> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close" p:url="jdbc:h2:tcp://dbserver/~/test"p:driverClassName="org.h2.Driver" p:username="sa" p:password="password"p:initialSize="20"p:maxActive="30" /> </beans> <beans profile="prod"> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatabase" resource-ref="true" proxy-interface="javax.sql.DataSource" /> </beans> </beans> 【1.2】激活profile 1)spring在確定哪個 profile處于激活狀態時,需要依賴兩個獨立的屬性:?spring.profiles.active and spring.profiles.default ; 如果設置了 active屬性的話,相應的Profile被激活,否則,查看default屬性的值,如果default的值 沒有設置的話,那就會忽略掉 profile中的bean的創建; 2)多種方法設置上述兩個屬性; method1)作為 DispatcherServlet 的初始化參數; method2)作為web 應用的上下文參數; method3)作為 JNDI條目; method4)作為環境變量; method5)作為JVM的 系統屬性; method6)在集成測試類上, 使用 @ActiveProfiles 注解設置;(干貨——注解@ActiveProfile的作用) 原書作者的設置方法:喜歡用DispatcherServlet?的參數將spring.profiles.default屬性設置為開發環境的profile,會在Servlet上下文中進行設置; 看個荔枝)在web.xml 中設置 spring.profiles.default 屬性
Attention)?spring.profiles.active and spring.profiles.default 中的profile都是 復數形式: 這意味著可以同時激活多個 profile,列出多個profile 名稱,以逗號分隔來實現;
3)使用profile進行測試 3.1)intro:spring提供了 @ActiveProfiles注解,來指定測試時要激活哪個profile; 3.2)下面的荔枝展示了 使用?@ActiveProfiles注解 激活 dev profile;
【2】條件化bean 1)intro:當滿足給定條件時,才裝配相應的bean;組合@Conditional注解 和 @Bean注解;(干貨——注解@Conditional的作用)
對以上代碼的分析(Analysis): A1)matches() 方法:會得到 ConditionContext 和 AnnotatedTypeMetadata 對象用來做出決策; A2)ConditionContext是一個接口: public interface ConditionContext { BeanDefinitionRegistry getRegistry(); ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader();ClassLoader getClassLoader(); }A3)通過ConditionContext,可以做到如下幾點(works): work1)借助getRegistry方法返回的BeanDefinitionRegistry?檢查bean的定義; work2)借助getBeanFactory方法返回的ConfigurableListableBeanFactory 檢查bean是否存在,查看bean的屬性 work3)借助getEnvironment方法返回的Environment?檢查環境變量是否存在以及它的值是什么; work4)讀取并檢查getResourceLoader方法返回的ResourceLoader?所加載的資源; work5)借助getRegistry方法返回的BeanDefinitionRegistry?檢查bean的定義; work6)借助getClassLoader方法返回的ClassLoader?加載并檢查類是否存在; A4)AnnotatedTypeMetadata?能夠讓我們檢查帶有 @Bean 注解的方法上還有什么其他的注解; public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationName); // 判斷帶有@Bean注解的方法是不是還有其他的注解.Map<String, Object> getAnnotationAttributes(String annotationName); Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString); } 2)從spring4 開始,@Profile注解進行了重構,使其基于@Conditional 和 Condition實現:@Profile注解如下所示:
對以上代碼的分析(Analysis): A1)ProfileCondition 通過?AnnotatedTypeMetadata? 得到了用于 @Profile 注解的所有屬性; A2)根據 通過ConditionContext得到的 Environment 來檢查(acceptProfiles()方法)該profile 是否處于激活狀態;
【3】處理自動裝配的歧義性 1)problem+solution 1.1)problem測試用例:(error info: No qualifying bean of type [com.spring.chapter3.Disc] is defined: expected single matching bean but found 2: jayChou,leehom)
1.2)solution:spring 提供了多種可選方案來解決這樣的問題。你可以將可選的bean 中的某個設為首選(primary)的bean,或者使用限定符來幫助 spring 將可選的bean的范圍縮小到只有一個bean;
1.3)如果在XML 配置bean的話,設置primary屬性為 true來指定;
Attention)如果標識了多個首選(Primary)bean的話,@Primary注解就無法正常工作了;
【3.2】限定自動裝配的bean 1)problem+solution 1.1)problem:設置首選bean的 局限性: 在于@Primary 無法將可選方案的范圍限定到唯一一個無歧義性的可選項中。當首選bean的數量超過一個四,我們并沒有其他的方法來進一步縮小可選范圍; 1.2)solution:spring的限定符能夠在所有可選的bean上進行縮小范圍的操作,最終能夠達到一個bena 滿足所規定的限制條件;(使用@Qualifier注解) 看個荔枝)確保將jaychou 注入到CDPlayer中
2)創建自定義限定符:我們可以為bean設置自己的限定符,而不是依賴于將bean ID 作為限定符; @Component @Qualifier("cold") public class IceCream implements Dessert { ... }3)@Qualifier注解也可以和 @Bean注解一起使用 @Bean @Qualifier("cold") public Dessert iceCream() {return new IceCream(); } 4)使用自定義的限定符注解(干貨——開發人員自己創建限定符注解) 4.1)problem+solution @Component @Qualifier("fashion") public class JayChou implements Disc { ... } @Component @Qualifier("fashion") public class Leehom implements Disc { ... } 4.1.1)problem:現在我們有兩個帶有“fashion”限定符的唱片,在自動裝配Disc bean的時候,我們再次遇到了歧義性問題; 4.1.2)solution:需要更多的限定符來將可選范圍限定到只有一個bean;(多個 @Qualifier 注解) @Component @Qualifier("fashion") @Qualifier("cool") public class JayChou implements Disc { ... }@Component @Qualifier("fashion") @Qualifier("handsome") public class Leehom implements Disc { ... } 5)problem+solution:
5.1)problem:java不允許在同一個條目上重復使用出現相同類型的多個注解;因為這樣的話,編譯器會報錯;
5.2)solution:自定義限定符注解;(不能再干貨——創建自定義的限定符注解) 6)how to build diy @Qualifier annotaion. step1)不再使用?@Qualifier("fashion") 注解,使用自定義的 @Fashion這,該注解 的定義如下: @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Fashion{ } Attention) A1)自定義注解本身實際上就成為了 限定符注解; A2)通過聲明自定義的限定符注解,我們可以同時使用多個限定符,不會再有java 編譯器的限制或錯誤了; 7)所以,我們可以添加 @Component @Qualifier("fashion") @Qualifier("cool") public class JayChou implements Disc { ... } // 改為 @Component @Fashion // a qualifier annotaion. @Cool// a qualifier annotaion. public class JayChou implements Disc { ... }
(干貨——說白了,自定義限定符注解 就是多個標識而已,用于區別不同bean 和 對bean 進行分組(因為他們都屬于fashion 組))
【4】 bean的作用域 1)intro: default case下,spring應用上下文中所有 bean都是以單例的形式創建的;也就是說,不管給定一個bean被注入到其他bean中多少次,每次所注入的都是 同一個實例; 2)problem+solution:顯然,讓多個對象引用同一個bean 是不合理的,因為對象中的內容是易變的,一個對象對bean做了修改,這會波及到其他bean的; 3)spring定義了多種作用域,可以基于這些作用域創建bean,包括(scope): scope1)單例(Singleton):在整個應用中,只創建bean的一個實例;(干貨——默認情況下,bean以單例模式創建) scope2)原型(Prototype):每次注入或者通過spring應用上下文獲取的時候,都會創建一個新的 bean 實例; scope3)會話(Session):在web 應用中,為每個會話創建一個 bean實例; scope4)請求(Request):在web 應用中,為每個請求創建一個 bean實例; Attention) A1)default case下: 是單例作用域; A2)如果選擇其他作用域,要使用 @Scope注解,它可以和 @Component 或 @Bean 聯用;(干貨——@Scope注解的作用) A3)不管用哪一種方式來聲明原型作用域,每次注入或從 spring 應用上下文中檢索該bean的時候,都會創建新的實例; 看個荔枝)將其聲明為原型bean: @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Notepad { ... }用XML 來配置的話,代碼形式如下: <bean id="notepad"class="com.myapp.Notepad"scope="prototype" />
【4.1】使用會話和請求作用域 1)指定會話作用域: 使用 @Scope 注解,它的使用方式與 指定原型作用域是相同的; @Component @Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES) public ShoppingCart cart() { ... } 對以上代碼的分析(Analysis): A1)這會創建多個?ShoppingCart? bean的實例,但是對于給定的會話只會創建一個實例,在當前會話相關的操作中,這個bean 實際上相當于單例的; A2)@Scope注解:同時還有一個 proxyMode 屬性,它被設置成了?proxyMode=ScopedProxyMode.INTERFACES;這個屬性解決了將會話或請求作用域的bean 注入到單例bean中所遇到的問題; 2)proxyMode 代理模式所要解決的問題: 2.1)假設我們要將 ShoppingCart bean在注入到 單例 StoreService bean 的Setter 方法中,如下所示: @Component public class StoreService {@Autowiredpublic void setShoppingCart(ShoppingCart shoppingCart) {this.shoppingCart = shoppingCart;}... } 對以上代碼的分析(Analysis): A1)因為StoreService 是一個單例bean,會在spring 應用上下文加載的時候創建; A2)當它創建的時候,spring會試圖 將 ShoppingCart bean 注入到 setShoppingCart() 方法中;但是ShoppingCart bean 是會話作用域的,此時并不存在。直到某個client 進入系統,創建了會話后,才會出現?ShoppingCart 實例;(干貨——我此時才體會到了為什么需要懶加載) A3)而且,系統中有多個?ShoppingCart 實例(多個購物車):每個用戶一個。我們并不想讓spring注入 到某個固定的?ShoppingCart 實例到 StoreService中。我們希望的是當StoreService 處理ShoppingCart 功能時,它所使用的?ShoppingCart 實例恰好是當前會話所對應的那一個; A4)spring 并不會將實際的ShoppingCart bean 注入到 StoreService 中。spring 會注入一個到?ShoppingCart bean 的代理,如下圖所示。這個代理會暴露與?ShoppingCart 相同的方法,所以 StoreService 會認為他是一個ShoppingCart。但是,當StoreService 調用?ShoppingCart 的方法時,代理會對其進行懶解析并將調用委托給會話作用域內真正的?ShoppingCart bean; (干貨——懶加載的調用過程,引入了代理proxy)
3)如果 ShoppingCart 是接口不是類的話,這是可以的;但如果ShoppingCart 是具體的類,那么spring無法創建基于接口的代理了。這時,spring必須使用CGLIB 來生成基于類的代理; 3.1)所以,如果bean類型是具體的類的話,我們必須要將 proxyMode 屬性設置為 ScopedProxy-?Mode.TARGET_CLASS,以此來表明要以生成目標類擴展的方式創建代理;
【4.2】在XML 中聲明作用域代理 1)要設置代理模式,需要使用 spring aop 命名空間的一個新元素(<aop:scoped-proxy />?): <bean id="cart"class="com.myapp.ShoppingCart"scope="session"><aop:scoped-proxy /> </bean>2)為了使用?<aop:scoped-proxy /> 新元素,我們必須在 XML 配置中聲明spring 的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/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">... </beans> 【5】運行時值注入 1)problem+solution 1.1)problem:在實現的時候將值硬編碼在配置類中。用XML 裝配bean的話,同樣值也會是硬編碼的;
1.2)solution:為了避免硬編碼,而是想讓這些值在運行時再確定。spring 提供了兩種在運行時求值的方式(ways): way1)屬性占位符(Property placeholder) way2)spring 表達式語言(SpEL)
【5.1】注入外部的值
對以上代碼的分析(Analysis):這個屬性文件(app.properties)會加載到 spring 的 Environment中,稍后可以從這里通過 getProperty()方法 檢索屬性。
2)深入學習Spring的Environment 2.1)getProperty()方法有4個重載的變種形式(variant): v1)String getProperty(String key) v2)String getProperty(String key, String defaultValue) v3)T getProperty(String key, Class<T> type) v4)T getProperty(String key, Class<T> type, T defaultValue) 2.2)稍微修改下源碼,當指定屬性不存在時,使用一個default value:
2.3)如果我們從屬性文件中得到的是一個String類型的值,那么在使用前還需要將其轉換為 integer,但是使用 getProperty()方法的重載形式,就能便利地解決這個問題:(干貨——getProperty()重載方法的作用) int connectionCount = env.getProperty("db.connection.count", Integer.class, 30); 3)Environment方法概覽(methods) method1)getRequiredProperty()方法:如果你希望這個屬性必須要定義;沒有定義會拋出異常; @Bean public Disc disc() {return new JayChou(env.getRequiredProperty("disc.title"),env.getRequiredProperty("disc.artist")); } method2)containsProperty()方法:檢查該屬性是否存在; boolean titleExists = env.containsProperty("disc.title"); method3)getPropertyAsClass()方法:如果想要吧屬性解析為類的話; Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class); method4)String[] getActiveProfiles():返回激活的profile名稱數組; method5)String[] getDefaultProfiles():返回默認的 profile 名稱 的數組; method6)boolean acceptsProfiles(String... profiles):如果 environment 支持給定的 profile的話,返回ture;
4)解析屬性占位符 4.1)intro:spring 一直支持將屬性定義到 外部的屬性文件中,并使用占位符將其插入到 spring bean中; 4.2)在spring裝配中,占位符的形式為 使用 "${...}" 包裝的屬性名稱; <bean id="sgtPeppers"class="soundsystem.BlankDisc"c:_title="${disc.title}"c:_artist="${disc.artist}" />4.3)如果我們依賴于組件掃描和自動裝配來創建和初始化應用組建的話,就沒有XML配置文件或者類了。此時,可以使用 @Value 注解,如下所示: public JayChou(@Value("${disc.title}") String title,@Value("${disc.artist}") String artist) { this.title = title; this.artist = artist; }4.4)為了使用占位符,必須要配置一個 PropertySourcesPlaceholderConfigurer(spring3.1推薦使用),因為它能夠基于spring Environment 及其屬性源來解析占位符;如下的@Bean方法配置了?PropertySourcesPlaceholderConfigurer @Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } 4.5)如果使用了XML配置?PropertySourcesPlaceholderConfigurer,推薦使用新元素 <context:property-placeholder>: <?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder /> </beans> Attention) A1)解析外部屬性能夠將值的處理推遲到 運行時,但是它的關注點在于根據名稱解析來自于 spring Environment和屬性源的屬性; A2)而 spring 表達式語言提供了一種更通用的方式在 運行時計算所要注入的值;
【5.2】 使用spring 表達式語言進行裝配 1)intro to spring表達式語言==Spring Expression Language, SpEL; 2)SpEL 擁有很多特性(characters): c1)使用bean的ID 來引用bean; c2)調用方法和訪問對象的屬性; c3)對值進行算術,關系和邏輯運算 ; c4)正則表達式匹配; c5)集合操作; 3)SpEL 荔枝 3.1)first task : 要知道將 SpEL 表達式放到 " #{...} "之中,這與屬性占位符有些類似,屬性占位符需要放到 " ${...} " 之中; 3.2)荔枝組團來襲 #{T(System).currentTimeMillis()} : 計算表達式的那一刻當前時間的毫秒數;T() 表達式會將java.lang.System 視為 java中對應的類型;因此可以調用其 static 修飾的currentTimeMillis方法; #{jaychou.artist}:得到id 為 jaychou 的bean 的artist屬性; #{systemProperties['disc.title']}:引用系統屬性; 4)裝配bean的時候如何使用這些表達式 4.1)通過組件掃描 創建bean的話,在注入屬性和構造器參數時,我們可以使用 @Value 注解,這與之前看到的屬性占位符有點類似,但現在我們要使用 SpEL表達式; 看個荔枝):從系統屬性中獲取專輯名稱和藝術家的名字; public BlankDisc(@Value("#{systemProperties['disc.title']}") String title,@Value("#{systemProperties['disc.artist']}") String artist) {this.title = title;this.artist = artist; }4.2)在XML配置中,可以將SpEL 表達式傳入 <property> or <constructor-arg> 的value屬性中,或者將其作為 p-命名空間或c-命名空間條目的值; 看個荔枝)構造器參數通過SePL 表達式設置; <bean id="sgtPeppers"class="soundsystem.BlankDisc"c:_title="#{systemProperties['disc.title']}"c:_artist="#{systemProperties['disc.artist']}" /> 5)SePL 表達式 表示字面量 組團荔枝) #{3.14159} == 表示浮點值; #{9.87E4} ==98700 #{'Hello'} ==計算String類型的字面值; #{false} ==boolean類型的值; 6)引用bean,屬性和方法(通過ID 引用其他bean) 組團荔枝)使用bean ID(jaychou) 作為SpEL 表達式; #{jaychou} #{jaychou.artist} // 對屬性(artist)的引用; #{jaychou.selectArtist()} // 調用bean上的方法; #{jaychou.selectArtist().toUpperCase()} // 對方法的連續調用; #{artistSelector.selectArtist()?.toUpperCase()} // 使用類型安全的運算符,當返回不為null時,才調用后面的方法; 7)在表達式中使用類型 7.1)T()運算符:如果要在 SpEL 中訪問類作用域的方法和常量的話,要依賴T() 這個關鍵的運算符;(干貨——T() 是?SpEL?中一個關鍵運算符) 組團荔枝,我們繼續) T(java.lang.Math) // 為了在SpEL 中表達java的Math類,可以像左側這樣使用 T() 運算符; T(java.lang.Math).PI // 將PI 值裝配到bean的屬性中; T(java.lang.Math).random() // 計算得到一個0~1 間的隨機數; 8)SpEL 運算符 8.1)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} or #{counter.total eq 100}// 數字比較; #{scoreboard.score > 1000 ? "Winner!" : "Loser"} //三元運算符的應用; #{disc.title ?: 'Rattle and Hum'}// Elvis運算符,表達式判斷disc.title是否為null,若是null的話,計算結果是 后面的字符串; 9)計算正則表達式 9.1)intro:SpEL 通過matches 運算符支持表達式中的模式匹配, 且matches() 函數會返回一個boolean 類型的值; #{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'} // 判斷一個字符串是否包含有效的郵件地址; 10)計算集合 組團荔枝來襲) #{jaychou.songs[4].title}//計算songs集合中第5個(基于零開始)元素的title屬性;(干貨——SpEL 計算集合時的start index等于0); #{jaychou.songs[T(java.lang.Math).random() * jaychou.songs.size()].title} // 隨機取集合某個下標的song的title屬性; #{'This is a test'[3]} //返回第4個字母(s); #{jaychou.songs.?[album eq '十二新作']}//SpEL 提供了查詢運算符,用于對集合的過濾,得到集合的一個子集;(返回jaychou的十二新作專輯下的所有songs) 10.1)SpEL 提供了兩個查詢運算符: ".^[]", ".$[]";分別用于查詢第一個匹配項和最后一個匹配項; #{jaychou.songs.^[album eq '十二新作']}//查找列表中第一個屬于十二新作專輯的歌曲; 10.2) SpEL提供了投影運算符:(.![]),它會從集合中的每個成員中選擇特定的屬性放到另一個集合中; #{jaychou.songs.![title]}//將title屬性投影到一個新的String類型的集合中; 10.3)投影操作可以和其它任意的 SpEL 運算符一起使用; #{jaychou.songs.?[album eq '十二新作'].![title]} // 獲得十二新作專輯下的所有歌曲名稱; // 難道沒有發現,上述表達式等同于 select ... where...
【1】環境與profile(考慮數據庫配置) 1)使用嵌入式數據庫 @Bean(destroyMethod="shutdown") public DataSource dataSource() {return new EmbeddedDatabaseBuilder().addScript("classpath:schema.sql").addScript("classpath:test-data.sql").build(); }2)使用JNDI從容器中獲取DataSource @Bean public DataSource dataSource() {JndiObjectFactoryBean jndiObjectFactoryBean =new JndiObjectFactoryBean();jndiObjectFactoryBean.setJndiName("jdbc/myDS");jndiObjectFactoryBean.setResourceRef(true);jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);return (DataSource) jndiObjectFactoryBean.getObject(); }3)選擇不同的DataSource配置 @Bean(destroyMethod="close") public DataSource dataSource() {BasicDataSource dataSource = new BasicDataSource();dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");dataSource.setDriverClassName("org.h2.Driver");dataSource.setUsername("sa");dataSource.setPassword("password");dataSource.setInitialSize(20);dataSource.setMaxActive(30);return dataSource; }Attention)我們必須要有一種方法來配置DataSource,使其在每種環境下都會選擇最為合適的配置;其中一種方式是在單獨的配置類(或XML 文件)中配置每個bean,然后在構建階段(可能會使用Maven的profiles)確定要將哪一個配置編譯到可部署的應用中;
problem+solution) problem)從開發階段遷移到QA 階段,重新構建可能不會出大問題,但從QA階段遷移到 生產階段,重新構建可能會引入bug; solution)Spring所提供 的 解決方案并不需要重新構建;
【1.1】配置 profile bean 1)intro:spring 在重新構建的過程中需要根據環境決定該創建那個 bean 和 不創建那個bean;不過spring 并不是在構建時做出決策,而是等待運行時再來確定;這樣的結果就是同一個部署單元能夠適用于所有的環境,沒有必要進行重新構建; 2)spring 引入了bean profile:要使用profile,首先要將所有不同的 bean 定義整理到一個或多個profile中,在將應用部署到每個環境時,要確保對應的 profile 處于激活狀態 ; 3)在javaConfig中,可以使用 @Profile 注解指定某個bean屬于哪一個profile;
看個荔枝)注解@Profile(干貨——注解@Profile的作用) 荔枝1)嵌入式數據庫可能會配置為如下形式: @Configuration @Profile("dev") public class DevelopmentProfileConfig { @Bean(destroyMethod="shutdown") public DataSource dataSource() {return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).addScript("classpath:schema.sql").addScript("classpath:test-data.sql").build();} }荔枝2)使用JNDI從容器中獲取DataSource 的 Profile配置 @Configuration @Profile("prod") public class ProductionProfileConfig {@Beanpublic DataSource dataSource() {JndiObjectFactoryBean jndiObjectFactoryBean =new JndiObjectFactoryBean();jndiObjectFactoryBean.setJndiName("jdbc/myDS");jndiObjectFactoryBean.setResourceRef(true);jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);return (DataSource) jndiObjectFactoryBean.getObject();} } 荔枝3)從spring3.2開始,@Profile注解既可以在方法級別上使用,也可以在類級別上使用;(還可以和 @Bean 注解一起使用)
Attention) A1)沒有定義在profile中的bean 都會被創建,無論profile 激活與否; A2)而定義在 profile中的bean,當且僅當對應的 profile 被激活時才可以創建;
4)在XML中配置 profile 4.1)通過<beans>元素的profile屬性,在XML 中配置profile bean; 4.2)還可以在根<beans>元素中嵌套定義 <beans> 元素,而不是為每個環境都創建一個 profile 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"> <beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:schema.sql" /> <jdbc:script location="classpath:test-data.sql" /> </jdbc:embedded-database> </beans><beans profile="qa"> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close" p:url="jdbc:h2:tcp://dbserver/~/test"p:driverClassName="org.h2.Driver" p:username="sa" p:password="password"p:initialSize="20"p:maxActive="30" /> </beans> <beans profile="prod"> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatabase" resource-ref="true" proxy-interface="javax.sql.DataSource" /> </beans> </beans> 【1.2】激活profile 1)spring在確定哪個 profile處于激活狀態時,需要依賴兩個獨立的屬性:?spring.profiles.active and spring.profiles.default ; 如果設置了 active屬性的話,相應的Profile被激活,否則,查看default屬性的值,如果default的值 沒有設置的話,那就會忽略掉 profile中的bean的創建; 2)多種方法設置上述兩個屬性; method1)作為 DispatcherServlet 的初始化參數; method2)作為web 應用的上下文參數; method3)作為 JNDI條目; method4)作為環境變量; method5)作為JVM的 系統屬性; method6)在集成測試類上, 使用 @ActiveProfiles 注解設置;(干貨——注解@ActiveProfile的作用) 原書作者的設置方法:喜歡用DispatcherServlet?的參數將spring.profiles.default屬性設置為開發環境的profile,會在Servlet上下文中進行設置; 看個荔枝)在web.xml 中設置 spring.profiles.default 屬性
Attention)?spring.profiles.active and spring.profiles.default 中的profile都是 復數形式: 這意味著可以同時激活多個 profile,列出多個profile 名稱,以逗號分隔來實現;
3)使用profile進行測試 3.1)intro:spring提供了 @ActiveProfiles注解,來指定測試時要激活哪個profile; 3.2)下面的荔枝展示了 使用?@ActiveProfiles注解 激活 dev profile;
【2】條件化bean 1)intro:當滿足給定條件時,才裝配相應的bean;組合@Conditional注解 和 @Bean注解;(干貨——注解@Conditional的作用)
對以上代碼的分析(Analysis): A1)matches() 方法:會得到 ConditionContext 和 AnnotatedTypeMetadata 對象用來做出決策; A2)ConditionContext是一個接口: public interface ConditionContext { BeanDefinitionRegistry getRegistry(); ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader();ClassLoader getClassLoader(); }A3)通過ConditionContext,可以做到如下幾點(works): work1)借助getRegistry方法返回的BeanDefinitionRegistry?檢查bean的定義; work2)借助getBeanFactory方法返回的ConfigurableListableBeanFactory 檢查bean是否存在,查看bean的屬性 work3)借助getEnvironment方法返回的Environment?檢查環境變量是否存在以及它的值是什么; work4)讀取并檢查getResourceLoader方法返回的ResourceLoader?所加載的資源; work5)借助getRegistry方法返回的BeanDefinitionRegistry?檢查bean的定義; work6)借助getClassLoader方法返回的ClassLoader?加載并檢查類是否存在; A4)AnnotatedTypeMetadata?能夠讓我們檢查帶有 @Bean 注解的方法上還有什么其他的注解; public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationName); // 判斷帶有@Bean注解的方法是不是還有其他的注解.Map<String, Object> getAnnotationAttributes(String annotationName); Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString); } 2)從spring4 開始,@Profile注解進行了重構,使其基于@Conditional 和 Condition實現:@Profile注解如下所示:
對以上代碼的分析(Analysis): A1)ProfileCondition 通過?AnnotatedTypeMetadata? 得到了用于 @Profile 注解的所有屬性; A2)根據 通過ConditionContext得到的 Environment 來檢查(acceptProfiles()方法)該profile 是否處于激活狀態;
【3】處理自動裝配的歧義性 1)problem+solution 1.1)problem測試用例:(error info: No qualifying bean of type [com.spring.chapter3.Disc] is defined: expected single matching bean but found 2: jayChou,leehom)
1.2)solution:spring 提供了多種可選方案來解決這樣的問題。你可以將可選的bean 中的某個設為首選(primary)的bean,或者使用限定符來幫助 spring 將可選的bean的范圍縮小到只有一個bean;
1.3)如果在XML 配置bean的話,設置primary屬性為 true來指定;
Attention)如果標識了多個首選(Primary)bean的話,@Primary注解就無法正常工作了;
【3.2】限定自動裝配的bean 1)problem+solution 1.1)problem:設置首選bean的 局限性: 在于@Primary 無法將可選方案的范圍限定到唯一一個無歧義性的可選項中。當首選bean的數量超過一個四,我們并沒有其他的方法來進一步縮小可選范圍; 1.2)solution:spring的限定符能夠在所有可選的bean上進行縮小范圍的操作,最終能夠達到一個bena 滿足所規定的限制條件;(使用@Qualifier注解) 看個荔枝)確保將jaychou 注入到CDPlayer中
2)創建自定義限定符:我們可以為bean設置自己的限定符,而不是依賴于將bean ID 作為限定符; @Component @Qualifier("cold") public class IceCream implements Dessert { ... }3)@Qualifier注解也可以和 @Bean注解一起使用 @Bean @Qualifier("cold") public Dessert iceCream() {return new IceCream(); } 4)使用自定義的限定符注解(干貨——開發人員自己創建限定符注解) 4.1)problem+solution @Component @Qualifier("fashion") public class JayChou implements Disc { ... } @Component @Qualifier("fashion") public class Leehom implements Disc { ... } 4.1.1)problem:現在我們有兩個帶有“fashion”限定符的唱片,在自動裝配Disc bean的時候,我們再次遇到了歧義性問題; 4.1.2)solution:需要更多的限定符來將可選范圍限定到只有一個bean;(多個 @Qualifier 注解) @Component @Qualifier("fashion") @Qualifier("cool") public class JayChou implements Disc { ... }@Component @Qualifier("fashion") @Qualifier("handsome") public class Leehom implements Disc { ... } 5)problem+solution:
5.1)problem:java不允許在同一個條目上重復使用出現相同類型的多個注解;因為這樣的話,編譯器會報錯;
5.2)solution:自定義限定符注解;(不能再干貨——創建自定義的限定符注解) 6)how to build diy @Qualifier annotaion. step1)不再使用?@Qualifier("fashion") 注解,使用自定義的 @Fashion這,該注解 的定義如下: @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Fashion{ } Attention) A1)自定義注解本身實際上就成為了 限定符注解; A2)通過聲明自定義的限定符注解,我們可以同時使用多個限定符,不會再有java 編譯器的限制或錯誤了; 7)所以,我們可以添加 @Component @Qualifier("fashion") @Qualifier("cool") public class JayChou implements Disc { ... } // 改為 @Component @Fashion // a qualifier annotaion. @Cool// a qualifier annotaion. public class JayChou implements Disc { ... }
(干貨——說白了,自定義限定符注解 就是多個標識而已,用于區別不同bean 和 對bean 進行分組(因為他們都屬于fashion 組))
【4】 bean的作用域 1)intro: default case下,spring應用上下文中所有 bean都是以單例的形式創建的;也就是說,不管給定一個bean被注入到其他bean中多少次,每次所注入的都是 同一個實例; 2)problem+solution:顯然,讓多個對象引用同一個bean 是不合理的,因為對象中的內容是易變的,一個對象對bean做了修改,這會波及到其他bean的; 3)spring定義了多種作用域,可以基于這些作用域創建bean,包括(scope): scope1)單例(Singleton):在整個應用中,只創建bean的一個實例;(干貨——默認情況下,bean以單例模式創建) scope2)原型(Prototype):每次注入或者通過spring應用上下文獲取的時候,都會創建一個新的 bean 實例; scope3)會話(Session):在web 應用中,為每個會話創建一個 bean實例; scope4)請求(Request):在web 應用中,為每個請求創建一個 bean實例; Attention) A1)default case下: 是單例作用域; A2)如果選擇其他作用域,要使用 @Scope注解,它可以和 @Component 或 @Bean 聯用;(干貨——@Scope注解的作用) A3)不管用哪一種方式來聲明原型作用域,每次注入或從 spring 應用上下文中檢索該bean的時候,都會創建新的實例; 看個荔枝)將其聲明為原型bean: @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Notepad { ... }用XML 來配置的話,代碼形式如下: <bean id="notepad"class="com.myapp.Notepad"scope="prototype" />
【4.1】使用會話和請求作用域 1)指定會話作用域: 使用 @Scope 注解,它的使用方式與 指定原型作用域是相同的; @Component @Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES) public ShoppingCart cart() { ... } 對以上代碼的分析(Analysis): A1)這會創建多個?ShoppingCart? bean的實例,但是對于給定的會話只會創建一個實例,在當前會話相關的操作中,這個bean 實際上相當于單例的; A2)@Scope注解:同時還有一個 proxyMode 屬性,它被設置成了?proxyMode=ScopedProxyMode.INTERFACES;這個屬性解決了將會話或請求作用域的bean 注入到單例bean中所遇到的問題; 2)proxyMode 代理模式所要解決的問題: 2.1)假設我們要將 ShoppingCart bean在注入到 單例 StoreService bean 的Setter 方法中,如下所示: @Component public class StoreService {@Autowiredpublic void setShoppingCart(ShoppingCart shoppingCart) {this.shoppingCart = shoppingCart;}... } 對以上代碼的分析(Analysis): A1)因為StoreService 是一個單例bean,會在spring 應用上下文加載的時候創建; A2)當它創建的時候,spring會試圖 將 ShoppingCart bean 注入到 setShoppingCart() 方法中;但是ShoppingCart bean 是會話作用域的,此時并不存在。直到某個client 進入系統,創建了會話后,才會出現?ShoppingCart 實例;(干貨——我此時才體會到了為什么需要懶加載) A3)而且,系統中有多個?ShoppingCart 實例(多個購物車):每個用戶一個。我們并不想讓spring注入 到某個固定的?ShoppingCart 實例到 StoreService中。我們希望的是當StoreService 處理ShoppingCart 功能時,它所使用的?ShoppingCart 實例恰好是當前會話所對應的那一個; A4)spring 并不會將實際的ShoppingCart bean 注入到 StoreService 中。spring 會注入一個到?ShoppingCart bean 的代理,如下圖所示。這個代理會暴露與?ShoppingCart 相同的方法,所以 StoreService 會認為他是一個ShoppingCart。但是,當StoreService 調用?ShoppingCart 的方法時,代理會對其進行懶解析并將調用委托給會話作用域內真正的?ShoppingCart bean; (干貨——懶加載的調用過程,引入了代理proxy)
3)如果 ShoppingCart 是接口不是類的話,這是可以的;但如果ShoppingCart 是具體的類,那么spring無法創建基于接口的代理了。這時,spring必須使用CGLIB 來生成基于類的代理; 3.1)所以,如果bean類型是具體的類的話,我們必須要將 proxyMode 屬性設置為 ScopedProxy-?Mode.TARGET_CLASS,以此來表明要以生成目標類擴展的方式創建代理;
【4.2】在XML 中聲明作用域代理 1)要設置代理模式,需要使用 spring aop 命名空間的一個新元素(<aop:scoped-proxy />?): <bean id="cart"class="com.myapp.ShoppingCart"scope="session"><aop:scoped-proxy /> </bean>2)為了使用?<aop:scoped-proxy /> 新元素,我們必須在 XML 配置中聲明spring 的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/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">... </beans> 【5】運行時值注入 1)problem+solution 1.1)problem:在實現的時候將值硬編碼在配置類中。用XML 裝配bean的話,同樣值也會是硬編碼的;
1.2)solution:為了避免硬編碼,而是想讓這些值在運行時再確定。spring 提供了兩種在運行時求值的方式(ways): way1)屬性占位符(Property placeholder) way2)spring 表達式語言(SpEL)
【5.1】注入外部的值
對以上代碼的分析(Analysis):這個屬性文件(app.properties)會加載到 spring 的 Environment中,稍后可以從這里通過 getProperty()方法 檢索屬性。
2)深入學習Spring的Environment 2.1)getProperty()方法有4個重載的變種形式(variant): v1)String getProperty(String key) v2)String getProperty(String key, String defaultValue) v3)T getProperty(String key, Class<T> type) v4)T getProperty(String key, Class<T> type, T defaultValue) 2.2)稍微修改下源碼,當指定屬性不存在時,使用一個default value:
2.3)如果我們從屬性文件中得到的是一個String類型的值,那么在使用前還需要將其轉換為 integer,但是使用 getProperty()方法的重載形式,就能便利地解決這個問題:(干貨——getProperty()重載方法的作用) int connectionCount = env.getProperty("db.connection.count", Integer.class, 30); 3)Environment方法概覽(methods) method1)getRequiredProperty()方法:如果你希望這個屬性必須要定義;沒有定義會拋出異常; @Bean public Disc disc() {return new JayChou(env.getRequiredProperty("disc.title"),env.getRequiredProperty("disc.artist")); } method2)containsProperty()方法:檢查該屬性是否存在; boolean titleExists = env.containsProperty("disc.title"); method3)getPropertyAsClass()方法:如果想要吧屬性解析為類的話; Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class); method4)String[] getActiveProfiles():返回激活的profile名稱數組; method5)String[] getDefaultProfiles():返回默認的 profile 名稱 的數組; method6)boolean acceptsProfiles(String... profiles):如果 environment 支持給定的 profile的話,返回ture;
4)解析屬性占位符 4.1)intro:spring 一直支持將屬性定義到 外部的屬性文件中,并使用占位符將其插入到 spring bean中; 4.2)在spring裝配中,占位符的形式為 使用 "${...}" 包裝的屬性名稱; <bean id="sgtPeppers"class="soundsystem.BlankDisc"c:_title="${disc.title}"c:_artist="${disc.artist}" />4.3)如果我們依賴于組件掃描和自動裝配來創建和初始化應用組建的話,就沒有XML配置文件或者類了。此時,可以使用 @Value 注解,如下所示: public JayChou(@Value("${disc.title}") String title,@Value("${disc.artist}") String artist) { this.title = title; this.artist = artist; }4.4)為了使用占位符,必須要配置一個 PropertySourcesPlaceholderConfigurer(spring3.1推薦使用),因為它能夠基于spring Environment 及其屬性源來解析占位符;如下的@Bean方法配置了?PropertySourcesPlaceholderConfigurer @Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } 4.5)如果使用了XML配置?PropertySourcesPlaceholderConfigurer,推薦使用新元素 <context:property-placeholder>: <?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder /> </beans> Attention) A1)解析外部屬性能夠將值的處理推遲到 運行時,但是它的關注點在于根據名稱解析來自于 spring Environment和屬性源的屬性; A2)而 spring 表達式語言提供了一種更通用的方式在 運行時計算所要注入的值;
【5.2】 使用spring 表達式語言進行裝配 1)intro to spring表達式語言==Spring Expression Language, SpEL; 2)SpEL 擁有很多特性(characters): c1)使用bean的ID 來引用bean; c2)調用方法和訪問對象的屬性; c3)對值進行算術,關系和邏輯運算 ; c4)正則表達式匹配; c5)集合操作; 3)SpEL 荔枝 3.1)first task : 要知道將 SpEL 表達式放到 " #{...} "之中,這與屬性占位符有些類似,屬性占位符需要放到 " ${...} " 之中; 3.2)荔枝組團來襲 #{T(System).currentTimeMillis()} : 計算表達式的那一刻當前時間的毫秒數;T() 表達式會將java.lang.System 視為 java中對應的類型;因此可以調用其 static 修飾的currentTimeMillis方法; #{jaychou.artist}:得到id 為 jaychou 的bean 的artist屬性; #{systemProperties['disc.title']}:引用系統屬性; 4)裝配bean的時候如何使用這些表達式 4.1)通過組件掃描 創建bean的話,在注入屬性和構造器參數時,我們可以使用 @Value 注解,這與之前看到的屬性占位符有點類似,但現在我們要使用 SpEL表達式; 看個荔枝):從系統屬性中獲取專輯名稱和藝術家的名字; public BlankDisc(@Value("#{systemProperties['disc.title']}") String title,@Value("#{systemProperties['disc.artist']}") String artist) {this.title = title;this.artist = artist; }4.2)在XML配置中,可以將SpEL 表達式傳入 <property> or <constructor-arg> 的value屬性中,或者將其作為 p-命名空間或c-命名空間條目的值; 看個荔枝)構造器參數通過SePL 表達式設置; <bean id="sgtPeppers"class="soundsystem.BlankDisc"c:_title="#{systemProperties['disc.title']}"c:_artist="#{systemProperties['disc.artist']}" /> 5)SePL 表達式 表示字面量 組團荔枝) #{3.14159} == 表示浮點值; #{9.87E4} ==98700 #{'Hello'} ==計算String類型的字面值; #{false} ==boolean類型的值; 6)引用bean,屬性和方法(通過ID 引用其他bean) 組團荔枝)使用bean ID(jaychou) 作為SpEL 表達式; #{jaychou} #{jaychou.artist} // 對屬性(artist)的引用; #{jaychou.selectArtist()} // 調用bean上的方法; #{jaychou.selectArtist().toUpperCase()} // 對方法的連續調用; #{artistSelector.selectArtist()?.toUpperCase()} // 使用類型安全的運算符,當返回不為null時,才調用后面的方法; 7)在表達式中使用類型 7.1)T()運算符:如果要在 SpEL 中訪問類作用域的方法和常量的話,要依賴T() 這個關鍵的運算符;(干貨——T() 是?SpEL?中一個關鍵運算符) 組團荔枝,我們繼續) T(java.lang.Math) // 為了在SpEL 中表達java的Math類,可以像左側這樣使用 T() 運算符; T(java.lang.Math).PI // 將PI 值裝配到bean的屬性中; T(java.lang.Math).random() // 計算得到一個0~1 間的隨機數; 8)SpEL 運算符 8.1)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} or #{counter.total eq 100}// 數字比較; #{scoreboard.score > 1000 ? "Winner!" : "Loser"} //三元運算符的應用; #{disc.title ?: 'Rattle and Hum'}// Elvis運算符,表達式判斷disc.title是否為null,若是null的話,計算結果是 后面的字符串; 9)計算正則表達式 9.1)intro:SpEL 通過matches 運算符支持表達式中的模式匹配, 且matches() 函數會返回一個boolean 類型的值; #{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'} // 判斷一個字符串是否包含有效的郵件地址; 10)計算集合 組團荔枝來襲) #{jaychou.songs[4].title}//計算songs集合中第5個(基于零開始)元素的title屬性;(干貨——SpEL 計算集合時的start index等于0); #{jaychou.songs[T(java.lang.Math).random() * jaychou.songs.size()].title} // 隨機取集合某個下標的song的title屬性; #{'This is a test'[3]} //返回第4個字母(s); #{jaychou.songs.?[album eq '十二新作']}//SpEL 提供了查詢運算符,用于對集合的過濾,得到集合的一個子集;(返回jaychou的十二新作專輯下的所有songs) 10.1)SpEL 提供了兩個查詢運算符: ".^[]", ".$[]";分別用于查詢第一個匹配項和最后一個匹配項; #{jaychou.songs.^[album eq '十二新作']}//查找列表中第一個屬于十二新作專輯的歌曲; 10.2) SpEL提供了投影運算符:(.![]),它會從集合中的每個成員中選擇特定的屬性放到另一個集合中; #{jaychou.songs.![title]}//將title屬性投影到一個新的String類型的集合中; 10.3)投影操作可以和其它任意的 SpEL 運算符一起使用; #{jaychou.songs.?[album eq '十二新作'].![title]} // 獲得十二新作專輯下的所有歌曲名稱; // 難道沒有發現,上述表達式等同于 select ... where...
總結
以上是生活随笔為你收集整理的spring(3)高级装配的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: img absolute 怎么居中(怎么
- 下一篇: tomcat(18)部署器