Spring4.2新特性(一)

已經發在了併發編程網: http://ifeve.com/spring4-2/html

1. 簡介.java

    前些天spring4.2出來了, 從GA開始就一直在跟了, 前2天看完了全部Release Notes, 以爲記錄下我比較感興趣的特性.spring


    官方的Release Notes:shell

    https://jira.spring.io/browse/SPR?selectedTab=com.atlassian.jira.jira-projects-plugin:changelog-panel&allVersions=true數據庫

    我看的是4.2GA, 4.2RC3, 4.2RC2, 4.2RC1.編程


    4.0和4.1的新特性, 能夠看看濤哥的博客: 併發

    http://jinnianshilongnian.iteye.com/blog/1989381mvc

    http://jinnianshilongnian.iteye.com/blog/2103752 app


    這裏主要是講照官方文檔裏面列的, changelog裏面太多了 -.-!異步

    http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#new-in-4.2




2. 核心改進.

    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<String, Main> 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;
    }
}


    輸出: {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 {}
}


    輸出: 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;
    }
}


    輸出: 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 {
}


    輸出: 

    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 <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }
}


    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<TestModel> event) {
        System.out.println(event.getPayload().getName());
        //這裏能夠記錄日誌, 發送消息等操做.
        //這裏拋出異常, 會致使addTestModel方法異常, 但不會回滾事務.
        //注意, ApplicationEventPublisher不能使用線程池, 不然不會執行到這裏
        //由於, 包裝類是經過ThreadLocal來判斷當前是否有活動的事務信息.
        //TransactionalEventListener.fallbackExecution就是爲了決定當當前線程沒有事務上下文時, 
        //是否還調用 handle 方法, 默認不調用.
    }
}


    結果, 當調用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 "";
}


    能夠看到, 可讓註解中讓人困惑的value更加讓人理解, Spring4.2中大量的註解都爲value添加了別名.



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

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




4. 總結

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

    mvc的接下篇.

相關文章
相關標籤/搜索