Spring4.2新特性(一)

jopen 9年前發布 | 21K 次閱讀 Spring

1. 簡介.

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

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


  1. 核心改進. </p>

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

    @Configuration
    public class Main implements DefaultIface {

    public String name = "main";

    public static void main(String[] args) {

     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
    
     //會有兩個Main實例, 一個是config實例, 用來做配置解析, 一個是我們@Bean注解的實例.
     Map&lt;String, Main&gt; bean = context.getBeansOfType(Main.class);
     System.out.println(bean);
    
     context.close();
    

    }

    @Override public String toString() {

     return "Main [name=" + name + "]";
    

    } }

interface DefaultIface {

@Bean
default Main getMain() {
    Main main = new Main();
    main.name = "iface";
    return main;
}

}</pre>

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

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

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

@Import(Main.Dao.class)
@Configuration
public class Main {

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

    Main.Dao bean = context.getBean(Main.Dao.class);
    System.out.println(bean);

    context.close();
}

public static class Dao {}

}</pre>

輸出: 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配置等).

@Order(2)
@Configuration
public class Main {

String name;

@Bean
public Main getMain() {
    Main main = new Main();
    main.name = "main";
    return main;
}

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class, SubMain.class);

    Main bean = context.getBean("getMain", Main.class);

    //@Order值大的, 會覆蓋值小的, 比如如果submain的order為3, main的order為2時, 輸出submain
    System.out.println(bean.name);

    context.close();
}

}

@Order(3) @Configuration class SubMain {

@Bean
public Main getMain() {
    Main main = new Main();
    main.name = "submain";
    return main;
}

}</pre>

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

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

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

@Import(ScopedBean.class)
@Configuration
public class Main {

@Lazy @Resource ScopedBean bean;

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

    Main bean = context.getBean(Main.class);

    //如果bean上沒有@Lazy注解, 則2個獲取的bean是一個實例, 加了@Lazy注解后, 則2次獲取的是2個實例
    System.out.println(bean.bean);
    System.out.println(bean.bean);

    context.close();
}

}

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) class ScopedBean { }</pre>

輸出:

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)現在可以這么寫.

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

@Import(AppContextUtil.class)
@Configuration
public class Main {

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

    Main bean = AppContextUtil.getBean(Main.class);
    System.out.println(bean);//輸出:com.haogrgr.test.main.Main$$EnhancerBySpringCGLIB$$10ba9cf8@4ae3c1cd

    context.close();
}

}

@Component class AppContextUtil {

private static ApplicationContext context = null;

@EventListener
public void setApplicationContext(ContextRefreshedEvent eve) {
    context = eve.getApplicationContext();
}

public static &lt;T&gt; T getBean(Class&lt;T&gt; clazz) {
    return context.getBean(clazz);
}

}</pre>

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

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

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

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

@Service
public class TransactionEventTestService {

@Resource
private TestMapper mapper;

@Resource
private ApplicationEventPublisher publisher;

@Transactional
public void addTestModel() {
    TestModel model = new TestModel();
    model.setName("haogrgr");
    mapper.insert(model);

    //如果model沒有繼承ApplicationEvent, 則內部會包裝為PayloadApplicationEvent
    //對于@TransactionalEventListener, 會在事務提交后才執行Listener處理邏輯.
    //
    //發布事件, 事務提交后, 記錄日志, 或發送消息等操作
    publisher.publishEvent(model);
}
//當事務提交后, 才會真正的執行@TransactionalEventListener配置的Listener, 如果Listener拋異常, 方法返回失敗, 但事務不會回滾.

}

@Component public class TransactionEventListener {

@TransactionalEventListener
public void handle(PayloadApplicationEvent&lt;TestModel&gt; event) {
    System.out.println(event.getPayload().getName());
    //這里可以記錄日志, 發送消息等操作.
    //這里拋出異常, 會導致addTestModel方法異常, 但不會回滾事務.
    //注意, ApplicationEventPublisher不能使用線程池, 否則不會執行到這里
    //因為, 包裝類是通過ThreadLocal來判斷當前是否有活動的事務信息.
    //TransactionalEventListener.fallbackExecution就是為了決定當當前線程沒有事務上下文時,
    //是否還調用 handle 方法, 默認不調用.
}

}</pre>

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

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

@MainBean(beanName = "mainbean")
public class Main {

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

    String[] beannames = context.getBeanNamesForType(Main.class);

    //當加上@AliasFor時, 輸出"mainbean"
    //當去掉@AliasFor注解后, 輸出"main"
    System.out.println(beannames[0]);

    context.close();
}

}

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration @interface MainBean {

@AliasFor(annotation = Component.class, attribute = "value")
String beanName() default "";

}</pre>

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

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

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


4. 總結

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

原創文章,轉載請注明: 轉載自并發編程網 – ifeve.com

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!