最近工作中总是发现类型不匹配的现象。比如说在mybatis里面,如果表里面的数据类型是timestamp ,如果你的dto对应字段是Date类型那么就会发生出来的数据不是你要的样子。这个时候你需要自己写一个自定义的转换器,并将其配置到mybatis的配置文件中。 第一步:配置
<typeHandlers>
<typeHandler handler="com.framework.demo.utils.MyDateTypeHandler" javaType="String" jdbcType="TIMESTAMP"/>
</typeHandlers>
java代码主要是实现接口TypeHandler<T>接口。比如讲temestamp类型的数据转换成自定义格式的string 第二步:编写代码
public class MyDateTypeHandler implements TypeHandler<String> {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void setParameter(java.sql.PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, String.valueOf(parameter));
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
String sqlTimestamp = rs.getString(columnName);
if (sqlTimestamp != null) {
try {
return sqlTimestamp.substring(0,sqlTimestamp.lastIndexOf("."));
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
Date sqlTimestamp = rs.getDate(columnIndex);
if (sqlTimestamp != null) {
try {
return dateFormat.format(sqlTimestamp);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
@Override
public String getResult(java.sql.CallableStatement cs, int columnIndex) throws SQLException {
String sqlTimestamp = cs.getString(columnIndex);
if (sqlTimestamp != null) {
try {
return dateFormat.format(sqlTimestamp);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
这样就完成了,我发现如果不写这个自定义也能够转换,只是出来的字符串 多了一个(.0)。不知道什么原因。
除了mybatis经常会遇到类型转换之外,spring中的类型也是随处可见。 #案例 在我的项目中有这样一种配置
@RequestMapping(value = "{id}/delete", method = RequestMethod.GET)
public String showDeleteForm(@PathVariable("id") M m, Model model)
这句话不只是将id映射到M中对应字段中,是将id对应的表行信息映射到M中去。 第一步,自定义类型转换器。
public class DomainClassConverter<T extends ConversionService & ConverterRegistry> implements
ConditionalGenericConverter, ApplicationContextAware {
private ApplicationContext beanFactory;
public DomainClassConverter(){
this.beanFactory = null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanFactory =applicationContext;
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
if(FieldAccessVo.class.isAssignableFrom(targetType.getObjectType())&&targetType.hasAnnotation(PathVariable.class)){
return true;
}
return false;
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null || !StringUtils.hasText(source.toString())) {
return null;
}
Class<?> domainType = targetType.getType();
String daoImpl = domainType.getSimpleName().substring(0,1).toLowerCase()+domainType.getSimpleName().substring(1, domainType.getSimpleName().length())+"Dao";
try{
ConfigurableBaseSqlMapDao bean =(ConfigurableBaseSqlMapDao)beanFactory.getBean(daoImpl);
return bean.findById(Long.valueOf(source.toString()));
}catch (Exception e1){
return null;
}
}
这里的原意是找到service的实现类,然后带着事务去查数据。可是我发现,即使我找到了service的实现类,这个代理类要想真正被调用,需要method方法,而我底层是泛型,method方法怎么也映射到不。于是,我找到dao对应的方法去调用去获取数据。 第二步,将类型转换器配置到xml中 spring为我们提供了工厂类,我们只要将我们要注册的converter实例放置到工厂类相应参数当中,最后会将转换器放置到上下文中
<!-- 类型转换及数据格式化 -->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean id="domainClassConverter" class="com.framework.demo.web.controller.converter.DomainClassConverter"/>
<bean id="stringToDateConverter" class="com.framework.demo.utils.converter.StringToDateConverter"/>
</set>
</property>
</bean>
这样转换器就根据matcher匹配之后调用转换方法去做转换动作。 #看看底层 FormattingConversionServiceFactoryBean这个工厂类很重要。他里面有转换器converter和格式转换器formater。最重要的里面有一个FormattingConversionService。这个类图解如下
图1 放置位置
可以看出这个类其实是一个注册器的派生,既能够注册converter又能够注册formater。它的实例化如下:
this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters);
这样什么converter、formater都在conversionService里面了,那么spring怎么用的呢? 首先:来看看类AnnotationDrivenBeanDefinitionParser 其中的方法getConversionService 将它包装成beanDefinition
private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
RuntimeBeanReference conversionServiceRef;
if (element.hasAttribute("conversion-service")) {
conversionServiceRef = new RuntimeBeanReference(element.getAttribute("conversion-service"));
}
else {
RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
conversionDef.setSource(source);
conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String conversionName = parserContext.getReaderContext().registerWithGeneratedName(conversionDef);
parserContext.registerComponent(new BeanComponentDefinition(conversionDef, conversionName));
conversionServiceRef = new RuntimeBeanReference(conversionName);
}
return conversionServiceRef;
}
这样它就在上下文中了。在需要转换的类将conversionService注入到类中做类型转换。
看图一发现转换这个类的关系非常紧密,也是为什么spring代码可扩展和可维护性特别强。我们不妨从头到尾看看每一个接口,每一个实现类是组合组织的。
首先是converter的注册中心:接口ConverterRegistry。该接口对外提供注册和注销converter的功能。
然后是formatter的注册中心:接口FormatterRegistry。该接口继承自ConverterRegistry,所以说实现了该接口的实现了肯定也实现了ConverterRegistry。图中可以看出FormattingConversionService类实现了该接口。
第三是接口ConversionService:有了上面的两个接口,那么converter和formatter就能够注册进来了。第三个接口ConversionService就是定义了如何使用这些注册进来的converter和formatter。 第四个就是ConfigurableConversionService接口:该接口扩展了两个接口ConversionService, ConverterRegistry 。将注册converter、formatter的功能和使用converter、formatter的功能结合在了一起。
第四个是实现类GenericConversionService。该实现类实现了converter、formatter注册操作和如何使用converter、formatter的操作。
注意这里面的代码组织。
首先是内部类Converters。这个类对所有注册过来的converter进行统一管理。
第二个就是缓存组织:converterCache。每一次都那么从converters里面查效率低下。配置一个HashMap类型的converterCache可以有效的缓存源类型和目标类型与转换器之间的关系。其中ConverterCacheKey的组织还是很重要的。一定要记住定义equals 和compareTo方法。确保这个key唯一性。
最后,我们发现它还给工厂留了一个是怕。将factory和converter做了一个适配。这样能够将factory“伪装”成converter注册到converters中。
最后一个实现类DefaultFormattingConversionService。该类在GenericConversionService的基础上组合了三个jsr354Present、jsr310Present、jodaTimePresent。将相应类型的converter注册进来。
至此一个简单的类组织接口就介绍完毕。我们平时写业务代码的时候总是觉得可扩展性不足,可维护性捉襟见肘。往往问题出现在代码的组织结构上。我们很少用内部类,将功能细分。往往忘了按照功能 划分各个类的关系。接口是提供服务在载体。只有接口的服务定义的清晰,代码才能够做到可扩展。在本案例能够让我们感觉到代码结构十分清晰,就是因为接口见关系清晰的原因吧。
欢迎加入qq群: 122567875 欢迎关注微信公众号:hyssop的后花园