閱讀544 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Spring4.2新特性(一)

1. 簡介.

前些天spring4.2出來了, 從GA開始就一直在跟了, 前2天看完了所有官方Release Notes, 覺得記錄下我比較感興趣的特性.

我看的是4.2GA, 4.2RC3, 4.2RC2, 4.2RC1。4.04.1的新特性, 可以看看濤哥的博客。這裏主要是講照官方文檔裏麵列的, changelog裏麵太多了 -.-!


2. 核心改進.

1) @Bean能注解在Java8默認方法上了, 例如:

01 @Configuration
02 public class Main implements DefaultIface {
03  
04     public String name = "main";
05  
06     public static void main(String[] args) {
07         AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(Main.class);
08  
09         //會有兩個Main實例, 一個是config實例, 用來做配置解析, 一個是我們@Bean注解的實例.
10         Map<String, Main> bean = context.getBeansOfType(Main.class);
11         System.out.println(bean);
12  
13         context.close();
14     }
15  
16     @Override
17     public String toString() {
18         return "Main [name=" + name + "]";
19     }
20 }
21  
22 interface DefaultIface {
23  
24     @Bean
25     default Main getMain() {
26         Main main = new Main();
27         main.name = "iface";
28         return main;
29     }
30 }

輸出: {main=Main [name=main], getMain=Main [name=iface]}

可以看到, 我們注解在Java8默認方法上的@Bean注解已經生效了.

2) 配置類上的@Import以前隻能引入配置類(注解了@Configuration等的類), 現在可以引入一般的組件了, 比如啥注解都沒有的類.

01 @Import(Main.Dao.class)
02 @Configuration
03 public class Main {
04  
05     public static void main(String[] args) {
06         AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(Main.class);
07  
08         Main.Dao bean = context.getBean(Main.Dao.class);
09         System.out.println(bean);
10  
11         context.close();
12     }
13  
14     public static class Dao {}
15 }

輸出: com.haogrgr.test.main.Main$Dao@7f77e91b.

在4.2之前, 會報如下錯誤:

Exception in thread “main” org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: com.haogrgr.test.main.Main$Dao was @Import’ed but is not annotated with @Configuration nor does it declare any @Bean methods; it does not implement ImportSelector or extend ImportBeanDefinitionRegistrar. Update the class to meet one of these requirements or do not attempt to @Import it.
Offending resource: class com.haogrgr.test.main.Main$Dao
at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:70)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.registerBeanDefinitionForImportedConfigurationClass(ConfigurationClassBeanDefinitionReader.java:164)

3)配置類上現在可以注解@Order了, 使其能按預期的順序來處理, 比如(通過名字來覆蓋Bean配置等).

01 @Order(2)
02 @Configuration
03 public class Main {
04  
05     String name;
06  
07     @Bean
08     public Main getMain() {
09         Main main = new Main();
10         main.name = "main";
11         return main;
12     }
13  
14     public static void main(String[] args) {
15         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class, SubMain.class);
16  
17         Main bean = context.getBean("getMain", Main.class);
18  
19         //@Order值大的, 會覆蓋值小的, 比如如果submain的order為3, main的order為2時, 輸出submain
20         System.out.println(bean.name);
21  
22         context.close();
23     }
24 }
25  
26 @Order(3)
27 @Configuration
28 class SubMain {
29  
30     @Bean
31     public Main getMain() {
32         Main main = new Main();
33         main.name = "submain";
34         return main;
35     }
36 }

輸出: submain, 可以通過修改Order的值, 來使輸出為 main.

注: 4.2之前, 是根據AnnotationConfigApplicationContext(Main.class, SubMain.class) 初始化時參數的順序來處理的.

4) @Resource注解的元素, 現在可以配合@Lazy, 和@Autowired一樣, 注入代理類, 來代理對應bean的請求.

01 @Import(ScopedBean.class)
02 @Configuration
03 public class Main {
04  
05     @Lazy @Resource ScopedBean bean;
06  
07     public static void main(String[] args) {
08         AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(Main.class);
09  
10         Main bean = context.getBean(Main.class);
11  
12         //如果bean上沒有@Lazy注解, 則2個獲取的bean是一個實例, 加了@Lazy注解後, 則2次獲取的是2個實例
13         System.out.println(bean.bean);
14         System.out.println(bean.bean);
15  
16         context.close();
17     }
18 }
19  
20 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
21 class ScopedBean {
22 }

輸出:

1. 沒加@Lazy時:

com.haogrgr.test.main.ScopedBean@525f1e4e

com.haogrgr.test.main.ScopedBean@525f1e4e

2. 加了@Lazy後:

com.haogrgr.test.main.ScopedBean@6293abcc

com.haogrgr.test.main.ScopedBean@7995092a

可以看到, 主要是為了方便實現Scope代理(或延遲獲取, 比如注入時還沒初始化等)情況, 也就是當singleton引用prototype時, 就需要@Lazy.

5) application event那套現在提供注解支持了, 比如以前常用的AppContextUtil(獲取Context, 提供靜態方法獲取bean)現在可以這麼寫.

具體可以看這篇文章: https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2

01 @Import(AppContextUtil.class)
02 @Configuration
03 public class Main {
04  
05     public static void main(String[] args) {
06         AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(Main.class);
07  
08         Main bean = AppContextUtil.getBean(Main.class);
09         System.out.println(bean);//輸出:com.haogrgr.test.main.Main$$EnhancerBySpringCGLIB$$10ba9cf8@4ae3c1cd
10  
11         context.close();
12     }
13 }
14  
15 @Component
16 class AppContextUtil {
17  
18     private static ApplicationContext context = null;
19  
20     @EventListener
21     public void setApplicationContext(ContextRefreshedEvent eve) {
22         context = eve.getApplicationContext();
23     }
24  
25     public static <T> T getBean(Class<T> clazz) {
26         return context.getBean(clazz);
27     }
28 }

EventListener的屬性value和classes一樣, 都是用來指定要處理的事件, condition屬性可以使用spel來過濾event

還一個就是@TransactionalEventListener, 可以方便我在事務周期內處理一些事情, 比如事務提交後觸發某一事件.

一個場景就是, 當插入記錄提交事務後, 異步發送消息到其他係統, 或本地記錄日誌等操作, 現在可以通過TransactionalEventListener來做了.

注: 下麵的代碼僅供參考, 如果要運行, 自己搭一個數據庫環境吧, 這裏隻貼了相關的代碼.

01 @Service
02 public class TransactionEventTestService {
03  
04     @Resource
05     private TestMapper mapper;
06  
07     @Resource
08     private ApplicationEventPublisher publisher;
09  
10     @Transactional
11     public void addTestModel() {
12         TestModel model = new TestModel();
13         model.setName("haogrgr");
14         mapper.insert(model);
15  
16         //如果model沒有繼承ApplicationEvent, 則內部會包裝為PayloadApplicationEvent
17         //對於@TransactionalEventListener, 會在事務提交後才執行Listener處理邏輯.
18         //
19         //發布事件, 事務提交後, 記錄日誌, 或發送消息等操作
20         publisher.publishEvent(model);
21     }
22     //當事務提交後, 才會真正的執行@TransactionalEventListener配置的Listener, 如果Listener拋異常, 方法返回失敗, 但事務不會回滾.
23  
24 }
25  
26 @Component
27 public class TransactionEventListener {
28  
29     @TransactionalEventListener
30     public void handle(PayloadApplicationEvent<TestModel> event) {
31         System.out.println(event.getPayload().getName());
32         //這裏可以記錄日誌, 發送消息等操作.
33         //這裏拋出異常, 會導致addTestModel方法異常, 但不會回滾事務.
34         //注意, ApplicationEventPublisher不能使用線程池, 否則不會執行到這裏
35         //因為, 包裝類是通過ThreadLocal來判斷當前是否有活動的事務信息.
36         //TransactionalEventListener.fallbackExecution就是為了決定當當前線程沒有事務上下文時,
37         //是否還調用 handle 方法, 默認不調用.
38     }
39 }

結果, 當調用addTestModel() 時, 會輸出”haogrgr”。官方說的比較少, 看了下源碼才知道怎麼用, 內部是包裝一下@TransactionalEventListener注解的方法,添加了一個適配器, ApplicationListenerMethodTransactionalAdapter,內部通過TransactionSynchronizationManager.registerSynchronization 注冊一個同步器發布事務時, 記下event, 然後注冊一個同步器TransactionSynchronizationEventAdapter,當事務提交後, TransactionSynchronizationManager會回調上麵注冊的同步適配器,這裏注冊就是放入到一個ThreadLocal裏麵, 通過它來透傳參數。這時,TransactionSynchronizationEventAdapter內部才會真正的去調用handle方法.

6) 提供@AliasFor注解, 來給注解的屬性起別名, 讓使用注解時, 更加的容易理解(比如給value屬性起別名, 更容易讓人理解).

01 @MainBean(beanName = "mainbean")
02 public class Main {
03  
04     public static void main(String[] args) {
05         AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(Main.class);
06  
07         String[] beannames = context.getBeanNamesForType(Main.class);
08  
09         //當加上@AliasFor時, 輸出"mainbean"
10         //當去掉@AliasFor注解後, 輸出"main"
11         System.out.println(beannames[0]);
12  
13         context.close();
14     }
15 }
16  
17 @Target(ElementType.TYPE)
18 @Retention(RetentionPolicy.RUNTIME)
19 @Documented
20 @Configuration
21 @interface MainBean {
22  
23     @AliasFor(annotation = Component.class, attribute = "value")
24     String beanName() default "";
25 }

可以看到, 可以讓注解中讓人困惑的value更加讓人理解, Spring4.2中大量的注解都為value添加了別名.

7) 其他一些的改進, 不細說了, 主要是內部的改進, Java8的Stream, 日期等支持, javax.money等支持,

commons-pool2支持, 腳本加強等, Hibernate5支持, JMS增強 等等等等.

4. 總結

Spring4.2提供了更多的注解支持。

最後更新:2017-05-22 14:32:27

  上一篇:go  Java Date Time 教程-java.sql.Date
  下一篇:go  讓你的 Linux 遠離黑客(三):FAQ