spring4新特性杂谈

Spring4新特性——泛型限定式依赖注入

Spring4新特性——核心容器的其他改进

Spring4新特性——Web开发的增强

Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC 

Spring4新特性——Groovy Bean定义DSL

Spring4新特性——更好的Java泛型操作API 

Spring4新特性——JSR310日期API的支持

Spring4新特性——注解、脚本、任务、MVC等其他特性改进 


Spring4新特性——泛型限定式依赖注入

Spring 4.0已经发布RELEASE版本,不仅支持Java8,而且向下兼容到JavaSE6/JavaEE6,并移出了相关废弃类,新添加如Java8的支持、Groovy式Bean定义DSL、对核心容器进行增强、对Web框架的增强、Websocket模块的实现、测试的增强等。其中两个我一直想要的增强就是:支持泛型依赖注入、对cglib类代理不再要求必须有空参构造器了。具体更新请参考:

http://docs.spring.io/spring/docs/4.0.0.RELEASE/spring-framework-reference/htmlsingle/#new-in-4.0

 

1、相关代码:

1.1、实体

Java代码   收藏代码
  1. public class User implements Serializable {  
  2.     private Long id;  
  3.     private String name;  
  4. }  
  5.   
  6. public class Organization implements Serializable {  
  7.     private Long id;  
  8.     private String name;  
  9. }  

 1.2、Repository

Java代码   收藏代码
  1. public abstract class BaseRepository<M extends Serializable> {  
  2.     public void save(M m) {  
  3.         System.out.println("=====repository save:" + m);  
  4.     }  
  5. }  
  6.   
  7. @Repository  
  8. public class UserRepository extends BaseRepository<User> {  
  9. }  
  10.   
  11. @Repository  
  12. public class OrganizationRepository extends BaseRepository<Organization> {  
  13. }  

 对于Repository,我们一般是这样实现的:首先写一个模板父类,把通用的crud等代码放在BaseRepository;然后子类继承后,只需要添加额外的实现。

 

1.3、Service

1.3.1、以前Service写法

Java代码   收藏代码
  1. public abstract class BaseService<M extends Serializable> {  
  2.     private BaseRepository<M> repository;  
  3.     public void setRepository(BaseRepository<M> repository) {  
  4.         this.repository = repository;  
  5.     }  
  6.     public void save(M m) {  
  7.         repository.save(m);  
  8.     }  
  9. }  
  10. @Service  
  11. public class UserService extends BaseService<User> {  
  12.     @Autowired  
  13.     public void setUserRepository(UserRepository userRepository) {  
  14.         setRepository(userRepository);  
  15.     }  
  16. }  
  17.   
  18. @Service  
  19. public class OrganizationService extends BaseService<Organization> {  
  20.     @Autowired  
  21.     public void setOrganizationRepository(OrganizationRepository organizationRepository) {  
  22.         setRepository(organizationRepository);  
  23.     }  
  24. }  

 

可以看到,以前必须再写一个setter方法,然后指定注入的具体类型,然后进行注入;

 

1.3.2、泛型Service的写法

Java代码   收藏代码
  1. public abstract class BaseService<M extends Serializable> {  
  2.     @Autowired  
  3.     protected BaseRepository<M> repository;  
  4.   
  5.     public void save(M m) {  
  6.         repository.save(m);  
  7.     }  
  8. }  
  9.   
  10. @Service  
  11. public class UserService extends BaseService<User> {  
  12. }  
  13.   
  14. @Service  
  15. public class OrganizationService extends BaseService<Organization> {  
  16. }  

 

 大家可以看到,现在的写法非常简洁。支持泛型式依赖注入。

 

这个也是我之前非常想要的一个功能,这样对于那些基本的CRUD式代码,可以简化更多的代码。

 

 

如果大家用过Spring data jpa的话,以后注入的话也可以使用泛型限定式依赖注入 :

Java代码   收藏代码
  1. @Autowired  
  2. private Repository<User> userRepository;  

 

 对于泛型依赖注入,最好使用setter注入,这样万一子类想变,比较容易切换。比如https://github.com/zhangkaitao/es,如果有多个实现时,子类可以使用@Qualifier指定使用哪一个。


Spring4新特性——核心容器的其他改进

1、Map依赖注入:

Java代码   收藏代码
  1. @Autowired  
  2. private Map<String, BaseService> map;  

这样会注入:key是bean名字;value就是所有实现了BaseService的Bean,假设使用上一篇的例子,则会得到:

{organizationService=com.sishuok.spring4.service.OrganizationService@617029, userService=com.sishuok.spring4.service.UserService@10ac73b}

 

2、List/数组注入:

Java代码   收藏代码
  1. @Autowired  
  2. private List<BaseService> list;  

 这样会注入所有实现了BaseService的Bean;但是顺序是不确定的,如果我们想要按照某个顺序获取;在Spring4中可以使用@Order或实现Ordered接口来实现,如:

Java代码   收藏代码
  1. @Order(value = 1)  
  2. @Service  
  3. public class UserService extends BaseService<User> {  
  4. }  

这种方式在一些需要多态的场景下是非常有用的。

 

3、@Lazy可以延迟依赖注入:

Java代码   收藏代码
  1. @Lazy  
  2. @Service  
  3. public class UserService extends BaseService<User> {  
  4. }  
Java代码   收藏代码
  1. @Lazy  
  2. @Autowired  
  3. private UserService userService;  

 我们可以把@Lazy放在@Autowired之上,即依赖注入也是延迟的;当我们调用userService时才会注入。即延迟依赖注入到使用时。同样适用于@Bean。

 

4、@Conditional

@Conditional类似于@Profile(一般用于如我们有开发环境、测试环境、正式机环境,为了方便切换不同的环境可以使用@Profile指定各个环境的配置,然后通过某个配置来开启某一个环境,方便切换,但是@Conditional的优点是允许自己定义规则。可以指定在如@Component、@Bean、@Configuration等注解的类上,以绝对Bean是否创建等。首先来看看使用@Profile的用例,假设我们有个用户模块:

1、在测试/开发期间调用本机的模拟接口方便开发;

2、在部署到正式机时换成调用远程接口;

Java代码   收藏代码
  1. public abstract class UserService extends BaseService<User> {  
  2. }  
  3.   
  4. @Profile("local")  
  5. @Service  
  6. public class LocalUserService extends UserService {  
  7. }  
  8.   
  9. @Profile("remote")  
  10. @Service  
  11. public class RemoteUserService extends UserService {  
  12. }  

我们在写测试用例时,可以指定我们使用哪个Profile:

Java代码   收藏代码
  1. @ActiveProfiles("remote")  
  2. @RunWith(SpringJUnit4ClassRunner.class)  
  3. @ContextConfiguration(locations =  "classpath:spring-config.xml")  
  4. public class ServiceTest {  
  5.   
  6.     @Autowired  
  7.     private UserService userService;  
  8. }  

  这种方式非常简单。如果想自定义如@Profile之类的注解等,那么@Conditional就派上用场了;假设我们系统中有好多本地/远程接口,那么我们定义两个注解@Local和@Remote注解要比使用@Profile方便的多;如:

 

Java代码   收藏代码
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target({ElementType.TYPE, ElementType.METHOD})  
  3. @Conditional(CustomCondition.class)  
  4. public @interface Local {  
  5. }  
  6.   
  7. @Retention(RetentionPolicy.RUNTIME)  
  8. @Target({ElementType.TYPE, ElementType.METHOD})  
  9. @Conditional(CustomCondition.class)  
  10. public @interface Remote {  
  11. }  

 

Java代码   收藏代码
  1. public class CustomCondition implements Condition {  
  2.   
  3.     @Override  
  4.     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {  
  5.         boolean isLocalBean = metadata.isAnnotated("com.sishuok.spring4.annotation.Local");  
  6.         boolean isRemoteBean = metadata.isAnnotated("com.sishuok.spring4.annotation.Remote");  
  7.         //如果bean没有注解@Local或@Remote,返回true,表示创建Bean  
  8.         if(!isLocalBean && !isRemoteBean) {  
  9.             return true;  
  10.         }  
  11.   
  12.         boolean isLocalProfile = context.getEnvironment().acceptsProfiles("local");  
  13.   
  14.         //如果profile=local 且 bean注解了@Local,则返回true 表示创建bean;  
  15.         if(isLocalProfile) {  
  16.             return isLocalBean;  
  17.         }  
  18.   
  19.         //否则默认返回注解了@Remote或没有注解@Remote的Bean  
  20.         return isRemoteBean;  
  21.     }  
  22. }  

 

 然后我们使用这两个注解分别注解我们的Service:

Java代码   收藏代码
  1. @Local  
  2. @Service  
  3. public class LocalUserService extends UserService {  
  4. }  

 

Java代码   收藏代码
  1. @Remote  
  2. @Service  
  3. public class RemoteUserService extends UserService {  
  4. }  

 

首先在@Local和@Remote注解上使用@Conditional(CustomCondition.class)指定条件,然后使用@Local和@Remote注解我们的Service,这样当加载Service时,会先执行条件然后判断是否加载为Bean。@Profile就是这样实现的,其Condition是:org.springframework.context.annotation.ProfileCondition。可以去看下源码,很简单。

 

5、基于CGLIB的类代理不再要求类必须有空参构造器了:

这是一个很好的特性,使用构造器注入有很多好处,比如可以只在创建Bean时注入依赖,然后就不变了,如果使用setter注入,是允许别人改的。当然我们可以使用spring的字段级别注入。如果大家使用过如Shiro,我们可能要对Controller加代理。如果是类级别代理,此时要求Controller必须有空参构造器,有时候挺烦人的。spring如何实现的呢?其内联了objenesis类库,通过它来实现,可以去其官网看看介绍。这样就支持如下方式的构造器注入了:

 

Java代码   收藏代码
  1. @Controller  
  2. public class UserController {  
  3.     private UserService userService;  
  4.     @Autowired  
  5.     public UserController(UserService userService) {  
  6.         this.userService = userService;  
  7.     }  
  8. }  

 

org.springframework.cglib.proxy.Enhancer在其github和maven仓库中的source中竟然木有,其github:https://github.com/spring-projects/spring-framework/tree/master/spring-core/src/main/java/org/springframework/cglib;难道忘了吗?


Spring4新特性——Web开发的增强

从Spring4开始,Spring以Servlet3为进行开发,如果用Spring MVC 测试框架的话需要指定Servlet3兼容的jar包(因为其Mock的对象都是基于Servlet3的)。另外为了方便Rest开发,通过新的@RestController指定在控制器上,这样就不需要在每个@RequestMapping方法上加 @ResponseBody了。而且添加了一个AsyncRestTemplate ,支持REST客户端的异步无阻塞支持。

 

1、@RestController

Java代码   收藏代码
  1. @RestController  
  2. public class UserController {  
  3.     private UserService userService;  
  4.     @Autowired  
  5.     public UserController(UserService userService) {  
  6.         this.userService = userService;  
  7.     }  
  8.     @RequestMapping("/test")  
  9.       public User view() {  
  10.         User user = new User();  
  11.         user.setId(1L);  
  12.         user.setName("haha");  
  13.         return user;  
  14.     }  
  15.   
  16.     @RequestMapping("/test2")  
  17.     public String view2() {  
  18.         return "{\"id\" : 1}";  
  19.     }  
  20. }  

 其实现就是在@@RestController中加入@ResponseBody:

Java代码   收藏代码
  1. @org.springframework.stereotype.Controller  
  2. @org.springframework.web.bind.annotation.ResponseBody  
  3. public @interface RestController {  
  4. }  

这样当你开发Rest服务器端的时候,spring-mvc配置文件需要的代码极少,可能就仅需如下一行:

Java代码   收藏代码
  1. <context:component-scan base-package="com.sishuok.spring4"/>  
  2. <mvc:annotation-driven/>  

  

2、mvc:annotation-driven配置变化

统一风格;将 enableMatrixVariables改为enable-matrix-variables属性;将ignoreDefaultModelOnRedirect改为ignore-default-model-on-redirect。

 

3、提供AsyncRestTemplate用于客户端非阻塞异步支持。

3.1、服务器端

对于服务器端的springmvc开发可以参考https://github.com/zhangkaitao/servlet3-showcase中的chapter3-springmvc

Java代码   收藏代码
  1. @RestController  
  2. public class UserController {  
  3.     private UserService userService;  
  4.     @Autowired  
  5.     public UserController(UserService userService) {  
  6.         this.userService = userService;  
  7.     }  
  8.     @RequestMapping("/api")  
  9.       public Callable<User> api() {  
  10.         System.out.println("=====hello");  
  11.         return new Callable<User>() {  
  12.             @Override  
  13.             public User call() throws Exception {  
  14.                 Thread.sleep(10L * 1000); //暂停两秒  
  15.                 User user = new User();  
  16.                 user.setId(1L);  
  17.                 user.setName("haha");  
  18.                 return user;  
  19.             }  
  20.         };  
  21.     }  
  22. }  

非常简单,服务器端暂停10秒再返回结果(但是服务器也是非阻塞的)。具体参考我github上的代码。

 

3.2、客户端

Java代码   收藏代码
  1. public static void main(String[] args) {  
  2.     AsyncRestTemplate template = new AsyncRestTemplate();  
  3.     //调用完后立即返回(没有阻塞)  
  4.     ListenableFuture<ResponseEntity<User>> future = template.getForEntity("http://localhost:9080/spring4/api", User.class);  
  5.     //设置异步回调  
  6.     future.addCallback(new ListenableFutureCallback<ResponseEntity<User>>() {  
  7.         @Override  
  8.         public void onSuccess(ResponseEntity<User> result) {  
  9.             System.out.println("======client get result : " + result.getBody());  
  10.         }  
  11.   
  12.         @Override  
  13.         public void onFailure(Throwable t) {  
  14.             System.out.println("======client failure : " + t);  
  15.         }  
  16.     });  
  17.     System.out.println("==no wait");  
  18. }  

 此处使用Future来完成非阻塞,这样的话我们也需要给它一个回调接口来拿结果; Future和Callable是一对,一个消费结果,一个产生结果。调用完模板后会立即返回,不会阻塞;有结果时会调用其回调。

 

AsyncRestTemplate默认使用SimpleClientHttpRequestFactory,即通过java.net.HttpURLConnection实现;另外我们也可以使用apache的http components;使用template.setAsyncRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());设置即可。

 

另外在开发时尽量不要自己注册如:

Java代码   收藏代码
  1. <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>  
  2. <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">  

尽量使用

Java代码   收藏代码
  1. <mvc:annotation-driven/>   

它设计的已经足够好,使用子元素可以配置我们需要的配置。

  

且不要使用老版本的:

Java代码   收藏代码
  1. <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>  
  2. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">  

否则可能得到如下异常:

写道
Circular view path [login]: would dispatch back to the current handler URL [/spring4/login] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

你可能感兴趣的:(spring4新特性,泛型注入,核心容器改进,web开发增强)