ROO中的ApplicationConversionServiceFactoryBean

问题提出

ROO项目中,第一次用web mvc命令新建一个controller后, ROO会在该controller所在的包中新建一个ApplicationConversionServiceFactoryBean类,这个类是干什么的呢?

线索

打开ApplicationConversionServiceFactoryBean.java文件,可得如下代码:

@RooConversionService
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {

    @Override
    protected void installFormatters(FormatterRegistry registry) {
        super.installFormatters(registry);
        // Register application converters and formatters
    }

    public Converter<SensorType, String> getSensorTypeToStringConverter() {
        return new org.springframework.core.convert.converter.Converter<demo.imlab.ims.entity.SensorType, java.lang.String>() {
            public String convert(SensorType sensorType) {
                return new StringBuilder().append(sensorType.toString()).append(" | ").append(sensorType.getDescription()).toString();
                //return sensorType.toString();
            }
        };
    }
}

从这段代码出发,我们一步步搞清楚这个ApplicationConversionServiceFactoryBean

FormattingConversionServiceFactoryBean

ApplicationConversionServiceFactoryBean继承于FormattingConversionServiceFactoryBean,打开FormattingConversionServiceFactoryBean.java文件,可见如下代码:

public class FormattingConversionServiceFactoryBean
        implements FactoryBean<FormattingConversionService>, EmbeddedValueResolverAware, InitializingBean {

    ....

}

先从它实现的接口出发,google了一下FactoryBeanFormattingConversionServiceEmbeddedValueResolverAwareInitializingBean这几个关键词:

FactoryBean

FactoryBean接口声明如下所示:

public interface FactoryBean {  
    Object getObject() throws Exception;  
    Class getObjectType();  
    boolean isSingleton();  
} 

这里可以大致了解其用途:

Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean。工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂Bean的getObject方法所返回的对象。
使用场景:1、通过外部对类是否是单例进行控制,该类自己无法感知 2、对类的创建之前进行初始化的操作,在afterPropertiesSet()中完成。

例如如下FactoryBean实现:

import org.joda.time.DateTime;  
import org.springframework.beans.factory.FactoryBean;  

public class NextDayDateFactoryBean implements FactoryBean {  

    public Object getObject() throws Exception {  
        return new DateTime().plusDays(1);  
    }  

    public Class getObjectType() {  
        return DateTime.class;  
    }  

    public boolean isSingleton() {  
        return false;  
    }  

} 

注册到容器:

<bean id="nextDayDateDisplayer" class="...NextDayDateDisplayer">  
    <property name="dateOfNextDay">  
        <ref bean="nextDayDate"/>  
    </property>  
</bean>  

<bean id="nextDayDate" class="...NextDayDateFactoryBean">  
</bean> 

若nextDayDateDisplayer类如下所示:

public class NextDayDateDisplayer  
{  
    private DateTime dateOfNextDay;  
    // 相应的setter方法  
    // ...  
} 

尽管注册时用的是NextDayDateFactoryBean类,但dateOfNextDay属性的类型是DateTime而不是NextDayDateFactoryBean。这就是说:

容器返回的是FactoryBean所”生产”的对象类型,而非FactoryBean实现本身。

所以,FactoryBean提供了一种高级的配置机制,令复杂的bean配置成为可能。

FormattingConversionService

之前写过一篇博文提到了spring mvc中数据类型转换、验证和格式化的流程,如图所示:

流程

从图中可以看出,Spring3引入了格式化转换器(Formatter SPI) 和格式化服务API(FormattingConversionService)来实现格式化(从Obejct到String),例如Date对象到String对象。该类实现了四个接口,分别是:EmbeddedValueResolverAware, ConversionService, ConverterRegistryFormatterRegistry

Formatter

先说一下Formatter接口,如下所示:

package org.springframework.format;  
public interface Formatter<T> extends Printer<T>, Parser<T> {  
}

其中Printer和Parser接口分别定义了String print(T object, Locale locale);T parse(String text, Locale locale) throws ParseException;方法。

我们用的时候可以这样用

CurrencyFormatter currencyFormatter = new CurrencyFormatter();  
currencyFormatter.setFractionDigits(2);

//从String到BigDecimal
Assert.assertEquals(new BigDecimal("123.13"), currencyFormatter.parse("$123.125", Locale.US));  
//从BigDecimal到String
Assert.assertEquals("¥123.00", currencyFormatter.print(new BigDecimal("123"), Locale.CHINA));  

可以看出,Formatter接口包装了Object和String类型之间的转换,主要应用在把view层用户输入或选择的数据转换成controller层的实体类型。

Converter

Converter接口定义的方法如下所示:

package org.springframework.core.convert.converter;  
public interface Converter<S, T> {
    T convert(S source);  
}  

若要实现多种类型之间的转换,可以用GenericConverter接口:

package org.springframework.core.convert.converter;  
public interface GenericConverter {  
    Set<ConvertiblePair> getConvertibleTypes();  
    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);  
}  

从上述可以看出,于Formatter不同,Converter负责任意类型之间的转换。

ConverterRegistry & FormatterRegistry

这两个接口主要用于用于注册格式化转换器

package org.springframework.format;  
public interface FormatterRegistry extends ConverterRegistry {  
    void addFormatter(Formatter<?> formatter);  
    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);  
    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); 
    void addFormatterForFieldAnnotation(  
                AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);  
}  

package org.springframework.core.convert.converter;  
public interface ConverterRegistry {  
    void addConverter(Converter<?, ?> converter);  
    void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);  
    void addConverter(GenericConverter converter);  
    void addConverterFactory(ConverterFactory<?, ?> converterFactory);  
    void removeConvertible(Class<?> sourceType, Class<?> targetType);  
} 

可以看出,ConverterRegistry 和 FormatterRegistry 声明了格式转换服务的注册接口。什么是“注册”?个人认为可以理解成spring在读取配置时,要将formatter等托管给某个统一的服务接口(facade模式?),这个服务接口由某个类来实现。而ConverterRegistry和FormatterRegistry尽管没有声明服务接口,但是给予了spring给该托管类注入formatter的能力。

ConversionService

参考api文档

A service interface for type conversion. This is the entry point into the convert system. Call convert(Object, Class) to perform a thread-safe type conversion using this system.

FormattingConversionService继承自ConversionService。而ConversionService接口声明了从任意类到任意类之间转换的接口:

package org.springframework.core.convert;  
public interface ConversionService {  
    boolean canConvert(Class<?> sourceType, Class<?> targetType);  
    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);  
    <T> T convert(Object source, Class<T> targetType);  
    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);  
}  

故FormattingConversionService代表从String到Object或者从Object的转换,即格式化转换。

综上,再来看看FormattingConversionService。其一般使用方法如下

DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();  
//默认不自动注册任何Formatter  
CurrencyFormatter currencyFormatter = new CurrencyFormatter();  
currencyFormatter.setFractionDigits(2);//保留小数点后几位  
currencyFormatter.setRoundingMode(RoundingMode.CEILING);//舍入模式(ceilling表示四舍五入)  
//注册Formatter SPI实现  
conversionService.addFormatter(currencyFormatter);  

//绑定Locale信息到ThreadLocal  
//FormattingConversionService内部自动获取作为Locale信息,如果不设值默认是 Locale.getDefault()  
LocaleContextHolder.setLocale(Locale.US);  
Assert.assertEquals("$1,234.13", conversionService.convert(new BigDecimal("1234.128"), String.class));

从该类的使用方法可以看出,ConversionService接口给了它转换类型的能力,ConverterRegistry和FormatterRegistry给了它被管理(注入)的能力。FormattingConversionService将不同的Formatter包装起来,对外提供统一的类型格式化服务接口。整个convert框架如下图所示:

框架

InitializingBean

google到这里

InitializingBean
Spirng的InitializingBean为bean提供了定义初始化方法的方式。InitializingBean是一个接口,它仅仅包含一个方法:afterPropertiesSet()。
Spring在设置完一个bean所有的合作者(依赖)后,会检查bean是否实现了InitializingBean接口,如果实现就调用bean的afterPropertiesSet方法。

可以看出InitializingBean接口的意义在于提供了一种bean装配完毕以后的自定义初始化入口。

EmbeddedValueResolverAware

请看这里

Interface to be implemented by any object that wishes to be notified of a StringValueResolver for the resolution of embedded definition values.
This is an alternative to a full ConfigurableBeanFactory dependency via the ApplicationContextAware/BeanFactoryAware interfaces.

留意到其中的StringValueResolver,google了一下,找到了这里

Simple strategy interface for resolving a String value. Used by ConfigurableBeanFactory.

这是什么意思呢?StringValueResolver是一个接口,resolve可以理解成“解析”,而spring在根据配置装配bean的时候,需要将配置中的String类型转换成其它类型,这个转换可以由实现来StringValueResolver接口的类来完成。

Aware的意思是“意识到的”,因此EmbeddedValueResolverAware接口的意思大概是某个bean实现了该接口,则在其被装配时,如果StringValueResolver被调用,则该resolver会通过void setEmbeddedValueResolver(StringValueResolver resolver)方法传入bean内部,用户可以在这里取得resolver。

综上,FormattingConversionServiceFactoryBean可以这样理解:

  1. 它是一个FactoryBean,在初始化时需要复杂的配置。
  2. 它“生成”一个FormattingConversionService实例,负责数据的格式化。
  3. 可以为它添加不同的formatter,令其支持不同的数据的格式化转换。
  4. afterPropertiesSet()方法在其初始化工作完成后被调用。

FormattingConversionServiceFactoryBean的实现

我们打开org.springframework.format.support.FormattingConversionServiceFactoryBean.java文件,可以观察到:

//一系列的set方法
...
public void afterPropertiesSet() {//在FormattingConversionService初始化后调用
    this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters);
    ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
    registerFormatters();
}

private void registerFormatters() {
    //设置formatters
    ...
    //
    installFormatters(this.conversionService);
}

//默认为空实现,留给子类实现
//Subclasses may override this method to register formatters and/or converters.
protected void installFormatters(FormatterRegistry registry) {
}

可以看出,FormattingConversionServiceFactoryBean提供了installFormatters()方法,使子类可以在父类完全生成后添加Formatter或者Converter。

ApplicationConversionServiceFactoryBean使用方法

默认的ApplicationConversionServiceFactoryBean实现中,只有一个空实现的方法:

@Override
protected void installFormatters(FormatterRegistry registry) {
    super.installFormatters(registry);
    // Register application converters and formatters
}

从上文可以得知,installFormatters方法是FormattingConversionServiceFactoryBean为实现方便地增添formatter功能而暴露给子类的一个空方法,其在FormattingConversionServiceFactoryBean装配完成后被调用。打开ApplicationConversionServiceFactoryBean_Roo_ConversionService.aj文件,可见一系列如下代码:

public Converter<Region, String> ApplicationConversionServiceFactoryBean.getRegionToStringConverter() {
    return new org.springframework.core.convert.converter.Converter<demo.imlab.ims.entity.Region, java.lang.String>() {
        public String convert(Region region) {
            return new StringBuilder().append(region.getIdentifier()).toString();
        }
    };
}

这些方法都是由ROO自动根据项目中的Entity的设置自动生成的,返回包括从ID到Object,Object到String等一系列Converter。文件的最后有还有两个方法:

public void ApplicationConversionServiceFactoryBean.installLabelConverters(FormatterRegistry registry) {
    registry.addConverter(getRegionToStringConverter());
    ...
    registry.addConverter(getStringToSensorLayoutConverter());
}

public void ApplicationConversionServiceFactoryBean.afterPropertiesSet() {
    super.afterPropertiesSet();
    installLabelConverters(getObject());
}

由此可以看出ApplicationConversionServiceFactoryBean的调用顺序为:

ApplicationConversionServiceFactoryBean.afterPropertiesSet() -> FormattingConversionServiceFactoryBean.afterPropertiesSet() -> FormattingConversionServiceFactoryBean.registerFormatters() -> ApplicationConversionServiceFactoryBean.installFormatters() -> ApplicationConversionServiceFactoryBean.installLabelConverters()

可以看出,getStringToSensorLayoutConverter()等一系列方法返回从某类到某类的converter,通过registry接口加入到service中,而installFormatters()方法暴露了一个“添加点”。所以,在自定义converter工厂方法后,要在installFormatters中调用registry.addConverter()完成注册。

ROO如何与ApplicationConversionServiceFactoryBean结合?

按ctrl+H,输入ApplicationConversionServiceFactoryBean进行全局文件搜索,在webmvc-config.xml文件中可见如下bean定义:

<bean class="demo.imlab.ims.controller.ApplicationConversionServiceFactoryBean" id="applicationConversionService"/>
</beans>

又见如下定义:

<!-- Turns on support for mapping requests to Spring MVC @Controller methods
     Also registers default Formatters and Validators for use across all @Controllers -->
<mvc:annotation-driven conversion-service="applicationConversionService"/>

根据注解可以大概了解到这个的作用是令Spring MVC支持@Controller注解和注册了一系列Formatters和Validators用于@**Format和@vaild标签。那么这个究竟是什么意思呢?

这里提到,实际上声明了两个bean:DefaultAnnotationHandlerMappingAnnotationMethodHandlerAdapter

DefaultAnnotationHandlerMapping

这幅图 可以了解到,HandlerMapping负责url到controller的转发,而DefaultAnnotationHandlerMapping则负责

Implementation of the HandlerMapping interface that maps handlers based on portlet modes expressed through the RequestMapping annotation at the type or method level.

可以得知,DefaultAnnotationHandlerMapping实现了url到通过@RequestMapping注解标志的controller的转发功能。

AnnotationMethodHandlerAdapter

先说一下HandlerAdapterHandlerAdapterHandlerMapping的一个代理类,DispatcherServlet通过该代理来访问所有装配好的Handlers。 而AnnotationMethodHandlerAdapter从这里可以了解到:

Implementation of the HandlerAdapter interface that maps handler methods based on HTTP paths, HTTP methods and request parameters expressed through the RequestMapping annotation.

Supports request parameter binding through the RequestParam annotation. Also supports the ModelAttribute annotation for exposing model attribute values to the view, as well as InitBinder for binder initialization methods and SessionAttributes for automatic session management of specific attributes.

AnnotationMethodHandlerAdapter实现了handler转发请求过程中的参数绑定,@RequestParam、@ModelAttribute注解的解析。

综上,可以知道,<mvc:annotation-driven conversion-service="applicationConversionService"/>的意思是,实现了把请求转发到@controller注解标注的controller类,并解析了@RequestParam,@Valid等等标签,最后该标签的conversion-service属性指定了在转发请求中采用名为applicationConversionService的bean作为数据格式化,数据转换service。

总结

最后总结一下ApplicationConversionServiceFactoryBean的作用和调用流程:spring容器根据webmvc-config.xml进行初始化,根据<mvc:annotation-driven conversion-service="applicationConversionService"/>标签初始化了整个URLMapping和HTTP请求参数绑定、验证等工作,然后我们可以通过@RequestMapping来注册转发路径和通过@RequestParam等注解处理HTTP参数。该标签同时指定了ApplicationConversionServiceFactoryBean返回的ApplicationConversionService作为这些请求的数据格式化和数据转化的服务类,该服务类负责请求参数(通常为字符串)到实体的转换,所以我们能在controller的某个method里面直接取得实体类。我们可以通过修改或自定义Convertor来实现不同类型之间的转换,这种转换被注册到ApplicationConversionServiceFactoryBean之中,通常用来自定义实体类在view层的显示方式(转换成怎么样的字符串),也可以实现自定义form参数到实体类的转换方式。

发表评论

您的电子邮箱地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据