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

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

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

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

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注入时的顺序”

使用统一转换服务来处理不同数据展现的思路和实现

本文示例代码:https://github.com/flym/train-propertytranslate

本文描述了这样一个场景:
针对于一个功能场景,第三方过来的数据格式均不相同,但需要通过一个统一的功能接口来进行调用,然后根据不同来源数据格式进行不同的数据展现。在后端实现时,尽量不要通过if else进行硬编码,而是通过配置的方式来完成数据的处理和呈现。

场景中提到了几个概念,如下:

  • 功能场景和数据来源:分组信息,针对同一个来源,其格式是相同的。不同来源的数据格式不一样
  • 统一功能接口:调用入口是统一的,即方法名,参数定义,以及返回格式都是相同的,仅参数内容不相同
  • 配置化:场景是通用的,可以通过配置来实现,而不是在业务代码中硬编码
  • 不同的数据展现:可以针对不同的分组和场景通过模板来进行渲染,而模板本身是可以配置的,这样即隔离了数据封装这一层。

举例如下, 如下的一个数据内容:

{
    "trade_fullinfo_get_response": {
        "trade": {
            "seller_nick": "我在测试",
            "pic_name": "T1jVXXXePbXXaoPB6a_091917.jpg",

            "receiver_name": "东方不败",
            "buyer_message": "要送的礼物的,不要忘记的哦",
            "receiver_city": "杭州市",
            "receiver_district": "西湖区",
            "orders": {
                "order": [
                    {
                        "title": "苹果",
                        "unit": "个",
                        "oid": 1,
                        "price": 1.1,
                        "sum": 1
                    }
                ]
            },
        }
    }
}

通过转换服务处理,可以展现为下面两种不同的数据内容(以html渲染为例)

渲染场景1
渲染场景2

本文从数据格式,转换器,转换过程,数据渲染几个方面来描述这一思路.

继续阅读“使用统一转换服务来处理不同数据展现的思路和实现”

spring3.0版本configuration注解对象的启动过程分析

spring 3.0版本的一个很重要的特性在于支持注解式配置.之前在xml配置中的所有信息均可以通过注解进行配置了.本篇即从实现的角度理解spring是通过怎么样的机制来实现的.为了避免重复发明轮子,尽量采用之前已有的技术来进行实现才是最好的处理方式.

使用Configuration对象

整个的支持首先即是通过configuration这个对象来完成的.

第一种方式是通过声明AnnotationConfigApplicationContext上下文,然后通过调用其register方式来完成.如下参考所示:

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AppConfig.class);
        context.register(App2Config.class);

第二种方式,则是通过在一个统一的xml中声明<context:annotation-config/>,然后再通过声明<context:component-scan base-package="com.iflym"/>,将自己的@configuration对象放到相应的包下面,让spring容器自行进行解析即可.因为@Configuration注解上有声明@Component注解,因此也可以认为这也是注解一个bean的方式.

这两种方式均是通过将configBean放到spring容器当中,我们可以认为就是手动地注册相应的bean到容器当中.只不过这里的bean是特殊的configBean.

ConfigurationClassPostProcessor处理器

在整个spring容器处理周期当中,存在一个特殊的处理周期,即invokeBeanFactoryPostProcessors.这里即在容器完成初始解析之后,再进行特定的处理,以添加更多的bean或者对容器内的bean进行修改.这里分别通过接口BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor来完成.后者表示会对容器进行修改,会添加新的bean定义信息.

针对configBean的处理即是通过类ConfigurationClassPostProcessor来完成,其实现了BeanDefinitionRegistryPostProcessor接口,同时实现了BeanFactoryPostProcessor接口.(因为definitionRegistry接口本身即是继承了beanFactoryPost接口). 

ConfigurationClassPostProcessor类是通过静态方法AnnotationConfigUtils.registerAnnotationConfigProcessors注册到spring容器当中.而此静态方法,又是通过<context:annotation-config/>或者是AnnotationConfigApplicationContext声明自动完成此操作.

继续阅读“spring3.0版本configuration注解对象的启动过程分析”

spring 多placeHolder问题的解决方案

当前的spring版本为: 4.0.6.RELEASE
问题官方地址:https://jira.spring.io/browse/SPR-9989

问题重现

@Component
public class Tb {
    @Value("${tb.username:abcd}")
    private String username;

    public String getUsername() {
        return username;
    }
}

以上为定义bean,其中属性表示需要去获取属性为tb.username的属性定义,默认值为abcd。然后在spring分别如下配置

<context:property-placeholder location="classpath:springa.properties" ignore-unresolvable="true" ignore-resource-not-found="true"/>
<context:property-placeholder location="classpath:springb.properties" ignore-unresolvable="true" ignore-resource-not-found="true"/>

配置文件的值如下所示:
配置文件一:tb.username1=spring1
配置文件二:tb.username=spring2

因为在配置文件2中,有相应的配置定义,因此我们期望其返回username的值为 spring2。但是在实际运行中,此值即是abcd,如下输出所示:

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Tb tb = context.getBean(Tb.class);
        System.out.println("->" + tb.getUsername());
    }

//输出值
->abcd

本文即通过更换相应的属性解析器,用于解决此问题,以让spring能够正常的解析并输出我们需要的值。

继续阅读“spring 多placeHolder问题的解决方案”

使用线程安全的spring类型转换器ConversionService VS TypeConverter

此文翻译源:http://www.theserverside.com/tip/Spring-Converters-and-Formatters。部分无用的部分有删除

1 类型转换接口

在spring3.0之前,如果要自己实现一个从字符串到其它对象的转换,那么就需要实现PropertyEditor接口。PropertyEditor是遵循javaBean规范的属性处理器,其通过set方法设置属性值,通过get方法获取属性值,相应的转换逻辑就隐藏于其中。这个接口惟一的问题在于,他们不是线程安全的.并且只能实现从字符串到其它对象的转换。

在spring3.0,提供了一个更简单的类型转换器接口。在spring mvc中,可以使用内置的或者自己实现的接口来实现从请求参数到对象之间的转换。其相应的接口定义如下:

public interface Converter<S,T> {
    public T convert(S source);
}

即通过一个S类型的对象,将其转换为T类型的对象。其中S类型并不局限于字符串,可以是任何类型。这是与PropertyEditor的主要区别。

继续阅读“使用线程安全的spring类型转换器ConversionService VS TypeConverter”