阅读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