在最近开发过程中,需要解析XML获得相应的数据。XML元素的属性存放了数据信息,每一个XML元素中的属性代表一条数据。其格式如下所示:
对于以上格式的XML数据,每一个person元素对应于Java中的一个bean对象。通常,可以通过注解标注bean中的成员变量来使其和XML元素中的属性一一对应,这样就可以通过反射将XML中的数据解析成bean对象。但是使用注解的方式需要在创建Bean类的时候需要为成员变量添加额外的注解,在成员变量数目比较多的情况下,这也会增加不少的工作量。
在以往使用Hibernate的过程中,知道Hibernate有别名映射的功能。XML解析成Bean实例也可以通过别名映射的方式实现。博文通过剥离出Hibernate中数据解析成bean实例的部分,并添加定制的功能来实现XML中的别名映射。
在Hibernate的映射实现中,定义了PropertyAccessor接口,该接口将Java的反射操作以面向对象的方式进行封装,并提供Getter和Setter接口来表示Bean中的getFoo()和setFoo(Value)方法。其代码如下:
PropertyAccessor.java:
public abstract class PropertyAccessor { /** * 创建一个合适的Getter对象,表示Bean中的setFoo(value)方法 * * @param clazz 类类型 * @param propertyName 属性名 * @return 返回一个合适的Getter对象,表示Bean中的setFoo(value)方法 * @throws PropertyNotFoundException 异常:表示未找到属性 */ public abstract Getter getGetter(Class clazz, String propertyName) throws PropertyNotFoundException; /** * 创建一个合适的Setter对象,表示Bean中的setFoo(value)方法 * * @param clazz 类类型 * @param propertyName 属性名 * @return 返回一个合适的Setter对象,表示Bean中属性对于的setFoo(value)方法 * @throws PropertyNotFoundException 异常:表示未找到属性 */ public abstract Setter getSetter(Class clazz, String propertyName) throws PropertyNotFoundException; }
Getter.java:
public abstract class Getter { /** * 从Obj实例中获得属性值 * @param obj 实例对象 * @return 返回属性值 * @throws GetterNotFoundException */ public abstract Object get(Object obj) throws GetterNotFoundException; /** * 获得返回类型 * * @return 返回类型 */ public abstract Class getReturnType(); }
Setter.java
public abstract class Setter { /** * 设置属性到实例中 * * @param obj 实例. * @param value 值. * @throws SetterNotFoundException */ public abstract void set(Object obj, Object value) throws SetterNotFoundException; /** * 获得值类型 */ public abstract Class getParamType(); }
对于PropertyAccessor、Setter、Getter接口的实现,大家可以查看Hibernate的源码深入了解。此处就不再详细展示,但在文末会提供定制过的实现类。
在Hibernate映射实现中,还定义了Transformer接口,用来实现数据转化成Bean对象。由于项目开发过程中,XML中的属性名有可能发生变化,但默认都是以下划线命名法,因此定制增加一个属性名转化接口,来将XML中的属性名映射成Bean中的属性名。此外,由于XML中的属性值都是字符串类型的,而Bean中的值类型并不固定,因此还定制实现一个值类型转化接口。对于属性名转化和值类型转化接口都有默认实现,也可支持自定义实现。
Transformer:
public abstract class Transformer { /** * tuples表示属性值数组,aliases表示属性名数组,transform方法则将属性值和属性名 * 转化成对于的Bean实例 * * @param tuples 属性值数组 * @param aliases 属性名数组 * @return Bean实例 */ public abstract Object transform(Object[] tuples, String[] aliases); }
AliasConverter.class:
/** * 别名转化器 * * @author Administrator * */ public static abstract class AliasConverter { public abstract String convert2SuitName(String aliasName); } /** * 默认别名转化器 * * @author Administrator * */ public static class DefaultAliasConvert extends AliasConverter { /** * 将下划线法命名改为驼峰命名 */ @Override public String convert2SuitName(String aliasName) { return aliasName.replaceAll("_", ""); } }
TupleConverter:
/** * 值类型转化器: * 在赋值过程中,可能存在tuple类型和setter方法值类型不一致的情况。 * 通过值类型转换器将tuple值转化为合适的类型。 * * @author Administrator * */ public static abstract class TupleConverter { protected Logger logger = LoggerFactory.getLogger(TupleConverter.class); public abstract Object convert2SuitTuple(Object obj, Class suitType); } protected static class DefaultTupleConvert extends TupleConverter { @Override public Object convert2SuitTuple(Object obj, Class suitType) { if (obj == null || suitType == null) { return obj; } // 如果obj的类型是suitType的子类,则直接转换 // 如果obj不是字符串类型,则不进行转换到目标类型,直接返回 if (suitType.isAssignableFrom(obj.getClass()) || !(obj instanceof String)) { return obj; } String suitTypeName = suitType.getName(); // try setFoo(int) if ("java.lang.Integer".equals(suitTypeName) || "int".equals(suitTypeName)) { return Integer.valueOf((String) obj); } // try setFoo(long) else if ("java.lang.Long".equals(suitTypeName) || "long".equals(suitTypeName)) { return Long.valueOf((String) obj); } // try setFoo(boolean) else if ("java.long.Boolean".equals(suitTypeName) || "boolean".equals(suitTypeName)) { return Boolean.valueOf((String) obj); } // try setFoo(Date) else if ("java.util.Date".equals(suitTypeName) || "java.sql.Date".equals(suitTypeName)) { } // try setFoo(Calendar) else if ("java.util.Calendar".equals(suitTypeName)) { } return obj; } }
最后,通过继承实现Transformer来完成别名映射的过程。其实现如下:
public class AliasToBeanResultTransformer extends Transformer { private final Class resultClass; private final AliasConverter aliasConverter; private final TupleConverter tupleConverter; private final PropertyAccessor propertyAccessor; private String[] aliases; // cache for aliases private Setter[] setters; // cache for setters private boolean isInitialilzed; public AliasToBeanResultTransformer(Class resultClass, TupleConverter tupleConverter, AliasConverter aliasConverter) { if (resultClass == null) { throw new IllegalArgumentException( "resultClass can not be null"); } // 目标对象 this.resultClass = resultClass; // 值类型转换器 this.tupleConverter = tupleConverter == null ? new DefaultTupleConvert() : tupleConverter; // 别名转换器 this.aliasConverter = aliasConverter == null ? new DefaultAliasConvert() : aliasConverter; propertyAccessor = new ChainedPropertyAccessor( new PropertyAccessor[] { PropertyAccessorFactory.getPropertyAccessor("basic"), PropertyAccessorFactory.getPropertyAccessor("field") } ); isInitialilzed = false; } /** * * 获得Setter集合 * * @param aliases */ private void initialize(String[] aliases) { this.aliases = aliases; setters = new Setter[aliases.length]; for ( int i = 0; i < aliases.length; i++ ) { String alias = aliases[i]; if (alias != null) { // 获得合适的Setter setters[ i ] = propertyAccessor.getSetter(resultClass, // 把别名转化为合适的名称 aliasConverter.convert2SuitName(alias)); } } isInitialilzed = true; } @Override public Object transform(Object[] tuples, String[] aliases) { Object result; try { if (!isInitialilzed) { initialize(aliases); } else { // 校验aliases是否发生改变 check(aliases); } result = resultClass.newInstance(); for ( int i = 0; i < aliases.length; i++ ) { if ( setters[i] != null ) { // 赋值 setters[i].set( result, // 把值转化为合适的类型 tupleConverter.convert2SuitTuple(tuples[i], setters[i].getParamType())); } } } catch (InstantiationException e) { throw new CmsException("Could not instantiate resultclass: " + resultClass.getName()); } catch (IllegalAccessException e) { throw new CmsException("Could not instantiate resultclass: " + resultClass.getName()); } return result; } private void check(String[] aliases) { if ( ! Arrays.equals( aliases, this.aliases ) ) { throw new IllegalStateException( "aliases are different from what is cached; aliases=" + Arrays.asList( aliases ) + " cached=" + Arrays.asList( this.aliases ) ); } } }
至此,实现了别名映射的过程。对于如何将XML中的数据映射成Bean实例,关键是定义别名映射的规则并自定义实现别名转化器。其使用方式如下:
@Test public void testTransform() { // ...省略解析XML的过程,一般封装为工具类 // 假设解析XML后获得的属性值 Object[] tuples = {"1", "hello world"}; // 假设解析XML后获得的属性名 String[] aliases = {"i_Num", "str_Value"}; // XML中的属性名采用下划线命名法,Bean中的属性名采用驼峰命名法, // 程序默认的别名转化器是下划线命名法转化为驼峰命名法,故别名转化器传入Null以使用默认的 AliasToBeanResultTransformer transformer = new AliasToBeanResultTransformer(TestBo.class, null, null); // 获得Bo类 TestBo testBo = (TestBo) transformer.transform(tuples, aliases); assertEquals(1, testBo.getiNum()); assertEquals("hello world", testBo.getStrValue()); } // 定义测试的Bo类 public static class TestBo { private int iNum; private String strValue; public int getiNum() { return iNum; } public void setiNum(int iNum) { this.iNum = iNum; } public String getStrValue() { return strValue; } public void setStrValue(String strValue) { this.strValue = strValue; } }