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

本文示例代码: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”

spring LTW agent方式在tomcat 8下无效

在常规的spring ltw中,我们对tomcat使用ltw,一般是以下两种方式。

//1 在启动项中添加javaagent选项
-javaagent:e:/spring-instrument-4.1.1.RELEASE.jar

//2 使用自定义的启动器,在META-INF/context.xml中增加以下内容
<?xml version="1.0" encoding="UTF-8" ?>
<Context>
 <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader" /> 
</Context>

这两种方式在tomcat6以及tomcat7均可以正常工作。但对于tomcat8,第1种方式已经不能再工作,仅能使用第二种方式。原因就在于tomcat8的webClassLoader已经提供了InstrumentableClassLoader接口。此接口将导致spring直接将aspectj的AspectJClassBypassingClassFileTransformer 直接添加到tomcat的classLoader中。但由于不是很正确的实现方式,导致aspect在使用tomcat8提供的classLoader时,并不能有效地对自己的advice进行weaver,导致报以下的错误信息: 

java.lang.NoSuchMethodError: XXXAdvice.aspectOf()LXXXAdvice;
 atXXX.index(AbcController.java:30)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
 at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
 at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)

这个错误的产生在于aspectJ的初始化过程和classLoader之间的交互行为,以及tomcat8中不准确的缓存行为。

继续阅读“spring LTW agent方式在tomcat 8下无效”

aspectj中call和execute在spring架构中的重要区别

在使用aspectj进行定义pointcut时,经常碰到的问题就是该使用call还是execute。当然,在spring中,只支持execute,而不支持call,但使用aspectj如ltw时就可以使用,但两者究竟有什么区别,最大的区别在哪儿,适用点又在哪儿。这就要从定义出来,来了解相关信息了。

在文档《aspectj_in_action_second_editon.pdf》中,针对二者,主要如下面所述:

a call is on the caller side, whereas execution happens on the receiver side—they’re two completely different places. 

即call工作在调用端,而execute工作在被调用端。
如我们将在pointcut打印一句helloworld。那么针对helloAction调用helloService而言。针对call调用,这句打印将会在helloAction中调用,而execute调用,而是在helloService中调用。

以上只是表面上的区别,在具体使用时其实并不是太大的差别,但如果有以下一句话,那么区别就可能完全不一样了:

aspectj是通过修改字节码来完成相应的功能,通过在相应的pointcut定义指定的advice来完成功能。

具体点说,就是aspectj首先需要能够匹配到相应的pointcut,然后才能执行相应的advice方法,如果不能匹配到pointcut,那么就不能执行相应的方法。

继续阅读“aspectj中call和execute在spring架构中的重要区别”