Spring功能介绍带你看看那些可能你还不知道的Spring技巧哦!

知是行的主意,行是知的功夫。这篇文章主要讲述Spring功能介绍带你看看那些可能你还不知道的Spring技巧哦!相关的知识,希望能为你提供帮助。
前提介绍
本文主要介绍相关Spring框架的一些新特性问题机制,包含了一些特定注解方面的认识。
@Lazy可以延迟依赖注入@Lazy注解修饰在类层面!

@Lazy @Service public class UserService extends BaseService< User> { }

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

@Conditional
  • 一般用于如有开发环境、测试环境、正式机环境,为了方便切换不同的环境可以使用@Profile指定各个环境的配置。
  • 通过某个配置来开启某个环境,方便切换,但是@Conditional的优点是允许自己定义规则,可以指定在如@Component、@Bean、@Configuration等注解的类上,以绝对Bean是否创建等。
首先来看看使用@Profile的用例,假设我们有个用户模块:
  1. 在测试/开发期间调用本机的模拟接口方便开发;
  2. 在部署到正式机时换成调用远程接口;
public abstract class UserService extends BaseService< User> { }@Profile("local") @Service public class LocalUserService extends UserService {}@Profile("remote") @Service public class RemoteUserService extends UserService {}

我们在写测试用例时,可以指定我们使用哪个Profile:
@ActiveProfiles("remote") @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations ="classpath:spring-config.xml") public class ServiceTest { @Autowired private UserService userService; }

如果想自定义如@Profile之类的注解等,那么@Conditional就派上用场了,假设我们系统中有好多本地/远程接口,那么我们定义两个注解@Local和@Remote注解要比使用@Profile方便的多;如:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Conditional(CustomCondition.class) public @interface Local { }@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Conditional(CustomCondition.class) public @interface Remote {}

public class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { boolean isLocalBean = metadata.isAnnotated("com.xxx.Local"); boolean isRemoteBean = metadata.isAnnotated("com.xxx.Remote"); //如果bean没有注解@Local或@Remote,返回true,表示创建Bean if(!isLocalBean & & !isRemoteBean) { return true; } boolean isLocalProfile = context.getEnvironment().acceptsProfiles("local"); //如果profile=local 且 bean注解了@Local,则返回true 表示创建bean if(isLocalProfile) { return isLocalBean; } // 否则默认返回注解了@Remote或没有注解@Remote的Bean return isRemoteBean; } }

然后我们使用这两个注解分别注解我们的Service:
@Local @Service public class LocalUserService extends UserService { } @Remote @Service public class RemoteUserService extends UserService {}

  • 首先在@Local和@Remote注解上使用@Conditional(CustomCondition.class)指定条件。
  • 然后使用@Local和@Remote注解我们的Service,这样当加载Service时,会先执行条件然后判断是否加载为Bean。
AsyncRestTemplate非阻塞异步(已废弃WebClient代替之)提供AsyncRestTemplate用于客户端非阻塞异步支持。
服务器端
@RestController public class UserController { private UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @RequestMapping("/api") public Callable< User> api() { return new Callable< User> () { @Override public User call() throws Exception { Thread.sleep(10L * 1000); //暂停两秒 User user = new User(); user.setId(1L); user.setName("haha"); return user; } }; } }

非常简单,服务器端暂停10秒再返回结果(但是服务器也是非阻塞的)。
客户端
public static void main(String[] args) { AsyncRestTemplate template = new AsyncRestTemplate(); //调用完后立即返回(没有阻塞) ListenableFuture< ResponseEntity< User> > future = template.getForEntity("http://localhost:9080/rest/api", User.class); //设置异步回调 future.addCallback(new ListenableFutureCallback< ResponseEntity< User> > () { @Override public void onSuccess(ResponseEntity< User> result) { System.out.println("======client get result : " + result.getBody()); } @Override public void onFailure(Throwable t) { System.out.println("======client failure : " + t); } }); System.out.println("==no wait"); }

承接上面的内容:Future增强,提供了一个ListenableFuture,其是jdk的Future的封装,用来支持回调(成功/失败),借鉴了com.google.common.util.concurrent.ListenableFuture。
@Test public void test() throws Exception { ListenableFutureTask< String> task = new ListenableFutureTask< String> (new Callable() { @Override public Object call() throws Exception { Thread.sleep(10 * 1000L); System.out.println("=======task execute"); return "hello"; } }); task.addCallback(new ListenableFutureCallback< String> () { @Override public void onSuccess(String result) { System.out.println("===success callback 1"); }@Override public void onFailure(Throwable t) { } }); task.addCallback(new ListenableFutureCallback< String> () { @Override public void onSuccess(String result) { System.out.println("===success callback 2"); }@Override public void onFailure(Throwable t) { } }); ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(task); String result = task.get(); System.out.println(result); }

  • 可以通过addCallback添加一些回调,当执行成功/失败时会自动调用。
  • 此处使用Future来完成非阻塞,这样的话我们也需要给它一个回调接口来拿结果;
  • Future和Callable是一对,一个消费结果,一个产生结果。调用完模板后会立即返回,不会阻塞;有结果时会调用其回调。
  • AsyncRestTemplate默认使用SimpleClientHttpRequestFactory,即通过java.net.HttpURLConnection实现;
  • 另外可以使用apache的http components,使用template.setAsyncRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory()),设置即可。
Spring对Java8的时间类型支持对jsr310的支持,只要能发现java.time.LocalDate,DefaultFormattingConversionService就会自动注册对jsr310的支持,只需要在实体/Bean上使用DateTimeFormat注解:
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime dateTime; @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate date; @DateTimeFormat(pattern = "HH:mm:ss") private LocalTime time;

比如我们在springmvc中:
@RequestMapping("/test") public String test(@ModelAttribute("entity") Entity entity) { return "test"; }

当前端页面请求:
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中的如下方法来得到:
protected TimeZone determineDefaultTimeZone(HttpServletRequest request) { return getDefaultTimeZone(); }

  • 另外还提供了DateTimeContextHolder,其用于线程绑定DateTimeContext;而DateTimeContext提供了如:Chronology、ZoneId、DateTimeFormatter等上下文数据,如果需要这种上下文信息的话,可以使用这个API进行绑定。
  • 比如在进行日期格式化时,就会去查找相应的DateTimeFormatter,因此如果想自定义相应的格式化格式,那么使用DateTimeContextHolder绑定即可。
泛型操作控制随着泛型用的越来越多,获取泛型实际类型信息的需求也会出现,如果用原生API,需要很多步操作才能获取到泛型,比如:
ParameterizedType parameterizedType = (ParameterizedType) ABService.class.getGenericInterfaces()[0]; Type genericType = parameterizedType.getActualTypeArguments()[1];

Spring提供的ResolvableType API,提供了更加简单易用的泛型操作支持,如:
接口层的泛型处理
ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class); resolvableType1.as(Service.class).getGeneric(1).resolve();

对于获取更复杂的泛型操作ResolvableType更加简单。
假设我们的API是:
public interface Service< N, M> { } @org.springframework.stereotype.Service public class ABService implements Service< A, B> { } @org.springframework.stereotype.Service public class CDService implements Service< C, D> {}

得到类型的泛型信息
ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);

通过如上API,可以得到类型的ResolvableType,如果类型被Spring AOP进行了CGLIB代理,请使用ClassUtils.getUserClass(ABService.class)得到原始类型,可以通过如下得到泛型参数的第1个位置(从0开始)的类型信息
resolvableType1.getInterfaces()[0].getGeneric(1).resolve()

  • 泛型信息放在 Service< A, B> 上,所以需要resolvableType1.getInterfaces()[0]得到;
  • 通过getGeneric(泛型参数索引)得到某个位置的泛型;
resolve()把实际泛型参数解析出来
得到字段级别的泛型信息假设我们的字段如下:
@Autowired private Service< A, B> abService; @Autowired private Service< C, D> cdService; private List< List< String> > list; private Map< String, Map< String, Integer> > map; private List< String> [] array;

通过如下API可以得到字段级别的ResolvableType
ResolvableType resolvableType2 = ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "cdService"));

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

比如 List< List< String> > list; 是一种嵌套的泛型用例,我们可以通过如下操作获取String类型:
ResolvableType resolvableType3 = ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "list")); resolvableType3.getGeneric(0).getGeneric(0).resolve();

更简单的写法
resolvableType3.getGeneric(0, 0).resolve(); //List< List< String> > 即String

比如,Map< String, Map< String, Integer> > map; 我们想得到Integer,可以使用:
ResolvableType resolvableType4 = ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "map")); resolvableType4.getGeneric(1).getGeneric(1).resolve();

更简单的写法
resolvableType4.getGeneric(1, 1).resolve()

得到方法返回值的泛型信息
private HashMap< String, List< String> > method() { return null; }

得到Map中的List中的String泛型实参:
ResolvableType resolvableType5 = ResolvableType.forMethodReturnType(ReflectionUtils.findMethod(GenricInjectTest.class, "method")); resolvableType5.getGeneric(1, 0).resolve();

得到构造器参数的泛型信息假设我们的构造器如下:
public Const(List< List< String> > list, Map< String, Map< String, Integer> > map) {}

我们可以通过如下方式得到第1个参数( Map< String, Map< String, Integer> > )中的Integer:
ResolvableType resolvableType6 = ResolvableType.forConstructorParameter(ClassUtils.getConstructorIfAvailable(Const.class, List.class, Map.class), 1); resolvableType6.getGeneric(1, 0).resolve();

得到数组组件类型的泛型信息如对于private List< String> [] array; 可以通过如下方式获取List的泛型实参String:
ResolvableType resolvableType7 = ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "array")); resolvableType7.isArray(); //判断是否是数组 resolvableType7.getComponentType().getGeneric(0).resolve();

自定义泛型类型
ResolvableType resolvableType8 = ResolvableType.forClassWithGenerics(List.class, String.class); ResolvableType resolvableType9 = ResolvableType.forArrayComponent(resolvableType8); resolvableType9.getComponentType().getGeneric(0).resolve(); ResolvableType.forClassWithGenerics(List.class, String.class)相当于创建一个List< String> 类型; ResolvableType.forArrayComponent(resolvableType8); :相当于创建一个List< String> []数组; resolvableType9.getComponentType().getGeneric(0).resolve():得到相应的泛型信息;

泛型等价比较:
resolvableType7.isAssignableFrom(resolvableType9)

ResolvableType resolvableType10 = ResolvableType.forClassWithGenerics(List.class, Integer.class); ResolvableType resolvableType11= ResolvableType.forArrayComponent(resolvableType10); resolvableType11.getComponentType().getGeneric(0).resolve(); resolvableType7.isAssignableFrom(resolvableType11);

从如上操作可以看出其泛型操作功能十分完善,尤其在嵌套的泛型信息获取上相当简洁。目前整个Spring环境都使用这个API来操作泛型信息。
注解方面的改进Spring对注解API和ApplicationContext获取注解Bean做了一点改进,取注解的注解,如@Service是被@Compent注解的注解,可以通过如下方式获取@Componet注解实例:
Annotation service = AnnotationUtils.findAnnotation(ABService.class, org.springframework.stereotype.Service.class); Annotation component = AnnotationUtils.getAnnotation(service, org.springframework.stereotype.Component.class);

获取重复注解:比如在使用hibernate validation时,我们想在一个方法上加相同的注解多个,需要使用如下方式:
@Length.List( value = https://www.songbingjia.com/android/{ @Length(min = 1, max = 2, groups = A.class), @Length(min = 3, max = 4, groups = B.class) } ) public void test() {}

可以通过如下方式获取@Length:
Method method = ClassUtils.getMethod(AnnotationUtilsTest.class, "test"); Set< Length> set = AnnotationUtils.getRepeatableAnnotation(method, Length.List.class, Length.class);

当然,如果你使用Java8,那么本身就支持重复注解,比如spring的任务调度注解,
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Schedules.class) public @interface Scheduled {}@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Schedules { Scheduled[] value(); }

这样的话,我们可以直接同时注解相同的多个注解:
@Scheduled(cron = "123") @Scheduled(cron = "234") public void test

但是获取的时候还是需要使用如下方式:
AnnotationUtils.getRepeatableAnnotation(ClassUtils.getMethod(TimeTest.class, "test"), Schedules.class, Scheduled.class)

ApplicationContext和BeanFactory提供了直接通过注解获取Bean的方法:
@Test public void test() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(GenericConfig.class); ctx.refresh(); Map< String, Object> beans = ctx.getBeansWithAnnotation(org.springframework.stereotype.Service.class); System.out.println(beans); }

另外和提供了一个AnnotatedElementUtils用于简化java.lang.reflect.AnnotatedElement的操作。
ScriptEvaluator脚本的支持spring也提供了类似于javax.script的简单封装,用于支持一些脚本语言,核心接口是:
public interface ScriptEvaluator { Object evaluate(ScriptSource script) throws ScriptCompilationException; Object evaluate(ScriptSource script, Map< String, Object> arguments) throws ScriptCompilationException; }

比如我们使用groovy脚本的话,可以这样:
@Test public void test() throws ExecutionException, InterruptedException { ScriptEvaluator scriptEvaluator = new GroovyScriptEvaluator(); //ResourceScriptSource 外部的 ScriptSource source = new StaticScriptSource("i+j"); Map< String, Object> args = new HashMap< > (); args.put("i", 1); args.put("j", 2); System.out.println(scriptEvaluator.evaluate(source, args)); }

另外还提供了BeanShell(BshScriptEvaluator)和javax.script(StandardScriptEvaluator)的简单封装。
MvcUriComponentsBuilderMvcUriComponentsBuilder类似于ServletUriComponentsBuilder,但是可以直接从控制器获取URI信息,如下所示:
假设我们的控制器是:
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/{id}") public String view(@PathVariable("id") Long id) { return "view"; } @RequestMapping("/{id}") public A getUser(@PathVariable("id") Long id) { return new A(); } }

  1. 需要静态导入 import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.*;
@Test public void test() { MockHttpServletRequest req = new MockHttpServletRequest(); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(req)); //MvcUriComponentsBuilder类似于ServletUriComponentsBuilder,但是直接从控制器获取 //类级别的 System.out.println( fromController(UserController.class).build().toString() ); //方法级别的 System.out.println( fromMethodName(UserController.class, "view", 1L).build().toString() ); //通过Mock方法调用得到 System.out.println( fromMethodCall(on(UserController.class).getUser(2L)).build() ); }

Socket支持
【Spring功能介绍带你看看那些可能你还不知道的Spring技巧哦!】提供了获取Socket TCP/UDP可用端口的工具,如
SocketUtils.findAvailableTcpPort() SocketUtils.findAvailableTcpPort(min, max) SocketUtils.findAvailableUdpPort()


    推荐阅读