zoukankan      html  css  js  c++  java
  • Spring4新特性

    一、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新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC :

    在之前的《跟我学SpringMVC》中的《第七章 注解式控制器的数据验证、类型转换及格式化》中已经介绍过SpringMVC集成Bean Validation 1.0(JSR-303),目前Bean Validation最新版本是Bean Validation 1.1(JSR-349),新特性可以到官网查看,笔者最喜欢的两个特性是:跨参数验证(比如密码和确认密码的验证)和支持在消息中使用EL表达式,其他的还有如方法参数/返回值验证、CDI和依赖注入、分组转换等。对于方法参数/返回值验证,大家可以参阅《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》。

     

    Bean Validation 1.1当前实现是Hibernate validator 5,且spring4才支持。接下来我们从以下几个方法讲解Bean Validation 1.1,当然不一定是新特性:

    1.  集成Bean Validation 1.1到SpringMVC
    2.  分组验证、分组顺序及级联验证
    3.  消息中使用EL表达式
    4.  方法参数/返回值验证
    5.  自定义验证规则
    6.  类级别验证器
    7.  脚本验证器
    8.  cross-parameter,跨参数验证
    9. 混合类级别验证器和跨参数验证器
    10. 组合多个验证注解
    11. 本地化

    因为大多数时候验证都配合web框架使用,而且很多朋友都咨询过如分组/跨参数验证,所以本文介绍下这些,且是和SpringMVC框架集成的例子,其他使用方式(比如集成到JPA中)可以参考其官方文档:

    规范:http://beanvalidation.org/1.1/spec/

    hibernate validator文档:http://hibernate.org/validator/ 

     

     1、集成Bean Validation 1.1到SpringMVC

    1.1、项目搭建

    首先添加hibernate validator 5依赖:

    Java代码  收藏代码
    1. <dependency>  
    2.     <groupId>org.hibernate</groupId>  
    3.     <artifactId>hibernate-validator</artifactId>  
    4.     <version>5.0.2.Final</version>  
    5. </dependency>  

    如果想在消息中使用EL表达式,请确保EL表达式版本是 2.2或以上,如使用Tomcat6,请到Tomcat7中拷贝相应的EL jar包到Tomcat6中。

    Java代码  收藏代码
    1. <dependency>  
    2.     <groupId>javax.el</groupId>  
    3.     <artifactId>javax.el-api</artifactId>  
    4.     <version>2.2.4</version>  
    5.     <scope>provided</scope>  
    6. </dependency>  

    请确保您使用的Web容器有相应版本的el jar包。

     

    对于其他POM依赖请下载附件中的项目参考。

     

    1.2、Spring MVC配置文件(spring-mvc.xml):

    Java代码  收藏代码
    1. <!-- 指定自己定义的validator -->  
    2. <mvc:annotation-driven validator="validator"/>  
    3.   
    4. <!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 会 自动注册-->  
    5. <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">  
    6.     <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>  
    7.     <!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->  
    8.     <property name="validationMessageSource" ref="messageSource"/>  
    9. </bean>  
    10.   
    11. <!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->  
    12. <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">  
    13.     <property name="basenames">  
    14.         <list>  
    15.             <!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找  -->  
    16.             <value>classpath:messages</value>  
    17.             <value>classpath:org/hibernate/validator/ValidationMessages</value>  
    18.         </list>  
    19.     </property>  
    20.     <property name="useCodeAsDefaultMessage" value="false"/>  
    21.     <property name="defaultEncoding" value="UTF-8"/>  
    22.     <property name="cacheSeconds" value="60"/>  
    23. </bean>  

    此处主要把bean validation的消息查找委托给spring的messageSource。

     

    1.3、实体验证注解:

    Java代码  收藏代码
    1. public class User implements Serializable {  
    2.     @NotNull(message = "{user.id.null}")  
    3.     private Long id;  
    4.   
    5.     @NotEmpty(message = "{user.name.null}")  
    6.     @Length(min = 5, max = 20, message = "{user.name.length.illegal}")  
    7.     @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}")  
    8.     private String name;  
    9.   
    10.     @NotNull(message = "{user.password.null}")  
    11.     private String password;  
    12. }  

    对于验证规则可以参考官方文档,或者《第七章 注解式控制器的数据验证、类型转换及格式化》。

     

    1.4、错误消息文件messages.properties:

    Java代码  收藏代码
    1. user.id.null=用户编号不能为空  
    2. user.name.null=用户名不能为空  
    3. user.name.length.illegal=用户名长度必须在520之间  
    4. user.name.illegal=用户名必须是字母  
    5. user.password.null=密码不能为空  

     

    1.5、控制器

    Java代码  收藏代码
    1. @Controller  
    2. public class UserController {  
    3.   
    4.     @RequestMapping("/save")  
    5.     public String save(@Valid User user, BindingResult result) {  
    6.         if(result.hasErrors()) {  
    7.             return "error";  
    8.         }  
    9.         return "success";  
    10.     }  
    11. }  

     

    1.6、错误页面:

    Java代码  收藏代码
    1. <spring:hasBindErrors name="user">  
    2.     <c:if test="${errors.fieldErrorCount > 0}">  
    3.         字段错误:<br/>  
    4.         <c:forEach items="${errors.fieldErrors}" var="error">  
    5.             <spring:message var="message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>  
    6.             ${error.field}------${message}<br/>  
    7.         </c:forEach>  
    8.     </c:if>  
    9.   
    10.     <c:if test="${errors.globalErrorCount > 0}">  
    11.         全局错误:<br/>  
    12.         <c:forEach items="${errors.globalErrors}" var="error">  
    13.             <spring:message var="message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>  
    14.             <c:if test="${not empty message}">  
    15.                 ${message}<br/>  
    16.             </c:if>  
    17.         </c:forEach>  
    18.     </c:if>  
    19. </spring:hasBindErrors>  

     

    大家以后可以根据这个做通用的错误消息显示规则。比如我前端页面使用validationEngine显示错误消息,那么我可以定义一个tag来通用化错误消息的显示:showFieldError.tag。  

     

    1.7、测试

    输入如:http://localhost:9080/spring4/save?name=123 , 我们得到如下错误:

    Java代码  收藏代码
    1. name------用户名必须是字母  
    2. name------用户名长度必须在520之间  
    3. password------密码不能为空  
    4. id------用户编号不能为空  

     

    基本的集成就完成了。

     

    如上测试有几个小问题:

    1、错误消息顺序,大家可以看到name的错误消息顺序不是按照书写顺序的,即不确定;

    2、我想显示如:用户名【zhangsan】必须在5到20之间;其中我们想动态显示:用户名、min,max;而不是写死了;

    3、我想在修改的时候只验证用户名,其他的不验证怎么办。

    接下来我们挨着试试吧。

     

    2、分组验证及分组顺序

    如果我们想在新增的情况验证id和name,而修改的情况验证name和password,怎么办? 那么就需要分组了。

    首先定义分组接口:

    Java代码  收藏代码
    1. public interface First {  
    2. }  
    3.   
    4. public interface Second {  
    5. }  

    分组接口就是两个普通的接口,用于标识,类似于java.io.Serializable。

     

    接着我们使用分组接口标识实体:

    Java代码  收藏代码
    1. public class User implements Serializable {  
    2.   
    3.     @NotNull(message = "{user.id.null}", groups = {First.class})  
    4.     private Long id;  
    5.   
    6.     @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {Second.class})  
    7.     @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})  
    8.     private String name;  
    9.   
    10.     @NotNull(message = "{user.password.null}", groups = {First.class, Second.class})  
    11.     private String password;  
    12. }  

     

    验证时使用如:

    Java代码  收藏代码
    1. @RequestMapping("/save")  
    2. public String save(@Validated({Second.class}) User user, BindingResult result) {  
    3.     if(result.hasErrors()) {  
    4.         return "error";  
    5.     }  
    6.     return "success";  
    7. }  

    即通过@Validate注解标识要验证的分组;如果要验证两个的话,可以这样@Validated({First.class, Second.class})。

     

    接下来我们来看看通过分组来指定顺序;还记得之前的错误消息吗? user.name会显示两个错误消息,而且顺序不确定;如果我们先验证一个消息;如果不通过再验证另一个怎么办?可以通过@GroupSequence指定分组验证顺序:

     

    Java代码  收藏代码
    1. @GroupSequence({First.class, Second.class, User.class})  
    2. public class User implements Serializable {  
    3.     private Long id;  
    4.   
    5.     @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})  
    6.     @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})  
    7.     private String name;  
    8.       
    9.     private String password;  
    10. }  

    通过@GroupSequence指定验证顺序:先验证First分组,如果有错误立即返回而不会验证Second分组,接着如果First分组验证通过了,那么才去验证Second分组,最后指定User.class表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。

     

    另一个比较常见的就是级联验证:

    如:

    Java代码  收藏代码
    1. public class User {  
    2.   
    3.     @Valid   
    4.     @ConvertGroup(from=First.class, to=Second.class)  
    5.     private Organization o;  
    6.   
    7. }  

     1、级联验证只要在相应的字段上加@Valid即可,会进行级联验证;@ConvertGroup的作用是当验证o的分组是First时,那么验证o的分组是Second,即分组验证的转换。

     

    3、消息中使用EL表达式

    假设我们需要显示如:用户名[NAME]长度必须在[MIN]到[MAX]之间,此处大家可以看到,我们不想把一些数据写死,如NAME、MIN、MAX;此时我们可以使用EL表达式。

     

    如:

    Java代码  收藏代码
    1. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})  

    错误消息:

    Java代码  收藏代码
    1. user.name.length.illegal=用户名长度必须在{min}到{max}之间  

     

    其中我们可以使用{验证注解的属性}得到这些值;如{min}得到@Length中的min值;其他的也是类似的。

     

    到此,我们还是无法得到出错的那个输入值,如name=zhangsan。此时就需要EL表达式的支持,首先确定引入EL jar包且版本正确。然后使用如:

    Java代码  收藏代码
    1. user.name.length.illegal=用户名[${validatedValue}]长度必须在520之间  

    使用如EL表达式:${validatedValue}得到输入的值,如zhangsan。当然我们还可以使用如${min > 1 ? '大于1' : '小于等于1'},及在EL表达式中也能拿到如@Length的min等数据。

     

    另外我们还可以拿到一个java.util.Formatter类型的formatter变量进行格式化:

    Java代码  收藏代码
    1. ${formatter.format("%04d", min)}  

     

    4、方法参数/返回值验证

    这个可以参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》,概念是类似的,具体可以参考Bean Validation 文档。

     

    5、自定义验证规则

    有时候默认的规则可能还不够,有时候还需要自定义规则,比如屏蔽关键词验证是非常常见的一个功能,比如在发帖时帖子中不允许出现admin等关键词。

     

    1、定义验证注解

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. import javax.validation.Constraint;  
    4. import javax.validation.Payload;  
    5. import java.lang.annotation.Documented;  
    6. import java.lang.annotation.Retention;  
    7. import java.lang.annotation.Target;  
    8. import static java.lang.annotation.ElementType.*;  
    9. import static java.lang.annotation.RetentionPolicy.*;  
    10. /** 
    11.  * <p>User: Zhang Kaitao 
    12.  * <p>Date: 13-12-15 
    13.  * <p>Version: 1.0 
    14.  */  
    15.   
    16. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })  
    17. @Retention(RUNTIME)  
    18. //指定验证器  
    19. @Constraint(validatedBy = ForbiddenValidator.class)  
    20. @Documented  
    21. public @interface Forbidden {  
    22.   
    23.     //默认错误消息  
    24.     String message() default "{forbidden.word}";  
    25.   
    26.     //分组  
    27.     Class<?>[] groups() default { };  
    28.   
    29.     //负载  
    30.     Class<? extends Payload>[] payload() default { };  
    31.   
    32.     //指定多个时使用  
    33.     @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })  
    34.     @Retention(RUNTIME)  
    35.     @Documented  
    36.     @interface List {  
    37.         Forbidden[] value();  
    38.     }  
    39. }  

     

    2、 定义验证器

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl;  
    4. import org.springframework.beans.factory.annotation.Autowired;  
    5. import org.springframework.context.ApplicationContext;  
    6. import org.springframework.util.StringUtils;  
    7.   
    8. import javax.validation.ConstraintValidator;  
    9. import javax.validation.ConstraintValidatorContext;  
    10. import java.io.Serializable;  
    11.   
    12. /** 
    13.  * <p>User: Zhang Kaitao 
    14.  * <p>Date: 13-12-15 
    15.  * <p>Version: 1.0 
    16.  */  
    17. public class ForbiddenValidator implements ConstraintValidator<Forbidden, String> {  
    18.   
    19.     private String[] forbiddenWords = {"admin"};  
    20.   
    21.     @Override  
    22.     public void initialize(Forbidden constraintAnnotation) {  
    23.         //初始化,得到注解数据  
    24.     }  
    25.   
    26.     @Override  
    27.     public boolean isValid(String value, ConstraintValidatorContext context) {  
    28.         if(StringUtils.isEmpty(value)) {  
    29.             return true;  
    30.         }  
    31.   
    32.         for(String word : forbiddenWords) {  
    33.             if(value.contains(word)) {  
    34.                 return false;//验证失败  
    35.             }  
    36.         }  
    37.         return true;  
    38.     }  
    39. }  

     验证器中可以使用spring的依赖注入,如注入:@Autowired  private ApplicationContext ctx; 

     

    3、使用

    Java代码  收藏代码
    1. public class User implements Serializable {  
    2.     @Forbidden()  
    3.     private String name;  
    4. }  

     

    4、当我们在提交name中含有admin的时候会输出错误消息:

    Java代码  收藏代码
    1. forbidden.word=您输入的数据中有非法关键词  

     

    问题来了,哪个词是非法的呢?bean validation 和 hibernate validator都没有提供相应的api提供这个数据,怎么办呢?通过跟踪代码,发现一种不是特别好的方法:我们可以覆盖org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl实现(即复制一份代码放到我们的src中),然后覆盖buildAnnotationParameterMap方法;

    Java代码  收藏代码
    1. private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {  
    2.     ……  
    3.     //将Collections.unmodifiableMap( parameters );替换为如下语句  
    4.     return parameters;  
    5. }  

     即允许这个数据可以修改;然后在ForbiddenValidator中:

    Java代码  收藏代码
    1. for(String word : forbiddenWords) {  
    2.     if(value.contains(word)) {  
    3.         ((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);  
    4.         return false;//验证失败  
    5.     }  
    6. }  

    通过((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);添加自己的属性;放到attributes中的数据可以通过${} 获取。然后消息就可以变成:

    Java代码  收藏代码
    1. forbidden.word=您输入的数据中有非法关键词【{word}】  

    这种方式不是很友好,但是可以解决我们的问题。

     

    典型的如密码、确认密码的场景,非常常用;如果没有这个功能我们需要自己写代码来完成;而且经常重复自己。接下来看看bean validation 1.1如何实现的。

     

    6、类级别验证器

    6.1、定义验证注解

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. import javax.validation.Constraint;  
    4. import javax.validation.Payload;  
    5. import javax.validation.constraints.NotNull;  
    6. import java.lang.annotation.Documented;  
    7. import java.lang.annotation.Retention;  
    8. import java.lang.annotation.Target;  
    9. import static java.lang.annotation.ElementType.*;  
    10. import static java.lang.annotation.RetentionPolicy.*;  
    11. /** 
    12.  * <p>User: Zhang Kaitao 
    13.  * <p>Date: 13-12-15 
    14.  * <p>Version: 1.0 
    15.  */  
    16.   
    17. @Target({ TYPE, ANNOTATION_TYPE})  
    18. @Retention(RUNTIME)  
    19. //指定验证器  
    20. @Constraint(validatedBy = CheckPasswordValidator.class)  
    21. @Documented  
    22. public @interface CheckPassword {  
    23.   
    24.     //默认错误消息  
    25.     String message() default "";  
    26.   
    27.     //分组  
    28.     Class<?>[] groups() default { };  
    29.   
    30.     //负载  
    31.     Class<? extends Payload>[] payload() default { };  
    32.   
    33.     //指定多个时使用  
    34.     @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })  
    35.     @Retention(RUNTIME)  
    36.     @Documented  
    37.     @interface List {  
    38.         CheckPassword[] value();  
    39.     }  
    40. }  

    6.2、 定义验证器

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. import com.sishuok.spring4.entity.User;  
    4. import org.springframework.util.StringUtils;  
    5.   
    6. import javax.validation.ConstraintValidator;  
    7. import javax.validation.ConstraintValidatorContext;  
    8.   
    9. /** 
    10.  * <p>User: Zhang Kaitao 
    11.  * <p>Date: 13-12-15 
    12.  * <p>Version: 1.0 
    13.  */  
    14. public class CheckPasswordValidator implements ConstraintValidator<CheckPassword, User> {  
    15.   
    16.     @Override  
    17.     public void initialize(CheckPassword constraintAnnotation) {  
    18.     }  
    19.   
    20.     @Override  
    21.     public boolean isValid(User user, ConstraintValidatorContext context) {  
    22.         if(user == null) {  
    23.             return true;  
    24.         }  
    25.   
    26.         //没有填密码  
    27.         if(!StringUtils.hasText(user.getPassword())) {  
    28.             context.disableDefaultConstraintViolation();  
    29.             context.buildConstraintViolationWithTemplate("{password.null}")  
    30.                     .addPropertyNode("password")  
    31.                     .addConstraintViolation();  
    32.             return false;  
    33.         }  
    34.   
    35.         if(!StringUtils.hasText(user.getConfirmation())) {  
    36.             context.disableDefaultConstraintViolation();  
    37.             context.buildConstraintViolationWithTemplate("{password.confirmation.null}")  
    38.                     .addPropertyNode("confirmation")  
    39.                     .addConstraintViolation();  
    40.             return false;  
    41.         }  
    42.   
    43.         //两次密码不一样  
    44.         if (!user.getPassword().trim().equals(user.getConfirmation().trim())) {  
    45.             context.disableDefaultConstraintViolation();  
    46.             context.buildConstraintViolationWithTemplate("{password.confirmation.error}")  
    47.                     .addPropertyNode("confirmation")  
    48.                     .addConstraintViolation();  
    49.             return false;  
    50.         }  
    51.         return true;  
    52.     }  
    53. }  

    其中我们通过disableDefaultConstraintViolation禁用默认的约束;然后通过buildConstraintViolationWithTemplate(消息模板)/addPropertyNode(所属属性)/addConstraintViolation定义我们自己的约束。

     

    6.3、使用

    Java代码  收藏代码
    1. @CheckPassword()  
    2. public class User implements Serializable {  
    3. }  

     放到类头上即可。

     

    7、通过脚本验证

    Java代码  收藏代码
    1. @ScriptAssert(script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")  
    2. public class User implements Serializable {  
    3. }  

    通过脚本验证是非常简单而且强大的,lang指定脚本语言(请参考javax.script.ScriptEngineManager JSR-223),alias是在脚本验证中User对象的名字,但是大家会发现一个问题:错误消息怎么显示呢? 在springmvc 中会添加到全局错误消息中,这肯定不是我们想要的,我们改造下吧。

     

    7.1、定义验证注解

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. import org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator;  
    4.   
    5. import java.lang.annotation.Documented;  
    6. import java.lang.annotation.Retention;  
    7. import java.lang.annotation.Target;  
    8. import javax.validation.Constraint;  
    9. import javax.validation.Payload;  
    10.   
    11. import static java.lang.annotation.ElementType.TYPE;  
    12. import static java.lang.annotation.RetentionPolicy.RUNTIME;  
    13.   
    14. @Target({ TYPE })  
    15. @Retention(RUNTIME)  
    16. @Constraint(validatedBy = {PropertyScriptAssertValidator.class})  
    17. @Documented  
    18. public @interface PropertyScriptAssert {  
    19.   
    20.     String message() default "{org.hibernate.validator.constraints.ScriptAssert.message}";  
    21.   
    22.     Class<?>[] groups() default { };  
    23.   
    24.     Class<? extends Payload>[] payload() default { };  
    25.   
    26.     String lang();  
    27.   
    28.     String script();  
    29.   
    30.     String alias() default "_this";  
    31.   
    32.     String property();  
    33.   
    34.     @Target({ TYPE })  
    35.     @Retention(RUNTIME)  
    36.     @Documented  
    37.     public @interface List {  
    38.         PropertyScriptAssert[] value();  
    39.     }  
    40. }  

    和ScriptAssert没什么区别,只是多了个property用来指定出错后给实体的哪个属性。

     

    7.2、验证器

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. import javax.script.ScriptException;  
    4. import javax.validation.ConstraintDeclarationException;  
    5. import javax.validation.ConstraintValidator;  
    6. import javax.validation.ConstraintValidatorContext;  
    7.   
    8. import com.sishuok.spring4.validator.PropertyScriptAssert;  
    9. import org.hibernate.validator.constraints.ScriptAssert;  
    10. import org.hibernate.validator.internal.util.Contracts;  
    11. import org.hibernate.validator.internal.util.logging.Log;  
    12. import org.hibernate.validator.internal.util.logging.LoggerFactory;  
    13. import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluator;  
    14. import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluatorFactory;  
    15.   
    16. import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;  
    17.   
    18. public class PropertyScriptAssertValidator implements ConstraintValidator<PropertyScriptAssert, Object> {  
    19.   
    20.     private static final Log log = LoggerFactory.make();  
    21.   
    22.     private String script;  
    23.     private String languageName;  
    24.     private String alias;  
    25.     private String property;  
    26.     private String message;  
    27.   
    28.     public void initialize(PropertyScriptAssert constraintAnnotation) {  
    29.         validateParameters( constraintAnnotation );  
    30.   
    31.         this.script = constraintAnnotation.script();  
    32.         this.languageName = constraintAnnotation.lang();  
    33.         this.alias = constraintAnnotation.alias();  
    34.         this.property = constraintAnnotation.property();  
    35.         this.message = constraintAnnotation.message();  
    36.     }  
    37.   
    38.     public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {  
    39.   
    40.         Object evaluationResult;  
    41.         ScriptEvaluator scriptEvaluator;  
    42.   
    43.         try {  
    44.             ScriptEvaluatorFactory evaluatorFactory = ScriptEvaluatorFactory.getInstance();  
    45.             scriptEvaluator = evaluatorFactory.getScriptEvaluatorByLanguageName( languageName );  
    46.         }  
    47.         catch ( ScriptException e ) {  
    48.             throw new ConstraintDeclarationException( e );  
    49.         }  
    50.   
    51.         try {  
    52.             evaluationResult = scriptEvaluator.evaluate( script, value, alias );  
    53.         }  
    54.         catch ( ScriptException e ) {  
    55.             throw log.getErrorDuringScriptExecutionException( script, e );  
    56.         }  
    57.   
    58.         if ( evaluationResult == null ) {  
    59.             throw log.getScriptMustReturnTrueOrFalseException( script );  
    60.         }  
    61.         if ( !( evaluationResult instanceof Boolean ) ) {  
    62.             throw log.getScriptMustReturnTrueOrFalseException(  
    63.                     script,  
    64.                     evaluationResult,  
    65.                     evaluationResult.getClass().getCanonicalName()  
    66.             );  
    67.         }  
    68.   
    69.         if(Boolean.FALSE.equals(evaluationResult)) {  
    70.             constraintValidatorContext.disableDefaultConstraintViolation();  
    71.             constraintValidatorContext  
    72.                     .buildConstraintViolationWithTemplate(message)  
    73.                     .addPropertyNode(property)  
    74.                     .addConstraintViolation();  
    75.         }  
    76.   
    77.         return Boolean.TRUE.equals( evaluationResult );  
    78.     }  
    79.   
    80.     private void validateParameters(PropertyScriptAssert constraintAnnotation) {  
    81.         Contracts.assertNotEmpty( constraintAnnotation.script(), MESSAGES.parameterMustNotBeEmpty( "script" ) );  
    82.         Contracts.assertNotEmpty( constraintAnnotation.lang(), MESSAGES.parameterMustNotBeEmpty( "lang" ) );  
    83.         Contracts.assertNotEmpty( constraintAnnotation.alias(), MESSAGES.parameterMustNotBeEmpty( "alias" ) );  
    84.         Contracts.assertNotEmpty( constraintAnnotation.property(), MESSAGES.parameterMustNotBeEmpty( "property" ) );  
    85.         Contracts.assertNotEmpty( constraintAnnotation.message(), MESSAGES.parameterMustNotBeEmpty( "message" ) );  
    86.     }  
    87. }  

    和之前的类级别验证器类似,就不多解释了,其他代码全部拷贝自org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator。

     

    7.3、使用

    Java代码  收藏代码
    1. @PropertyScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")  

    和之前的区别就是多了个property,用来指定出错时给哪个字段。 这个相对之前的类级别验证器更通用一点。

     

    8、cross-parameter,跨参数验证

    直接看示例;

     

    8.1、首先注册MethodValidationPostProcessor,起作用请参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》 

    Java代码  收藏代码
    1. <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">  
    2.     <property name="validator" ref="validator"/>  
    3. </bean>  

     

    8.2、Service 

    Java代码  收藏代码
    1. @Validated  
    2. @Service  
    3. public class UserService {  
    4.   
    5.     @CrossParameter  
    6.     public void changePassword(String password, String confirmation) {  
    7.   
    8.     }  
    9. }  

    通过@Validated注解UserService表示该类中有需要进行方法参数/返回值验证;   @CrossParameter注解方法表示要进行跨参数验证;即验证password和confirmation是否相等。

     

    8.3、验证注解 

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. //省略import  
    4.   
    5. @Constraint(validatedBy = CrossParameterValidator.class)  
    6. @Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })  
    7. @Retention(RUNTIME)  
    8. @Documented  
    9. public @interface CrossParameter {  
    10.   
    11.     String message() default "{password.confirmation.error}";  
    12.     Class<?>[] groups() default { };  
    13.     Class<? extends Payload>[] payload() default { };  
    14.   
    15. }  

     

    8.4、验证器 

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. //省略import  
    4.   
    5. @SupportedValidationTarget(ValidationTarget.PARAMETERS)  
    6. public class CrossParameterValidator implements ConstraintValidator<CrossParameter, Object[]> {  
    7.   
    8.     @Override  
    9.     public void initialize(CrossParameter constraintAnnotation) {  
    10.     }  
    11.   
    12.     @Override  
    13.     public boolean isValid(Object[] value, ConstraintValidatorContext context) {  
    14.         if(value == null || value.length != 2) {  
    15.             throw new IllegalArgumentException("must have two args");  
    16.         }  
    17.         if(value[0] == null || value[1] == null) {  
    18.             return true;  
    19.         }  
    20.         if(value[0].equals(value[1])) {  
    21.             return true;  
    22.         }  
    23.         return false;  
    24.     }  
    25. }  

    其中@SupportedValidationTarget(ValidationTarget.PARAMETERS)表示验证参数; value将是参数列表。 

     

    8.5、使用

    Java代码  收藏代码
    1. @RequestMapping("/changePassword")  
    2. public String changePassword(  
    3.         @RequestParam("password") String password,  
    4.         @RequestParam("confirmation") String confirmation, Model model) {  
    5.     try {  
    6.         userService.changePassword(password, confirmation);  
    7.     } catch (ConstraintViolationException e) {  
    8.         for(ConstraintViolation violation : e.getConstraintViolations()) {  
    9.             System.out.println(violation.getMessage());  
    10.         }  
    11.     }  
    12.     return "success";  
    13. }  

    调用userService.changePassword方法,如果验证失败将抛出ConstraintViolationException异常,然后得到ConstraintViolation,调用getMessage即可得到错误消息;然后到前台显示即可。

     

    从以上来看,不如之前的使用方便,需要自己对错误消息进行处理。 下一节我们也写个脚本方式的跨参数验证器。

     

    9、混合类级别验证器和跨参数验证器

    9.1、验证注解

    Java代码  收藏代码
    1. package com.sishuok.spring4.validator;  
    2.   
    3. //省略import  
    4.   
    5. @Constraint(validatedBy = {  
    6.         CrossParameterScriptAssertClassValidator.class,  
    7.         CrossParameterScriptAssertParameterValidator.class  
    8. })  
    9. @Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })  
    10. @Retention(RUNTIME)  
    11. @Documented  
    12. public @interface CrossParameterScriptAssert {  
    13.     String message() default "error";  
    14.     Class<?>[] groups() default { };  
    15.     Class<? extends Payload>[] payload() default { };  
    16.     String script();  
    17.     String lang();  
    18.     String alias() default "_this";  
    19.     String property() default "";  
    20.     ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;  
    21. }   

     

    此处我们通过@Constraint指定了两个验证器,一个类级别的,一个跨参数的。validationAppliesTo指定为ConstraintTarget.IMPLICIT,表示隐式自动判断。

     

    9.2、验证器

    请下载源码查看

     

    9.3、使用

    9.3.1、类级别使用

    Java代码  收藏代码
    1. @CrossParameterScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")  

    指定property即可,其他和之前的一样。

    9.3.2、跨参数验证

    Java代码  收藏代码
    1. @CrossParameterScriptAssert(script = "args[0] == args[1]", lang = "javascript", alias = "args", message = "{password.confirmation.error}")  
    2. public void changePassword(String password, String confirmation) {  
    3.   
    4. }  

    通过args[0]==args[1] 来判断是否相等。

     

    这样,我们的验证注解就自动适应两种验证规则了。  

     

    10、组合验证注解 

    有时候,可能有好几个注解需要一起使用,此时就可以使用组合验证注解

    Java代码  收藏代码
    1. @Target({ FIELD})  
    2. @Retention(RUNTIME)  
    3. @Documented  
    4. @NotNull(message = "{user.name.null}")  
    5. @Length(min = 5, max = 20, message = "{user.name.length.illegal}")  
    6. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.length.illegal}")  
    7. @Constraint(validatedBy = { })  
    8. public @interface Composition {  
    9.     String message() default "";  
    10.     Class<?>[] groups() default { };  
    11.     Class<? extends Payload>[] payload() default { };  
    12. }  

    这样我们验证时只需要:

    Java代码  收藏代码
    1. @Composition()  
    2. private String name;  

    简洁多了。 

     

    11、本地化 

    即根据不同的语言选择不同的错误消息显示。

    1、本地化解析器

    Java代码  收藏代码
    1. <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">  
    2.     <property name="cookieName" value="locale"/>  
    3.     <property name="cookieMaxAge" value="-1"/>  
    4.     <property name="defaultLocale" value="zh_CN"/>  
    5. </bean>  

    此处使用cookie存储本地化信息,当然也可以选择其他的,如Session存储。

     

    2、设置本地化信息的拦截器

    Java代码  收藏代码
    1. <mvc:interceptors>  
    2.     <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">  
    3.         <property name="paramName" value="language"/>  
    4.     </bean>  
    5. </mvc:interceptors>  

    即请求参数中通过language设置语言。

     

    3、消息文件

     

    4、 浏览器输入

    http://localhost:9080/spring4/changePassword?password=1&confirmation=2&language=en_US

     

     

     

    到此,我们已经完成大部分Bean Validation的功能实验了。对于如XML配置、编程式验证API的使用等对于我们使用SpringMVC这种web环境用处不大,所以就不多介绍了,有兴趣可以自己下载官方文档学习。

     

     

    五、Spring4新特性——Groovy Bean定义DSL

     

    Spring4支持使用Groovy DSL来进行Bean定义配置,其类似于XML,不过因为是Groovy DSL,可以实现任何复杂的语法配置,但是对于配置,我们需要那么复杂吗?本着学习的态度试用了下其Groovy DSL定义Bean,其主要缺点:

    1、DSL语法规则不足,需要其后续维护;

    2、编辑器的代码补全需要跟进,否则没有代码补全,写这个很痛苦;

    3、出错提示不友好,排错难;

    4、当前对于一些配置还是需要XML的支持,所以还不是100%的纯Groovy DSL;

    5、目前对整个Spring生态支持还是不够的,比如Web,需要观望。

     

    其优点就是其本质是Groovy脚本,所以可以做非常复杂的配置,如果以上问题能够解决,其也是一个不错的选择。在Groovy中的话使用这种配置感觉不会有什么问题,但是在纯Java开发环境下也是有它,给我的感觉是这个功能其目的是去推广它的groovy。比较怀疑它的动机。

     

    接下来我们来看看Spring配置的发展:

    Spring 2时代是XML风格配置  可以参考《跟我学Spring3》的前几章

    Spring 3时代引入注解风格配置  可以参考《跟我学Spring3》的第12章 

    Spring 4时代引入Groovy DSL风格来配置 后续讲解

     

    一、对比

    对于我来说,没有哪个好/坏,只有适用不适用;开发方便不方便。接下来我们来看一下各种类型的配置吧:

    XML风格配置

    Java代码  收藏代码
    1. <context:component-scan base-package="com.sishuok.spring4"/>  
    2. <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">  
    3.     <property name="validator" ref="validator"/>  
    4. </bean>  
    5. <mvc:annotation-driven validator="validator"/>  
    6.   
    7. <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">  
    8.     <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>  
    9.     <property name="validationMessageSource" ref="messageSource"/>  
    10. </bean>  

     

    注解风格配置 

    Java代码  收藏代码
    1. @Configuration  
    2. @EnableWebMvc  
    3. @ComponentScan(basePackages = "com.sishuok.spring4")  
    4. public class MvcConfiguration extends WebMvcConfigurationSupport {  
    5.     @Override  
    6.     protected Validator getValidator() {  
    7.         LocalValidatorFactoryBean localValidatorFactoryBean =  
    8.                 new LocalValidatorFactoryBean();  
    9.         localValidatorFactoryBean.setProviderClass(HibernateValidator.class);  
    10.         localValidatorFactoryBean.setValidationMessageSource(messageSource());  
    11.         return localValidatorFactoryBean;  
    12.     }  
    13. }  

     

    Groovy DSL风格配置

    Java代码  收藏代码
    1. import org.hibernate.validator.HibernateValidator  
    2. import org.springframework.context.support.ReloadableResourceBundleMessageSource  
    3. import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean  
    4.   
    5. beans {  
    6.     xmlns context: "http://www.springframework.org/schema/context"  
    7.     xmlns mvc: "http://www.springframework.org/schema/mvc"  
    8.   
    9.     context.'component-scan'('base-package'"com,sishuok.spring4")  
    10.     mvc.'annotation-driven'('validator'"validator")  
    11.   
    12.     validator(LocalValidatorFactoryBean) {  
    13.         providerClass = HibernateValidator.class  
    14.         validationMessageSource = ref("messageSource")  
    15.     }  
    16. }  

    因为Spring4 webmvc没有提供用于Web环境的Groovy DSL实现的WebApplicationContext,所以为了在web环境使用,单独写了一个WebGenricGroovyApplicationContext,可以到源码中查找。

     

     

    可以看到,它们之前差别不是特别大;以上只提取了部分配置,完整的配置可以参考我的github:spring4-showcase

     

    对于注解风格的配置,如果在Servlet3容器中使用的话,可以借助WebApplicationInitializer实现无配置:

    Java代码  收藏代码
    1. public class AppInitializer implements WebApplicationInitializer {  
    2.   
    3.     @Override  
    4.     public void onStartup(javax.servlet.ServletContext sc) throws ServletException {  
    5.   
    6. //        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();  
    7. //        rootContext.register(AppConfig.class);  
    8. //        sc.addListener(new ContextLoaderListener(rootContext));  
    9.   
    10.         //2、springmvc上下文  
    11.         AnnotationConfigWebApplicationContext springMvcContext = new AnnotationConfigWebApplicationContext();  
    12.         springMvcContext.register(MvcConfiguration.class);  
    13.   
    14.         //3、DispatcherServlet  
    15.         DispatcherServlet dispatcherServlet = new DispatcherServlet(springMvcContext);  
    16.         ServletRegistration.Dynamic dynamic = sc.addServlet("dispatcherServlet", dispatcherServlet);  
    17.         dynamic.setLoadOnStartup(1);  
    18.         dynamic.addMapping("/");  
    19.   
    20.         //4、CharacterEncodingFilter  
    21.         FilterRegistration filterRegistration =  
    22.                 sc.addFilter("characterEncodingFilter", CharacterEncodingFilter.class);  
    23.         filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false"/*");  
    24.   
    25.   
    26.     }  
    27. }  

     

    到底好还是不好,需要根据自己项目大小等一些因素来衡量。对于Servlet3可以参考我github的示例:servlet3-showcase  

     

    对于Groovy风格配置,如果语法足够丰富、Spring内部支持完善,且编辑器支持也非常好的话,也是不错的选择。

     

     

    二、Groovy Bean定义

    接下来我们来看下groovy DSL的具体使用吧:

    1、安装环境

    Java代码  收藏代码
    1. <dependency>  
    2.     <groupId>org.codehaus.groovy</groupId>  
    3.     <artifactId>groovy-all</artifactId>  
    4.     <version>${groovy.version}</version>  
    5. </dependency>  

    我使用的groovy版本是2.2.1

     

    2、相关组件类

    此处使用Spring Framework官网的hello world,可以前往http://projects.spring.io/spring-framework/ 主页查看 

     

    3、Groovy Bean定义配置文件

    Java代码  收藏代码
    1. import com.sishuok.spring4.xml.MessageServiceImpl  
    2. import com.sishuok.spring4.xml.MessagePrinter  
    3.   
    4. beans {  
    5.     messageService(MessageServiceImpl) {//名字(类型)   
    6.         message = "hello"  //注入的属性  
    7.     }  
    8.   
    9.     messagePrinter(MessagePrinter, messageService) //名字(类型,构造器参数列表)  
    10.   
    11. }  

    从此处可以看到 如果仅仅是简单的Bean定义,确实比XML简洁。

     

     

    4、测试

    如果不测试环境可以这样测试:

    Java代码  收藏代码
    1. public class XmlGroovyBeanDefinitionTest1 {  
    2.     @Test  
    3.     public void test() {  
    4.         ApplicationContext ctx = new GenericGroovyApplicationContext("classpath:spring-config-xml.groovy");  
    5.         MessagePrinter messagePrinter = (MessagePrinter) ctx.getBean("messagePrinter");  
    6.         messagePrinter.printMessage();  
    7.     }  
    8. }  

    使用GenericGroovyApplicationContext加载groovy配置文件。 

     

     

    如果想集成到Spring Test中,可以这样:

    Java代码  收藏代码
    1. @RunWith(SpringJUnit4ClassRunner.class)  
    2. @ContextConfiguration(locations = "classpath:spring-config-xml.groovy", loader = GenericGroovyContextLoader.class)  
    3. public class XmlGroovyBeanDefinitionTest2 {  
    4.   
    5.     @Autowired  
    6.     private MessagePrinter messagePrinter;  
    7.   
    8.     @Test  
    9.     public void test() {  
    10.         messagePrinter.printMessage();  
    11.     }  
    12. }  

    此处需要定义我们自己的bean loader,即从groovy配置文件加载:

    Java代码  收藏代码
    1. public class GenericGroovyContextLoader extends AbstractGenericContextLoader {  
    2.   
    3.     @Override  
    4.     protected String getResourceSuffix() {  
    5.         throw new UnsupportedOperationException(  
    6.                 "GenericGroovyContextLoader does not support the getResourceSuffix() method");  
    7.     }  
    8.     @Override  
    9.     protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {  
    10.         return new GroovyBeanDefinitionReader(context);  
    11.     }  
    12. }  

    使用GroovyBeanDefinitionReader来加载groovy配置文件。  

     

    到此基本的使用就结束了,还算是比较简洁,但是我们已经注意到了,在纯Java环境做测试还是比较麻烦的。 比如没有给我们写好相关的测试支撑类。另外大家可以前往Spring的github看看在groovy中的单元测试:GroovyBeanDefinitionReaderTests.groovy

     

    再看一下我们使用注解方式呢:

    Java代码  收藏代码
    1. @Component  
    2. public class MessageServiceImpl implements MessageService {  
    3.     @Autowired  
    4.     @Qualifier("message")  
    5.     private String message;  
    6.     ……  
    7. }  
     
    Java代码  收藏代码
    1. @Component  
    2. public class MessagePrinter {  
    3.     private MessageService messageService;  
    4.     @Autowired  
    5.     public MessagePrinter(MessageService messageService) {  
    6.         this.messageService = messageService;  
    7.     }  
    8. ……  
    9. }  

     

    此处省略无关代码,需要的话直接去github查看 。点击前往 

     

    Groovy配置文件:

     

    Java代码  收藏代码
    1. beans {  
    2.     xmlns context: "http://www.springframework.org/schema/context"    //导入命名空间  
    3.   
    4.     context.'component-scan'('base-package'"com.sishuok.spring4") {  
    5.         'exclude-filter'('type'"aspectj"'expression'"com.sishuok.spring4.xml.*")  
    6.     }  
    7.   
    8.     message(String, "hello") {}  
    9.   
    10. }  
    在该配置文件中支持导入xml命名空间, 其中context.'component-scan'部分等价于XML中的:
    Java代码  收藏代码
    1. <context:component-scan base-package="com.sishuok.spring4">  
    2.     <context:exclude-filter type="aspectj" expression="com.sishuok.spring4.xml.*"/>  
    3. </context:component-scan>              
     从这里可以看出,其还没能完全从XML风格配置中走出来,不是纯Groovy DSL。

     

     

    测试方式和之前的一样就不重复了,可以查看XmlGroovyBeanDefinitionTest2.java

     

    三、Groovy Bean定义 DSL语法

    到目前为止,基本的helloworld就搞定了;接下来看看Groovy DSL都支持哪些配置吧:

    创建Bean

     

    构造器

    Java代码  收藏代码
    1. validator(LocalValidatorFactoryBean) { //名字(类型)  
    2.     providerClass = HibernateValidator.class  //属性=值  
    3.     validationMessageSource = ref("messageSource"//属性 = 引用,当然也支持如 validationMessageSource=messageSource 但是这种方式缺点是messageSource必须在validator之前声明  
    4. }   

    静态工厂方法

    Java代码  收藏代码
    1. def bean = factory(StaticFactory) {  
    2.     prop = 1  
    3. }  
    4. bean.factoryMethod = "getInstance"  
    或者
    Java代码  收藏代码
    1. bean(StaticFactory) { bean ->  
    2.     bean.factoryMethod = "getInstance"  
    3.     prop = 1  
    4. }  
     
    实例工厂方法
    Java代码  收藏代码
    1. beanFactory(Factory)  
    2. bean(beanFactory : "newInstance""args") {  
    3.     prop = 1  
    4. }  
    或者
    Java代码  收藏代码
    1. beanFactory(Factory)  
    2. bean("bean"){bean ->  
    3.     bean.factoryBean="beanFactory"  
    4.     bean.factoryMethod="newInstance"  
    5.     prop = 1  
    6. }  

     

    依赖注入

     

    属性注入

    Java代码  收藏代码
    1. beanName(BeanClass) { //名字(类型)  
    2.     str = "123" // 常量直接注入  
    3.     bean = ref("bean"//属性 = 引用 ref("bean", true) 这样的话是引用父容器的  
    4.     beans = [bean1, bean2] //数组/集合  
    5.     props = [key1:"value1", key2:"value2"// Properties / Map  
    6. }  

     

    构造器注入 

    Java代码  收藏代码
    1. bean(Bean, "args1""args2")   

     

    静态工厂注入/实例工厂注入,请参考创建bean部分

     

     

    匿名内部Bean

    Java代码  收藏代码
    1. outer(OuterBean) {  
    2.    prop = 1  
    3.    inner =  { InnerBean bean ->  //匿名内部Bean  
    4.                           prop =2  
    5.             }  
    6. }  
    Java代码  收藏代码
    1. outer(OuterBean) {  
    2.    prop = 1  
    3.    inner =  { bean ->  //匿名内部Bean 通过实例工厂方法创建  
    4.                           bean.factoryBean = "innerBean"  
    5.                           bean.factoryMethod = "create"  
    6.                           prop = 2  
    7.             }  
    8. }  

    单例/非单例/作用域

    Java代码  收藏代码
    1. singletonBean(Bean1) { bean ->  
    2.     bean.singleton = true  
    3. }  
    4. nonSingletonBean(Bean1) { bean ->  
    5.     bean.singleton = false  
    6. }  
    7. prototypeBean(Bean1) { bean ->  
    8.     bean.scope = "prototype"  
    9. }  

     

    其中bean可以理解为xml中的<bean> 标签,即bean定义。

     

     

    父子Bean

    Java代码  收藏代码
    1. parent(Bean1){ bean ->   
    2.     bean.'abstract' = true //抽象的  
    3.     prop = 123  
    4. }  
    5. child { bean ->  
    6.     bean.parent = parent //指定父bean  
    7. }  
     

     

    命名空间

    Java代码  收藏代码
    1. xmlns aop:"http://www.springframework.org/schema/aop"  
    2. myAspect(MyAspect)  
    3. aop {  
    4.     config("proxy-target-class":true) {  
    5.         aspect( id:"test",ref:"myAspect" ) {  
    6.             before method:"before", pointcut: "execution(void com.sishuok.spring4..*.*(..))"  
    7.         }  
    8.     }  
    9. }  
    以上是AOP的,可以自己推到其他相关的配置; 

     

    Java代码  收藏代码
    1. xmlns context: "http://www.springframework.org/schema/context"     
    2. context.'component-scan'('base-package'"com.sishuok.spring4") {  
    3.     'exclude-filter'('type'"aspectj"'expression'"com.sishuok.spring4.xml.*")  
    4. }  
    以上是component-scan,之前介绍过了。

      

    Java代码  收藏代码
    1. xmlns aop:"http://www.springframework.org/schema/aop"  
    2. scopedList(ArrayList) { bean ->  
    3.     bean.scope = "haha"  
    4.     aop.'scoped-proxy'()    
    5. }  
     等价于
    Java代码  收藏代码
    1. <bean id="scopedList" class="java.util.ArrayList" scope="haha">  
    2.     <aop:scoped-proxy/>  
    3. </bean>  

     

    Java代码  收藏代码
    1. xmlns util:"http://www.springframework.org/schema/util"  
    2. util.list(id : 'list') {  
    3.     value 1  
    4.     value 2  
    5. }  
    等价于XML:
    Java代码  收藏代码
    1. <util:list id="list">  
    2.     <value>1</value>  
    3.     <value>2</value>  
    4. </util:list>  

     

    Java代码  收藏代码
    1. xmlns util:"http://www.springframework.org/schema/util"  
    2. util.map(id : 'map') {  
    3.     entry(key : 1, value :1)  
    4.     entry('key-ref' : "messageService"'value-ref' : "messageService")  
    5. }  
     等价于
    Java代码  收藏代码
    1. <util:map id="map">  
    2.     <entry key="1" value="1"/>  
    3.     <entry key-ref="messageService" value-ref="messageService"/>  
    4. </util:map>  

    引入其他配置文件

    Java代码  收藏代码
    1. importBeans "classpath:org/springframework/context/groovy/test.xml"  
    当然也能引入XML的。 

     

    对于DSL新的更新大家可以关注:GroovyBeanDefinitionReaderTests.groovy,本文也是根据其编写的。

     

    再来看看groovy bean定义的另一个好处:

    我们可以直接在groovy bean定义文件中声明类,然后使用

    Java代码  收藏代码
    1. @Controller  
    2. def class GroovyController {  
    3.     @RequestMapping("/groovy")  
    4.     @ResponseBody  
    5.     public String hello() {  
    6.         return "hello";  
    7.     }  
    8. }  
    9.   
    10. beans {  
    11.   
    12.     groovyController(GroovyController)  
    13.   
    14. }  

     

     另一种Spring很早就支持的方式是引入外部groovy文件,如:

    Java代码  收藏代码
    1. xmlns lang: "http://www.springframework.org/schema/lang"  
    2. lang.'groovy'(id: 'groovyController2''script-source''classpath:com/sishuok/spring4/controller/GroovyController2.groovy')  

    使用其lang命名空间引入外部脚本文件。

     

     

    到此,Groovy Bean定义DSL就介绍完了,其没有什么特别之处,只是换了种写法而已,我认为目前试试即可,还不能用到真实环境。

     

     

    示例代码:

    https://github.com/zhangkaitao/spring4-showcase

    https://github.com/zhangkaitao/servlet3-showcase

     

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

    随着泛型用的越来越多,获取泛型实际类型信息的需求也会出现,如果用原生API,需要很多步操作才能获取到泛型,比如:

    Java代码  收藏代码
    1. ParameterizedType parameterizedType =   
    2.     (ParameterizedType) ABService.class.getGenericInterfaces()[0];  
    3. Type genericType = parameterizedType.getActualTypeArguments()[1];  

     

    Spring提供的ResolvableType API,提供了更加简单易用的泛型操作支持,如:

    Java代码  收藏代码
    1. ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);  
    2. resolvableType1.as(Service.class).getGeneric(1).resolve()  

    对于获取更复杂的泛型操作ResolvableType更加简单。

     

    假设我们的API是:

    Java代码  收藏代码
    1. public interface Service<N, M> {  
    2. }  
    3.   
    4. @org.springframework.stereotype.Service  
    5. public class ABService implements Service<A, B> {  
    6. }  
    7.   
    8. @org.springframework.stereotype.Service  
    9. public class CDService implements Service<C, D> {  
    10. }  

    如上泛型类非常简单。 

     

    1、得到类型的泛型信息

    Java代码  收藏代码
    1. ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);  

    通过如上API,可以得到类型的ResolvableType,如果类型被Spring AOP进行了CGLIB代理,请使用ClassUtils.getUserClass(ABService.class)得到原始类型。

     

    可以通过如下得到泛型参数的第1个位置(从0开始)的类型信息

    Java代码  收藏代码
    1. resolvableType1.getInterfaces()[0].getGeneric(1).resolve()  

    因为我们泛型信息放在 Service<A, B> 上,所以需要resolvableType1.getInterfaces()[0]得到;

     

    通过getGeneric(泛型参数索引)得到某个位置的泛型;

    resolve()把实际泛型参数解析出来

     

    2、得到字段级别的泛型信息

    假设我们的字段如下:

    Java代码  收藏代码
    1. @Autowired  
    2.  private Service<A, B> abService;  
    3.  @Autowired  
    4.  private Service<C, D> cdService;  
    5.   
    6.  private List<List<String>> list;  
    7.   
    8.  private Map<String, Map<String, Integer>> map;  
    9.   
    10.  private List<String>[] array;  

     

    通过如下API可以得到字段级别的ResolvableType

    Java代码  收藏代码
    1. ResolvableType resolvableType2 =  
    2.                 ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class"cdService"));  

     

    然后通过如下API得到Service<C, D>的第0个位置上的泛型实参类型,即C

    Java代码  收藏代码
    1. resolvableType2.getGeneric(0).resolve()  

     

    比如 List<List<String>> list;是一种嵌套的泛型用例,我们可以通过如下操作获取String类型:

    Java代码  收藏代码
    1. ResolvableType resolvableType3 =  
    2.                 ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class"list"));  
    3. resolvableType3.getGeneric(0).getGeneric(0).resolve();  

     

    更简单的写法

    Java代码  收藏代码
    1. resolvableType3.getGeneric(00).resolve(); //List<List<String>> 即String   

     

    比如Map<String, Map<String, Integer>> map;我们想得到Integer,可以使用:

    Java代码  收藏代码
    1. ResolvableType resolvableType4 =  
    2.                 ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class"map"));  
    3. resolvableType4.getGeneric(1).getGeneric(1).resolve();  

    更简单的写法  

    Java代码  收藏代码
    1. resolvableType4.getGeneric(11).resolve()  

     

    3、得到方法返回值的泛型信息

    假设我们的方法如下:

    Java代码  收藏代码
    1. private HashMap<String, List<String>> method() {  
    2.     return null;  
    3. }  

     

    得到Map中的List中的String泛型实参:

    Java代码  收藏代码
    1. ResolvableType resolvableType5 =  
    2.                 ResolvableType.forMethodReturnType(ReflectionUtils.findMethod(GenricInjectTest.class"method"));  
    3. resolvableType5.getGeneric(10).resolve();  

     

    4、得到构造器参数的泛型信息

    假设我们的构造器如下:

    Java代码  收藏代码
    1. public Const(List<List<String>> list, Map<String, Map<String, Integer>> map) {  
    2. }  

    我们可以通过如下方式得到第1个参数( Map<String, Map<String, Integer>>)中的Integer:

    Java代码  收藏代码
    1. ResolvableType resolvableType6 =  
    2.                 ResolvableType.forConstructorParameter(ClassUtils.getConstructorIfAvailable(Const.class, List.class, Map.class), 1);  
    3. resolvableType6.getGeneric(10).resolve();  

     

    5、得到数组组件类型的泛型信息

    如对于private List<String>[] array; 可以通过如下方式获取List的泛型实参String:

    Java代码  收藏代码
    1. ResolvableType resolvableType7 =  
    2.                 ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class"array"));  
    3. resolvableType7.isArray();//判断是否是数组  
    4. resolvableType7.getComponentType().getGeneric(0).resolve();  

     

    6、自定义泛型类型

    Java代码  收藏代码
    1. ResolvableType resolvableType8 = ResolvableType.forClassWithGenerics(List.class, String.class);  
    2.         ResolvableType resolvableType9 = ResolvableType.forArrayComponent(resolvableType8);  
    3. resolvableType9.getComponentType().getGeneric(0).resolve();  

    ResolvableType.forClassWithGenerics(List.class, String.class)相当于创建一个List<String>类型;

    ResolvableType.forArrayComponent(resolvableType8);:相当于创建一个List<String>[]数组;

    resolvableType9.getComponentType().getGeneric(0).resolve():得到相应的泛型信息;

     

    7、泛型等价比较:

    Java代码  收藏代码
    1. resolvableType7.isAssignableFrom(resolvableType9)  

     

    如下创建一个List<Integer>[]数组,与之前的List<String>[]数组比较,将返回false。

    Java代码  收藏代码
    1. ResolvableType resolvableType10 = ResolvableType.forClassWithGenerics(List.class, Integer.class);  
    2. ResolvableType resolvableType11= ResolvableType.forArrayComponent(resolvableType10);  
    3. resolvableType11.getComponentType().getGeneric(0).resolve();  
    4. resolvableType7.isAssignableFrom(resolvableType11);  

     

    从如上操作可以看出其泛型操作功能十分完善,尤其在嵌套的泛型信息获取上相当简洁。目前整个Spring4环境都使用这个API来操作泛型信息。

     

    如之前说的泛型注入:Spring4新特性——泛型限定式依赖注入,通过在依赖注入时使用如下类实现:

    GenericTypeAwareAutowireCandidateResolver

    QualifierAnnotationAutowireCandidateResolver

    ContextAnnotationAutowireCandidateResolver

     

    还有如Spring的核心BeanWrapperImpl,以及整个Spring/SpringWevMVC的泛型操作都是替换为这个API了:GenericCollectionTypeResolver和GenericTypeResolver都直接委托给ResolvableType这个API。

     

    所以大家以后对泛型操作可以全部使用这个API了,非常好用。测试用例请参考GenricInjectTest.java

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

    JSR-310规范提供一个新的和改进的Java日期与时间API。从2007投票到2013年11月发布公共Review版本已经好多年了,会在Java8中包含,可以下载OpenJDK早期发布版本试用(Win XP不支持):

     

    https://jdk8.java.net/download.html

     

    JSR 310规范领导者Stephen Colebourne就是joda-time作者,其主要思想也是借鉴了joda-time,而不是直接把joda-time移植到Java平台中,API是类似的,但做了改进,具体的改进请参考其2009年的一篇文章和InfoQ对他的采访:

    http://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html 

    http://www.infoq.com/cn/news/2010/05/jsr-310 

    http://blog.joda.org/2010/12/what-about-jsr-310_153.html

     

    规范请前往如下地址下载:

    https://jcp.org/en/jsr/detail?id=310

     

    JSR310 日期与时间规范主要API如下:

    Clock

    时钟,类似于钟表的概念,提供了如系统时钟、固定时钟、特定时区的时钟

    Java代码  收藏代码
    1. //时钟提供给我们用于访问某个特定 时区的 瞬时时间、日期 和 时间的。  
    2. Clock c1 = Clock.systemUTC(); //系统默认UTC时钟(当前瞬时时间 System.currentTimeMillis())  
    3. System.out.println(c1.millis()); //每次调用将返回当前瞬时时间(UTC)  
    4.   
    5. Clock c2 = Clock.systemDefaultZone(); //系统默认时区时钟(当前瞬时时间)  
    6.   
    7. Clock c31 = Clock.system(ZoneId.of("Europe/Paris")); //巴黎时区  
    8. System.out.println(c31.millis()); //每次调用将返回当前瞬时时间(UTC)  
    9.   
    10. Clock c32 = Clock.system(ZoneId.of("Asia/Shanghai"));//上海时区  
    11. System.out.println(c32.millis());//每次调用将返回当前瞬时时间(UTC)  
    12.   
    13. Clock c4 = Clock.fixed(Instant.now(), ZoneId.of("Asia/Shanghai"));//固定上海时区时钟  
    14. System.out.println(c4.millis());  
    15. Thread.sleep(1000);  
    16. System.out.println(c4.millis()); //不变 即时钟时钟在那一个点不动  
    17.   
    18. Clock c5 = Clock.offset(c1, Duration.ofSeconds(2)); //相对于系统默认时钟两秒的时钟  
    19. System.out.println(c1.millis());  
    20. System.out.println(c5.millis());  

     

     

    Instant

    瞬时时间,等价于以前的System.currentTimeMillis()

    Java代码  收藏代码
    1. //瞬时时间 相当于以前的System.currentTimeMillis()  
    2. Instant instant1 = Instant.now();  
    3. System.out.println(instant1.getEpochSecond());//精确到秒 得到相对于1970-01-01 00:00:00 UTC的一个时间  
    4. System.out.println(instant1.toEpochMilli()); //精确到毫秒  
    5.   
    6. Clock clock1 = Clock.systemUTC(); //获取系统UTC默认时钟  
    7. Instant instant2 = Instant.now(clock1);//得到时钟的瞬时时间  
    8. System.out.println(instant2.toEpochMilli());  
    9.   
    10. Clock clock2 = Clock.fixed(instant1, ZoneId.systemDefault()); //固定瞬时时间时钟  
    11. Instant instant3 = Instant.now(clock2);//得到时钟的瞬时时间  
    12. System.out.println(instant3.toEpochMilli());//equals instant1  

     

    LocalDateTime、LocalDate、LocalTime 

    提供了对java.util.Date的替代,另外还提供了新的DateTimeFormatter用于对格式化/解析的支持

    Java代码  收藏代码
    1. //使用默认时区时钟瞬时时间创建 Clock.systemDefaultZone() -->即相对于 ZoneId.systemDefault()默认时区  
    2. LocalDateTime now = LocalDateTime.now();  
    3. System.out.println(now);  
    4.   
    5. //自定义时区  
    6. LocalDateTime now2= LocalDateTime.now(ZoneId.of("Europe/Paris"));  
    7. System.out.println(now2);//会以相应的时区显示日期  
    8.   
    9. //自定义时钟  
    10. Clock clock = Clock.system(ZoneId.of("Asia/Dhaka"));  
    11. LocalDateTime now3= LocalDateTime.now(clock);  
    12. System.out.println(now3);//会以相应的时区显示日期  
    13.   
    14. //不需要写什么相对时间 如java.util.Date 年是相对于1900 月是从0开始  
    15. //2013-12-31 23:59  
    16. LocalDateTime d1 = LocalDateTime.of(201312312359);  
    17.   
    18. //年月日 时分秒 纳秒  
    19. LocalDateTime d2 = LocalDateTime.of(201312312359,5911);  
    20.   
    21. //使用瞬时时间 + 时区  
    22. Instant instant = Instant.now();  
    23. LocalDateTime d3 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());  
    24. System.out.println(d3);  
    25.   
    26. //解析String--->LocalDateTime  
    27. LocalDateTime d4 = LocalDateTime.parse("2013-12-31T23:59");  
    28. System.out.println(d4);  
    29.   
    30. LocalDateTime d5 = LocalDateTime.parse("2013-12-31T23:59:59.999");//999毫秒 等价于999000000纳秒  
    31. System.out.println(d5);  
    32.   
    33. //使用DateTimeFormatter API 解析 和 格式化  
    34. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");  
    35. LocalDateTime d6 = LocalDateTime.parse("2013/12/31 23:59:59", formatter);  
    36. System.out.println(formatter.format(d6));  
    37.   
    38.   
    39. //时间获取  
    40. System.out.println(d6.getYear());  
    41. System.out.println(d6.getMonth());  
    42. System.out.println(d6.getDayOfYear());  
    43. System.out.println(d6.getDayOfMonth());  
    44. System.out.println(d6.getDayOfWeek());  
    45. System.out.println(d6.getHour());  
    46. System.out.println(d6.getMinute());  
    47. System.out.println(d6.getSecond());  
    48. System.out.println(d6.getNano());  
    49. //时间增减  
    50. LocalDateTime d7 = d6.minusDays(1);  
    51. LocalDateTime d8 = d7.plus(1, IsoFields.QUARTER_YEARS);  
    52.   
    53. //LocalDate 即年月日 无时分秒  
    54. //LocalTime即时分秒 无年月日  
    55. //API和LocalDateTime类似就不演示了  

     

    ZonedDateTime

    带有时区的date-time 存储纳秒、时区和时差(避免与本地date-time歧义);API和LocalDateTime类似,只是多了时差(如2013-12-20T10:35:50.711+08:00[Asia/Shanghai])  

    Java代码  收藏代码
    1. //即带有时区的date-time 存储纳秒、时区和时差(避免与本地date-time歧义)。  
    2. //API和LocalDateTime类似,只是多了时差(如2013-12-20T10:35:50.711+08:00[Asia/Shanghai])  
    3. ZonedDateTime now = ZonedDateTime.now();  
    4. System.out.println(now);  
    5.   
    6. ZonedDateTime now2= ZonedDateTime.now(ZoneId.of("Europe/Paris"));  
    7. System.out.println(now2);  
    8.   
    9. //其他的用法也是类似的 就不介绍了  
    10.   
    11. ZonedDateTime z1 = ZonedDateTime.parse("2013-12-31T23:59:59Z[Europe/Paris]");  
    12. System.out.println(z1);  

     

     

    Duration

    表示两个瞬时时间的时间段 

     

    Java代码  收藏代码
    1. //表示两个瞬时时间的时间段  
    2. Duration d1 = Duration.between(Instant.ofEpochMilli(System.currentTimeMillis() - 12323123), Instant.now());  
    3. //得到相应的时差  
    4. System.out.println(d1.toDays());  
    5. System.out.println(d1.toHours());  
    6. System.out.println(d1.toMinutes());  
    7. System.out.println(d1.toMillis());  
    8. System.out.println(d1.toNanos());  
    9.   
    10. //1天时差 类似的还有如ofHours()  
    11. Duration d2 = Duration.ofDays(1);  
    12. System.out.println(d2.toDays());  

     

     

    Chronology

    用于对年历系统的支持,是java.util.Calendar的替代者

    Java代码  收藏代码
    1. //提供对java.util.Calendar的替换,提供对年历系统的支持  
    2. Chronology c  = HijrahChronology.INSTANCE;  
    3. ChronoLocalDateTime d = c.localDateTime(LocalDateTime.now());  
    4. System.out.println(d);  

      

    其他

    如果提供了年、年月、月日、周期的API支持

    Java代码  收藏代码
    1. Year year = Year.now();  
    2. YearMonth yearMonth = YearMonth.now();  
    3. MonthDay monthDay = MonthDay.now();  
    4.   
    5. System.out.println(year);//年  
    6. System.out.println(yearMonth); //年-月  
    7. System.out.println(monthDay); // 月-日  
    8.   
    9. //周期,如表示10天前  3年5个月钱  
    10. Period period1 = Period.ofDays(10);  
    11. System.out.println(period1);  
    12. Period period2 = Period.of(350);  
    13. System.out.println(period2);  

     

    代码示例请参考:TimeTest.java

     

    从以上来看,JSR310提供了更好、更强大的、更易使用的API。另外有三篇对joda-time和jsr310 使用的介绍文章:

    http://www.codedata.com.tw/java/jodatime-jsr310-1-date-calendar/

    http://www.codedata.com.tw/java/jodatime-jsr310-2-time-abc/

    http://www.codedata.com.tw/java/jodatime-jsr310-3-using-jodatime/

     

    spring4提供了对jsr310的支持,只要能发现如java.time.LocalDate,DefaultFormattingConversionService就会自动注册对jsr310的支持;对于ConversionService请参考:

    SpringMVC数据格式化——第七章 注解式控制器的数据验证、类型转换及格式化——跟着开涛学SpringMVC 

     

    我们只需要在实体/Bean上使用DateTimeFormat注解:

    Java代码  收藏代码
    1. @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
    2. private LocalDateTime dateTime;  
    3.   
    4. @DateTimeFormat(pattern = "yyyy-MM-dd")  
    5. private LocalDate date;  
    6.   
    7. @DateTimeFormat(pattern = "HH:mm:ss")  
    8. private LocalTime time;  

    比如我们在springmvc中:

    Java代码  收藏代码
    1. @RequestMapping("/test")  
    2. public String test(@ModelAttribute("entity") Entity entity) {  
    3.     return "test";  
    4. }  

    当前端页面请求:

    localhost:9080/spring4/test?dateTime=2013-11-11 11:11:11&date=2013-11-11&time=12:12:12

    会自动进行类型转换。

     

    另外spring4也提供了对TimeZone的支持;比如在springmvc中注册了LocaleContextResolver相应实现的话(如CookieLocaleResolver),我们就可以使用如下两种方式得到相应的TimeZone:

    RequestContextUtils.getTimeZone(request)

    LocaleContextHolder.getTimeZone()

     

    不过目前的缺点是不能像Local那样自动的根据当前请求得到相应的TimeZone,如果需要这种功能需要覆盖相应的如CookieLocaleResolver中的如下方法来得到:

    Java代码  收藏代码
    1. protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {  
    2.     return getDefaultTimeZone();  
    3. }  

     

    另外还提供了DateTimeContextHolder,其用于线程绑定DateTimeContext;而DateTimeContext提供了如:Chronology、ZoneId、DateTimeFormatter等上下文数据,如果需要这种上下文信息的话,可以使用这个API进行绑定。比如在进行日期格式化时,就会去查找相应的DateTimeFormatter,因此如果想自定义相应的格式化格式,那么使用DateTimeContextHolder绑定即可。

     

    源代码请参考github项目:spring4-datetime-jsr310 

     

    spring4只是简单的对jsr310提供了相应的支持,没有太多的增强。

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

    一、注解方面的改进

    spring4对注解API和ApplicationContext获取注解Bean做了一点改进。

    获取注解的注解,如@Service是被@Compent注解的注解,可以通过如下方式获取@Componet注解实例:

    Java代码  收藏代码
    1. Annotation service = AnnotationUtils.findAnnotation(ABService.class, org.springframework.stereotype.Service.class);  
    2. Annotation component = AnnotationUtils.getAnnotation(service, org.springframework.stereotype.Component.class);  

     

    获取重复注解:

    比如在使用hibernate validation时,我们想在一个方法上加相同的注解多个,需要使用如下方式:

    Java代码  收藏代码
    1. @Length.List(  
    2.         value = {  
    3.                 @Length(min = 1, max = 2, groups = A.class),  
    4.                 @Length(min = 3, max = 4, groups = B.class)  
    5.         }  
    6. )  
    7. public void test() {  

    可以通过如下方式获取@Length:

    Java代码  收藏代码
    1. Method method = ClassUtils.getMethod(AnnotationUtilsTest.class"test");  
    2. Set<Length> set = AnnotationUtils.getRepeatableAnnotation(method, Length.List.class, Length.class);  

     

    当然,如果你使用Java8,那么本身就支持重复注解,比如spring的任务调度注解,

    Java代码  收藏代码
    1. @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})  
    2. @Retention(RetentionPolicy.RUNTIME)  
    3. @Documented  
    4. @Repeatable(Schedules.class)  
    5. public @interface Scheduled {   
    Java代码  收藏代码
    1. @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})  
    2. @Retention(RetentionPolicy.RUNTIME)  
    3. @Documented  
    4. public @interface Schedules {  
    5.   
    6.     Scheduled[] value();  
    7.   
    8. }  

     

    这样的话,我们可以直接同时注解相同的多个注解:

    Java代码  收藏代码
    1. @Scheduled(cron = "123")  
    2. @Scheduled(cron = "234")  
    3. public void test     

    但是获取的时候还是需要使用如下方式:

    Java代码  收藏代码
    1. AnnotationUtils.getRepeatableAnnotation(ClassUtils.getMethod(TimeTest.class"test"), Schedules.class, Scheduled.class)  

     

    ApplicationContext和BeanFactory提供了直接通过注解获取Bean的方法:

    Java代码  收藏代码
    1. @Test  
    2. public void test() {  
    3.     AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();  
    4.     ctx.register(GenericConfig.class);  
    5.     ctx.refresh();  
    6.   
    7.     Map<String, Object> beans = ctx.getBeansWithAnnotation(org.springframework.stereotype.Service.class);  
    8.     System.out.println(beans);  
    9. }  

    这样可以实现一些特殊功能。

     

    另外和提供了一个AnnotatedElementUtils用于简化java.lang.reflect.AnnotatedElement的操作,具体请参考其javadoc。   

     

    二、脚本的支持 

    spring4也提供了类似于javax.script的简单封装,用于支持一些脚本语言,核心接口是:

    Java代码  收藏代码
    1. public interface ScriptEvaluator {  
    2.     Object evaluate(ScriptSource script) throws ScriptCompilationException;  
    3.     Object evaluate(ScriptSource script, Map<String, Object> arguments) throws ScriptCompilationException;  
    4. }  

     

    比如我们使用groovy脚本的话,可以这样:

    Java代码  收藏代码
    1. @Test  
    2. public void test() throws ExecutionException, InterruptedException {  
    3.     ScriptEvaluator scriptEvaluator = new GroovyScriptEvaluator();  
    4.   
    5.     //ResourceScriptSource 外部的  
    6.     ScriptSource source = new StaticScriptSource("i+j");  
    7.     Map<String, Object> args = new HashMap<>();  
    8.     args.put("i"1);  
    9.     args.put("j"2);  
    10.     System.out.println(scriptEvaluator.evaluate(source, args));  
    11. }  

    没什么很特别的地方。另外还提供了BeanShell(BshScriptEvaluator)和javax.script(StandardScriptEvaluator)的简单封装。

     

    三、Future增强

    提供了一个ListenableFuture,其是jdk的Future的封装,用来支持回调(成功/失败),其借鉴了com.google.common.util.concurrent.ListenableFuture。

    Java代码  收藏代码
    1. @Test  
    2. public void test() throws Exception {  
    3.     ListenableFutureTask<String> task = new ListenableFutureTask<String>(new Callable() {  
    4.         @Override  
    5.         public Object call() throws Exception {  
    6.             Thread.sleep(10 * 1000L);  
    7.             System.out.println("=======task execute");  
    8.             return "hello";  
    9.         }  
    10.     });  
    11.   
    12.     task.addCallback(new ListenableFutureCallback<String>() {  
    13.         @Override  
    14.         public void onSuccess(String result) {  
    15.             System.out.println("===success callback 1");  
    16.         }  
    17.   
    18.         @Override  
    19.         public void onFailure(Throwable t) {  
    20.         }  
    21.     });  
    22.   
    23.     task.addCallback(new ListenableFutureCallback<String>() {  
    24.         @Override  
    25.         public void onSuccess(String result) {  
    26.             System.out.println("===success callback 2");  
    27.         }  
    28.   
    29.         @Override  
    30.         public void onFailure(Throwable t) {  
    31.         }  
    32.     });  
    33.   
    34.     ExecutorService executorService = Executors.newSingleThreadExecutor();  
    35.     executorService.submit(task);  
    36.     String result = task.get();  
    37.     System.out.println(result);  
    38.   
    39. }  

    可以通过addCallback添加一些回调,当执行成功/失败时会自动调用。

     

    四、MvcUriComponentsBuilder

    MvcUriComponentsBuilder类似于ServletUriComponentsBuilder,但是可以直接从控制器获取URI信息,如下所示:

    假设我们的控制器是:

    Java代码  收藏代码
    1. @Controller  
    2. @RequestMapping("/user")  
    3. public class UserController {  
    4.   
    5.     @RequestMapping("/{id}")  
    6.     public String view(@PathVariable("id") Long id) {  
    7.         return "view";  
    8.     }  
    9.   
    10.     @RequestMapping("/{id}")  
    11.     public A getUser(@PathVariable("id") Long id) {  
    12.         return new A();  
    13.     }  
    14.   
    15. }  

    注:如果在真实mvc环境,存在两个@RequestMapping("/{id}")是错误的。当前只是为了测试。

     

    我们可以通过如下方式得到

    Java代码  收藏代码
    1. //需要静态导入 import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.*;  
    2. @Test  
    3. public void test() {  
    4.     MockHttpServletRequest req = new MockHttpServletRequest();  
    5.     RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(req));  
    6.   
    7.     //MvcUriComponentsBuilder类似于ServletUriComponentsBuilder,但是直接从控制器获取  
    8.     //类级别的  
    9.     System.out.println(  
    10.             fromController(UserController.class).build().toString()  
    11.     );  
    12.   
    13.     //方法级别的  
    14.     System.out.println(  
    15.             fromMethodName(UserController.class"view", 1L).build().toString()  
    16.     );  
    17.   
    18.     //通过Mock方法调用得到  
    19.     System.out.println(  
    20.             fromMethodCall(on(UserController.class).getUser(2L)).build()  
    21.     );  
    22. }  

    注意:当前MvcUriComponentsBuilder实现有问题,只有JDK环境支持,大家可以复制一份,然后修改:

    method.getParameterCount() (Java 8才支持)

    method.getParameterTypes().length

     

    五、Socket支持

    提供了获取Socket TCP/UDP可用端口的工具,如

    SocketUtils.findAvailableTcpPort()

    SocketUtils.findAvailableTcpPort(min, max) 

    SocketUtils.findAvailableUdpPort()

    非常简单,就不用特别说明了。

     

    示例代码请参考:spring4-others

     

    到此,spring4新特性就介绍完了,此处没有介绍websocket,后续有时间考虑写一个websocket完整系列,对于spring4除了websocket,其他方面并没有特别吸引人的功能。

     

     

    原文链接:https://jinnianshilongnian.iteye.com/blog/1995111

     

     

     

    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指定使用哪一个。

  • 相关阅读:
    cocos2d-x之物理引擎初试
    cocos2d-x之猜数字游戏
    cocos2d-x之加法计算器
    cocos2d-x之悦动的小球
    cocos2d-x之多个移动的小球
    cocos2d-x之json文件读取初试
    cocos2d-x之xml文件读取初试
    cocos2d-x之使用plist文件初试
    cocos2d-x之文件读写
    cocos2d-x之首选项数据初试
  • 原文地址:https://www.cnblogs.com/hongmoshui/p/10422270.html
Copyright © 2011-2022 走看看