首页>>后端>>Spring->带你看看那些可能你还不知道的Spring特性技巧哦!

带你看看那些可能你还不知道的Spring特性技巧哦!

时间:2023-11-29 本站 点击:1

前提介绍

本文主要介绍相关Spring框架的一些新特性问题机制,包含了一些特定注解方面的认识。

@Lazy可以延迟依赖注入

@Lazy注解修饰在类层面!

@Lazy@ServicepublicclassUserServiceextendsBaseService<User>{}

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

@Lazy@AutowiredprivateUserServiceuserService;

@Conditional

@Conditional类似于@Profile

一般用于如有开发环境、测试环境、正式机环境,为了方便切换不同的环境可以使用@Profile指定各个环境的配置。

通过某个配置来开启某个环境,方便切换,但是@Conditional的优点是允许自己定义规则,可以指定在如@Component、@Bean、@Configuration等注解的类上,以绝对Bean是否创建等。

首先来看看使用@Profile的用例,假设我们有个用户模块:

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

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

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}

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

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}

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

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceLocal{}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceRemote{}
publicclassCustomConditionimplementsCondition{@Overridepublicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){booleanisLocalBean=metadata.isAnnotated("com.xxx.Local");booleanisRemoteBean=metadata.isAnnotated("com.xxx.Remote");//如果bean没有注解@Local或@Remote,返回true,表示创建Beanif(!isLocalBean&&!isRemoteBean){returntrue;}booleanisLocalProfile=context.getEnvironment().acceptsProfiles("local");//如果profile=local且bean注解了@Local,则返回true表示创建beanif(isLocalProfile){returnisLocalBean;}//否则默认返回注解了@Remote或没有注解@Remote的BeanreturnisRemoteBean;}}

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

@Local@ServicepublicclassLocalUserServiceextendsUserService{}@Remote@ServicepublicclassRemoteUserServiceextendsUserService{}

首先在@Local和@Remote注解上使用@Conditional(CustomCondition.class)指定条件。

然后使用@Local和@Remote注解我们的Service,这样当加载Service时,会先执行条件然后判断是否加载为Bean。

@Profile实现的Condition是:org.springframework.context.annotation.ProfileCondition。

AsyncRestTemplate非阻塞异步(已废弃WebClient代替之)

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

服务器端
@RestControllerpublicclassUserController{privateUserServiceuserService;@AutowiredpublicUserController(UserServiceuserService){this.userService=userService;}@RequestMapping("/api")publicCallable<User>api(){returnnewCallable<User>(){@OverridepublicUsercall()throwsException{Thread.sleep(10L*1000);//暂停两秒Useruser=newUser();user.setId(1L);user.setName("haha");returnuser;}};}}

非常简单,服务器端暂停10秒再返回结果(但是服务器也是非阻塞的)。

客户端

publicstaticvoidmain(String[]args){AsyncRestTemplatetemplate=newAsyncRestTemplate();//调用完后立即返回(没有阻塞)ListenableFuture<ResponseEntity<User>>future=template.getForEntity("http://localhost:9080/rest/api",User.class);//设置异步回调future.addCallback(newListenableFutureCallback<ResponseEntity<User>>(){@OverridepublicvoidonSuccess(ResponseEntity<User>result){System.out.println("======clientgetresult:"+result.getBody());}@OverridepublicvoidonFailure(Throwablet){System.out.println("======clientfailure:"+t);}});System.out.println("==nowait");}

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

@Testpublicvoidtest()throwsException{ListenableFutureTask<String>task=newListenableFutureTask<String>(newCallable(){@OverridepublicObjectcall()throwsException{Thread.sleep(10*1000L);System.out.println("=======taskexecute");return"hello";}});task.addCallback(newListenableFutureCallback<String>(){@OverridepublicvoidonSuccess(Stringresult){System.out.println("===successcallback1");}@OverridepublicvoidonFailure(Throwablet){}});task.addCallback(newListenableFutureCallback<String>(){@OverridepublicvoidonSuccess(Stringresult){System.out.println("===successcallback2");}@OverridepublicvoidonFailure(Throwablet){}});ExecutorServiceexecutorService=Executors.newSingleThreadExecutor();executorService.submit(task);Stringresult=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注解:

@Lazy@AutowiredprivateUserServiceuserService;0

比如我们在springmvc中:

@Lazy@AutowiredprivateUserServiceuserService;1
当前端页面请求:
@Lazy@AutowiredprivateUserServiceuserService;2
会自动进行类型转换

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

@Lazy@AutowiredprivateUserServiceuserService;3

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

@Lazy@AutowiredprivateUserServiceuserService;4

另外还提供了DateTimeContextHolder,其用于线程绑定DateTimeContext;而DateTimeContext提供了如:Chronology、ZoneId、DateTimeFormatter等上下文数据,如果需要这种上下文信息的话,可以使用这个API进行绑定。

比如在进行日期格式化时,就会去查找相应的DateTimeFormatter,因此如果想自定义相应的格式化格式,那么使用DateTimeContextHolder绑定即可。

泛型操作控制

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

@Lazy@AutowiredprivateUserServiceuserService;5

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

接口层的泛型处理
@Lazy@AutowiredprivateUserServiceuserService;6

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

假设我们的API是:

@Lazy@AutowiredprivateUserServiceuserService;7

得到类型的泛型信息

@Lazy@AutowiredprivateUserServiceuserService;8

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

@Lazy@AutowiredprivateUserServiceuserService;9

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

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

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

得到字段级别的泛型信息

假设我们的字段如下:

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}0

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

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}1

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

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}2

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

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}3

更简单的写法

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}4

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

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}5

更简单的写法

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}6

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

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}7

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

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}8

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

假设我们的构造器如下:

publicabstractclassUserServiceextendsBaseService<User>{}@Profile("local")@ServicepublicclassLocalUserServiceextendsUserService{}@Profile("remote")@ServicepublicclassRemoteUserServiceextendsUserService{}9

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

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}0

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

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

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}1

自定义泛型类型

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}2

泛型等价比较:

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}3

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

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}4

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

注解方面的改进

Spring对注解API和ApplicationContext获取注解Bean做了一点改进,取注解的注解,如@Service是被@Compent注解的注解,可以通过如下方式获取@Componet注解实例:

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}5
获取重复注解:

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

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}6

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

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}7

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

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}8

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

@ActiveProfiles("remote")@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:spring-config.xml")publicclassServiceTest{@AutowiredprivateUserServiceuserService;}9

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

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceLocal{}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceRemote{}0

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

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceLocal{}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceRemote{}1

另外和提供了一个AnnotatedElementUtils用于简化java.lang.reflect.AnnotatedElement的操作。

ScriptEvaluator脚本的支持

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

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceLocal{}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceRemote{}2

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

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceLocal{}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceRemote{}3

另外还提供了BeanShell(BshScriptEvaluator)和javax.script(StandardScriptEvaluator)的简单封装。

MvcUriComponentsBuilder

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

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceLocal{}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceRemote{}4

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

需要静态导入 import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.*;

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceLocal{}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceRemote{}5

注意:当前MvcUriComponentsBuilder实现有问题,只有JDK环境支持,大家可以复制一份,然后修改:method.getParameterCount() (Java 8才支持) 到method.getParameterTypes().length

Socket支持

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

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceLocal{}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Conditional(CustomCondition.class)public@interfaceRemote{}6


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Spring/318.html