前提介绍

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

@Lazy可以延迟依赖注入

@Lazy注解修饰在类层面!

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

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

@Lazy
@Autowired
private UserService userService;

@Conditional

@Conditional类似于@Profile

  • 一般用于如有开发环境、测试环境、正式机环境,为了方便切换不同的环境可以使用@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。

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

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> 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[] 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)

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

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 = {
@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)的简单封装。

MvcUriComponentsBuilder

MvcUriComponentsBuilder类似于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();
}
}

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

  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()
);
}

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

到method.getParameterTypes().length

Socket支持

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

SocketUtils.findAvailableTcpPort()
SocketUtils.findAvailableTcpPort(min, max)
SocketUtils.findAvailableUdpPort()

👊 Spring技术原理系列(7)带你看看那些可能你还不知道的Spring特性技巧哦!的更多相关文章

  1. 👊 Spring技术原理系列-从零开始教你SpringEL表达式使用和功能分析讲解指南(上篇)

    Spring EL表达式语言,这种语言jsp中学到的el,但是在整个spring之中其表达式语言要更加的复杂,而且支持度更加的广泛,最重要的是他可以进行方法的调用,对象的实例化,集合操作等等,但是唯一 ...

  2. 【Spring技术原理】Aspectj和LoadTimeWeaving的动态代理技术实现指南

    前提介绍 当我们聊到Spring框架的项目实际开发中,用的强大的功能之一就是(面向切面编程)的这门AOP技术.如果使用得当,它的最大的作用就是侵入性比较少并且简化我们的工作任务(节省大量的重复性编码) ...

  3. 你所不知道的Spring的@Autowired实现细节

    前言 相信很多Java开发都遇到过一个面试题:Resource和Autowired的区别是什么?这个问题的答案相信基本都清楚,但是这两者在Spring中是如何实现的呢?这就要分析Spring源码才能知 ...

  4. Spring Data JPA系列2:SpringBoot集成JPA详细教程,快速在项目中熟练使用JPA

    大家好,又见面了. 这是Spring Data JPA系列的第2篇,在上一篇<Spring Data JPA系列1:JDBC.ORM.JPA.Spring Data JPA,傻傻分不清楚?给你个 ...

  5. spring cloud 入门系列:总结

    从我第一次接触Spring Cloud到现在已经有3个多月了,当时是在博客园里面注册了账号,并且看到很多文章都在谈论微服务,因此我就去了解了下,最终决定开始学习Spring Cloud.我在一款阅读A ...

  6. Spring技术内幕——深入解析Spring架构与设计原理(一)IOC实现原理

    IOC的基础 下面我们从IOC/AOP开始,它们是Spring平台实现的核心部分:虽然,我们一开始大多只是在这个层面上,做一些配置和外部特性的使用工作,但对这两个核心模块工作原理和运作机制的理解,对深 ...

  7. 分布式缓存技术redis系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  8. 一文带你深入浅出Spring 事务原理

    Spring事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行: 获 ...

  9. 【转】Spring Boot干货系列:(三)启动原理解析

    前言 前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开Sprin ...

随机推荐

  1. Spring Boot 入门系列(二十三)整合Mybatis,实现多数据源配置!

    d之前介绍了Spring Boot 整合mybatis 使用注解方式配置的方式实现增删改查以及一些复杂自定义的sql 语句 .想必大家对spring boot 项目中,如何使用mybatis 有了一定 ...

  2. MySQL-Cluster 初识

          最近,对mysql-cluster进行初步了解,发现和oracle提供的RAC有一定的相似之处,但区别又很大,下面主要是mysql-cluster的搭建,至于对其的深入了解,留着以后工作需 ...

  3. 一个Django项目中实现的简单HTML页面布局

    1 - 基础页面(被继承的模板) {% load static %} <!DOCTYPE html> <html lang="en"> <head&g ...

  4. Python常见问题 - python3 requests库提示警告InsecureRequestWarning的问题

    当使用 requests 库发送请求时报了以下警告 D:\python3.6\lib\site-packages\urllib3\connectionpool.py:847: InsecureRequ ...

  5. Apache Hudi内核之文件标记机制深入解析

    1. 摘要 Hudi 支持在写入时自动清理未成功提交的数据.Apache Hudi 在写入时引入标记机制来有效跟踪写入存储的数据文件. 在本博客中,我们将深入探讨现有直接标记文件机制的设计,并解释了其 ...

  6. matlab纹理映射之地球

    %地球 cla reset; load topo; [x,y,z] = sphere(45); s = surface(x,y,z,'facecolor','texturemap','cdata',t ...

  7. python获取邮件信息

    在项目的Terminal中注册模块pypiwin32 python -m pip install pypiwin32 import win32com.client outlook = win32com ...

  8. 如何将 Ubuntu 版本升级到新版本

    @ 目录 0.将 Ubuntu 版本升级到新版本的注意事项 1.以图形方式升级到 Ubuntu 20.04(适用于桌面用户) 2.使用命令行升级到 Ubuntu 21.10 本教程通过从 Ubuntu ...

  9. PHP中环境变量的操作

    在 PHP 中,我们可以通过 phpinfo() 查看到当前系统中的环境变量信息(Environment).在代码中,我们也可以通过两个函数,查看和修改相应的环境变量信息. getenv() 获取环境 ...

  10. PHP垃圾回收机制的一些浅薄理解

    相信只要入门学习过一点开发的同学都知道,不管任何编程语言,一个变量都会保存在内存中.其实,我们这些开发者就是在来回不停地操纵内存,相应地,我们如果一直增加新的变量,内存就会一直增加,如果没有一个好的机 ...