在基于spring体系的业务中正确地关闭线程池

在业务代码中,特别是基于spring体系的代码中,均会使用线程池进行一些操作,比如异步处理消息,定时任务,以及一些需要与当前业务分离开的操作等。常规情况下,使用spring体系的TaskExecutor或者是自己定义ExecutorService,均可以正常地完成相应的操作。不论是定义一个spring bean,或者是使用 static Thread工具类均是能满足条件。

但是,如果需要正常地关闭spring容器时,这些线程池就不一定能够按照预期地关闭了。结果就是,当使用代码 context.close() 时,期望进程会正常地退出,但实际上进程并不会退出掉.原因就在于这些线程池中还在运行的线程。
本文描述了在基于spring boot的项目中,如何正确地配置线程池,以保证线程池能够正确的在整个spring容器周期内运行,并且在容器正常关闭时能够一并退出掉.

  • 定义bean时添加destroyMethod方法或相应生命周期方法
  • 设置线程池中线程为daemon
  • 为每个线程池正确地命名及使用ThreadFactory
  • 丢弃不再需要的周期性任务
  • 监听ContextClosedEvent,触发额外操作
继续阅读“在基于spring体系的业务中正确地关闭线程池”

Spring Boot中多个application配置文件在属性覆盖时List和Map的不同处理行为

在spring boot 项目中,经常会使用profile来控制不同环境的属性设置及覆盖行为,即通过高优先级的配置文件来覆盖默认的属性,以达到测试/生产环境配置生效的目的. 常规的字符串,bean属性在覆盖时均会通过标准的merge覆盖过程,达到优先查找高优先级的配置文件,如果存在,则使用此值,并且不再继续继续处理.

但如果一个属性为List或Map属性,更或者List,Map中的值对象更是一个复杂类型时,其处理过程就比较麻烦。详见

https://github.com/spring-projects/spring-boot/issues/4313
https://github.com/spring-projects/spring-boot/issues/9137

为处理此问题,最终spring boot标准化了针对List或Map类型的处理规则。详见:

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-complex-type-merge

本文为翻译原文配置中的信息

继续阅读“Spring Boot中多个application配置文件在属性覆盖时List和Map的不同处理行为”

spring data 运行时添加JPA Repository

在特定的业务场景中,需要提供一个类似自定义实体的动态对象,并根据此对象生成相应的CRUD Repository。在这种场景中,与正常的domain对象不同,这个对象是在运行时,才定义出来,并产生相应的domain class和相应的repository class类。在业务体系中,需要将在运行时定义的对象重新加载至运行时中,并与普通的domain一样的操作。

如 普通的 AbcRepository ,可以使用如下的代码进行操作

@Autowired
private AbcRepository abcRepository;

abcRepository.findAll()

而新产生的repository,则可以通过如下的手法进行操作

val repository = (CrudRepository)applicationContext.getBean("newRepository");
repository.findAll();

本文即描述,通过 spring cloud 中自带的 RepositoryConfigurationDelegate, 使用新的自定义ClassLoader, 如何在运行时注册一个新的repository.

继续阅读“spring data 运行时添加JPA Repository”

spring体系中控制List注入时的顺序

在spring框架内部以及业务开发中,经常有注入List<T>的场景出现,一般情况下各个T的实现是相互不相干的,这样业务上也不会有问题,但如果实现上有重叠的部分,那么在某些场景中即会出现奇怪的问题。

如下的2个实现

class Impl1 implements Impl{
    public boolean canHandle(int i) {
        return i < 10;
    }
}

class Impl2 implements Impl {
    public boolean canHandle(int i) {
        return i < 100;
    }
}

上述实现中,程序中注入List<Impl>时 理想的顺序是 1 -> 2, 这样在逻辑上更满足业务的需要.但也有可能,实际注入后为 2 -> 1. 在这种情况下,如果 传入的 值,为 0,那么逻辑将导向 Impl2,在业务上就没有按预期进行。

这里不讨论 Impl本身的实现问题,这里仅为参考。在实际代码上,包括spring 相关框架本身,都存在着实现子类的逻辑互相重叠且相互影响的情况,这时候,注入正确的顺序非常重要.

本文简单描述解决问题的2种做法, 再描述一下spring框架自带的bean排序器.

继续阅读“spring体系中控制List注入时的顺序”

使用X-Forwarded-*正确处理浏览器端地址信息

在WEB开发中,有一定的场景中需要拿到基于浏览器视角的路径信息。比如在spring hateoas, spring security,以及基于302跳转的各类登陆,认证体系。在这种场景中,服务端拿到的地址信息可能并不是实际浏览器端访问的路径信息,这时候就要考虑地址转换的事务。特别是当前的业务系统必定前端有Nginx,以及https转http类的反向代理等场景。

本文描述了标准的 X-Forwarded-Host, X-Forwarded-Port 和 X-Forwarded-Proto的使用以及在部分框架中使用的 X-Forwarded-Prefix。同时,在业务框架(如Spring)中已经实际应用的示例代码。在具体场景下应当如何来进行正确地使用。

这些头信息Nginx以及浏览器不会自动地添加到请求头中,需要显示地进行配置。

X-Forwarded-Host
一般配置为 $http_host, 表示获取浏览器在请求时的 HOST 头

X-Forwarded-Port
表示浏览器在访问时的实际端口时,如nginx提供http服务,则为80,如果是https,则为 443。如果为标准服务,此项可以不用设置,仅用于非标准端口时才使用

X-Forwarded-Proto
表示浏览器在访问时的实际协议,如nginx提供https转http反向代理时,这里即要配置为 https,表示浏览器是使用https协议来进行访问,但后端可能用http提供服务。如果两边协议相同,则不用设置

X-Forwarded-Prefix
用于当nginx使用前缀代理后端请求时使用。如 浏览器请求的路径为 serviceName/a/b/c, Nginx在Location端通过serviceName匹配到后端服务时,但把前缀 serviceName去掉,实际访问后端为 /a/b/c时,这时即需要设置此值。

继续阅读“使用X-Forwarded-*正确处理浏览器端地址信息”

Jackson中基于上下文拦截属性输出的两种实现方式

需求如下,在一个类中,有一些字段属性,其是否输出并不是由字段上的JsonIgnore来决定,而是根据从上下文(如request)中传递过来的某些参数决定。如下类:

class A {
    @ContextIgnored("field1")
    private String field1;

    @ContextIgnored("field2")
    private String field2;
}

当上下文值为 field1 时,则表示 A 的最终输出json 中没有字段 field1. 当上下文为 field2 时, 则最终输出没有字段 field2. 其它情况则输出所有字段。

本文讨论Jackson中的处理方式,如果使用 Fastjson,则有很多方式处理,这里不表.

本文描述通过利用 JsonIgnore 注解处理方式解决此问题 和 通过 PropertyFilter 两种方式来完成此需求的处理方式。

继续阅读“Jackson中基于上下文拦截属性输出的两种实现方式”