在运行时(dynamic)创build简单的POJO类(字节码)

我有以下情况

我正在写一些工具,运行用户input的查询对数据库并返回结果..

最简单的方法是返回结果为: List<String[]>但我需要更进一步。

我需要创build(在运行时 )一些名称的POJO(或DTO),并创build它的字段,设置器和获取器,并使用返回的数据填充它,然后将其返回给用户。

所以这里的想法是如何创build简单的类(字节码)在运行时(dynamic)我做了一个基本的search,发现很多lib 包括Apache BCEL但我想我需要更简单的东西…

你怎么看?

谢谢。

如果你使用CGLib,用getter和setter创build一个简单的POJO是很简单的:

 public static Class<?> createBeanClass( /* fully qualified class name */ final String className, /* bean properties, name -> type */ final Map<String, Class<?>> properties){ final BeanGenerator beanGenerator = new BeanGenerator(); /* use our own hard coded class name instead of a real naming policy */ beanGenerator.setNamingPolicy(new NamingPolicy(){ @Override public String getClassName(final String prefix, final String source, final Object key, final Predicate names){ return className; }}); BeanGenerator.addProperties(beanGenerator, properties); return (Class<?>) beanGenerator.createClass(); } 

testing代码:

 public static void main(final String[] args) throws Exception{ final Map<String, Class<?>> properties = new HashMap<String, Class<?>>(); properties.put("foo", Integer.class); properties.put("bar", String.class); properties.put("baz", int[].class); final Class<?> beanClass = createBeanClass("some.ClassName", properties); System.out.println(beanClass); for(final Method method : beanClass.getDeclaredMethods()){ System.out.println(method); } } 

输出:

类some.ClassName
public int [] some.ClassName.getBaz()
public void some.ClassName.setBaz(int [])
public java.lang.Integer some.ClassName.getFoo()
public void some.ClassName.setFoo(java.lang.Integer)
public java.lang.String some.ClassName.getBar()
public void some.ClassName.setBar(java.lang.String)

但问题是:你没有办法对这些方法进行编码,因为它们在编译时不存在,所以我不知道这会对你有什么好处。

过去我曾经使用过ASM。 我喜欢的是可以创build代码来生成一个类的ASMifier。 例如,我使用Java代码创build了一个genericsPOJO,每个types都有一个字段,并使用ASMifier创buildJava代码,以便从字节代码创build此代码,并将其用作模板以生成任意POJO。

正如@Michael所build议的那样,您可能需要添加一个不reflection的方式来获取任意字段。 例如

 public Set<String> fieldNames(); public Object getField(String name); public void setField(String name, Object name); 

你为什么要这样做? 有许多方法可以使用Map<String, Object>样式对象比使用常规映射更高效。

另一种方法是使用Velocity生成Java源代码,并使用Compiler API编译代码。 它是一个痛苦的使用,所以我在这里写了一个包装它Essence JCF使用这种方法的唯一优点是你可以轻松地debugging你生成的代码。 (该库可以select将java代码保存到debugging器可以find的地方,这样当你进入生成的代码时)

调用者用一个即时生成的类来做什么 ,因此他的代码无法知道? 访问它的唯一方法是通过反思。 返回一个List<String[]>Map<String, String>实际上是一个更清洁,更实用的devise。

我也讨厌写getters和setter。 我宁愿使用POJO,甚至声明为嵌套类的POJO。

还有另外一种方法可以做到,即使是使用旧的服务器和技术,也不需要引入Spring(我们使用JBoss 4.2和JBoss的不完整的EJB 3.0)。 扩展org.apache.commons.beanutils.BeanMap,你可以将POJO包装在一个bean映射中,当你得到或者放入时,你可以使用reflection来操纵这些字段。 如果getter或setter不存在,我们只需使用字段操作来获取它。 显然这不是一个真正的豆,所以这是完全正确的。

 package com.aaa.ejb.common; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Set; import org.apache.commons.beanutils.BeanMap; import org.apache.commons.collections.set.UnmodifiableSet; import org.apache.log4j.Logger; /** * I want the bean map to be able to handle a POJO. * @author gbishop */ public final class NoGetterBeanMap extends BeanMap { private static final Logger LOG = Logger.getLogger(NoGetterBeanMap.class); /** * Gets a bean map that can handle writing to a pojo with no getters or setters. * @param bean */ public NoGetterBeanMap(Object bean) { super(bean); } /* (non-Javadoc) * @see org.apache.commons.beanutils.BeanMap#get(java.lang.Object) */ public Object get(Object name) { Object bean = getBean(); if ( bean != null ) { Method method = getReadMethod( name ); if ( method != null ) { try { return method.invoke( bean, NULL_ARGUMENTS ); } catch ( IllegalAccessException e ) { logWarn( e ); } catch ( IllegalArgumentException e ) { logWarn( e ); } catch ( InvocationTargetException e ) { logWarn( e ); } catch ( NullPointerException e ) { logWarn( e ); } } else { if(name instanceof String) { Class<?> c = bean.getClass(); try { Field datafield = c.getDeclaredField( (String)name ); datafield.setAccessible(true); return datafield.get(bean); } catch (SecurityException e) { throw new IllegalArgumentException( e.getMessage() ); } catch (NoSuchFieldException e) { throw new IllegalArgumentException( e.getMessage() ); } catch (IllegalAccessException e) { throw new IllegalArgumentException( e.getMessage() ); } } } } return null; } /* (non-Javadoc) * @see org.apache.commons.beanutils.BeanMap#put(java.lang.Object, java.lang.Object) */ public Object put(Object name, Object value) throws IllegalArgumentException, ClassCastException { Object bean = getBean(); if ( bean != null ) { Object oldValue = get( name ); Method method = getWriteMethod( name ); Object newValue = null; if ( method == null ) { if(name instanceof String) {//I'm going to try setting the property directly on the bean. Class<?> c = bean.getClass(); try { Field datafield = c.getDeclaredField( (String)name ); datafield.setAccessible(true); datafield.set(bean, value); newValue = datafield.get(bean); } catch (SecurityException e) { throw new IllegalArgumentException( e.getMessage() ); } catch (NoSuchFieldException e) { throw new IllegalArgumentException( e.getMessage() ); } catch (IllegalAccessException e) { throw new IllegalArgumentException( e.getMessage() ); } } else { throw new IllegalArgumentException( "The bean of type: "+ bean.getClass().getName() + " has no property called: " + name ); } } else { try { Object[] arguments = createWriteMethodArguments( method, value ); method.invoke( bean, arguments ); newValue = get( name ); } catch ( InvocationTargetException e ) { logInfo( e ); throw new IllegalArgumentException( e.getMessage() ); } catch ( IllegalAccessException e ) { logInfo( e ); throw new IllegalArgumentException( e.getMessage() ); } firePropertyChange( name, oldValue, newValue ); } return oldValue; } return null; } /* (non-Javadoc) * @see org.apache.commons.beanutils.BeanMap#keySet() */ public Set keySet() { Class<?> c = getBean().getClass(); Field[] fields = c.getDeclaredFields(); Set<String> keySet = new HashSet<String>(super.keySet()); for(Field f: fields){ if( Modifier.isPublic(f.getModifiers()) && !keySet.contains(f.getName())){ keySet.add(f.getName()); } } keySet.remove("class"); return UnmodifiableSet.decorate(keySet); } } 

棘手的部分是分解POJO返回,但reflection可以帮助你:

 /** * Returns a new instance of the specified object. If the object is a bean, * (serializable, with a default zero argument constructor), the default * constructor is called. If the object is a Cloneable, it is cloned, if the * object is a POJO or a nested POJO, it is cloned using the default * zero argument constructor through reflection. Such objects should only be * used as transfer objects since their constructors and initialization code * (if any) have not have been called. * @param obj * @return A new copy of the object, it's fields are blank. */ public static Object constructBeanOrPOJO(final Object obj) { Constructor<?> ctor = null; Object retval = null; //Try to invoke where it's Serializable and has a public zero argument constructor. if(obj instanceof Serializable){ try { ctor = obj.getClass().getConstructor((Class<?>)null); if(ctor.isAccessible()){ retval = ctor.newInstance(); //LOG.info("Serializable class called with a public constructor."); return retval; } } catch (Exception ignoredTryConeable) { } } //Maybe it's Clonable. if(obj instanceof Cloneable){ try { Method clone = obj.getClass().getMethod("clone"); clone.setAccessible(true); retval = clone.invoke(obj); //LOG.info("Cloneable class called."); return retval; } catch (Exception ignoredTryUnNestedClass) { } } try { //Maybe it's not a nested class. ctor = obj.getClass().getDeclaredConstructor((Class<?>)null); ctor.setAccessible(true); retval = ctor.newInstance(); //LOG.info("Class called with no public constructor."); return retval; } catch (Exception ignoredTryNestedClass) { } try { Constructor[] cs = obj.getClass().getDeclaredConstructors(); for(Constructor<?> c: cs){ if(c.getTypeParameters().length==0){ ctor = c; ctor.setAccessible(true); retval = ctor.newInstance(); return retval; } } //Try a nested class class. Field parent = obj.getClass().getDeclaredField("this$0"); parent.setAccessible(true); Object outer = (Object) parent.get(obj); //ctor = (Constructor<? extends Object>) obj.getClass().getConstructors()[0];//NO, getDECLAREDConstructors!!! ctor = (Constructor<? extends Object>) obj.getClass().getDeclaredConstructor(parent.get(obj).getClass()); ctor.setAccessible(true); retval = ctor.newInstance(outer); //LOG.info("Nested class called with no public constructor."); return retval; } catch (Exception failure) { throw new IllegalArgumentException(failure); } } 

从bean,可复制或POJO获取genericsbean的示例代码:

 public List<Object> getGenericEJBData(String tableName, Object desiredFields, Object beanCriteria){ NoGetterBeanMap desiredFieldMap = new NoGetterBeanMap(desiredFields); NoGetterBeanMap criteriaMap = new NoGetterBeanMap(beanCriteria); List<Object> data = new ArrayList<Object>(); List<Map<String, Object>> mapData = getGenericEJBData(tableName, desiredFieldMap, criteriaMap); for (Map<String,Object> row: mapData) { Object bean = NoGetterBeanMap.constructBeanOrPOJO(desiredFields);//Cool eh? new NoGetterBeanMap(bean).putAll(row);//Put the data back in too! data.add(bean); } return data; } 

使用EJB的示例:

 IGenericBean genericRemote = BeanLocator.lookup(IGenericBean.class); //This is the minimum required typing. class DesiredDataPOJO { public String makename="";//Name matches column and return type. } class CriteriaPOJO { //Names match column and contains criteria values. public String modelname=model,yearid=year; } List<DesiredDataPOJO> data = genericRemote.getGenericEJBData(ACES_VEHICLE_TABLE, new DesiredDataPOJO(), new CriteriaPOJO() ); for (DesiredDataPOJO o: data) { makes.add(o.makename); } 

EJB有这样一个接口:

 package com.aaa.ejb.common.interfaces; import java.util.List; import java.util.Map; import javax.ejb.Local; import javax.ejb.Remote; /** * @see * http://trycatchfinally.blogspot.com/2006/03/remote-or-local-interface.html * * Note that the local and remote interfaces extend a common business interface. * Also note that the local and remote interfaces are nested within the business * interface. I like this model because it reduces the clutter, keeps related * interfaces together, and eases understanding. * * When using dependency injection, you can specify explicitly whether you want * the remote or local interface. For example: * @EJB(beanInterface=services.DistrictService.IRemote.class) * public final void setDistrictService(DistrictService districtService) { * this.districtService = districtService; * } */ public interface IGenericBean { @Remote public interface IRemote extends IGenericBean { } @Local public interface ILocal extends IGenericBean { } /** * Gets a list of beans containing data. * Requires a table name and pair of beans containing the fields * to return and the criteria to use. * * You can even use anonymous inner classes for the criteria. * EX: new Object() { public String modelname=model,yearid=year; } * * @param tableName * @param fields * @param criteria * @return */ public <DesiredFields> List<DesiredFields> getGenericEJBData(String tableName, DesiredFields desiredFields, Object beanCriteria); } 

你可以想象ejb实现是什么样子的,现在我们正在编写准备好的语句并且调用它们,但是我们可以使用标准,或者像hibernate或者其他任何想要的东西。

这里是一个粗略的例子(有些不喜欢这个部分)。 在这个例子中,我们有一个以第三范式表示数据的表格。 为此,bean字段必须与表列名称匹配。 toLowerCase()调用的情况下,这是一个真正的bean被使用,这将搞砸名称匹配(MyField与getMyfield)。 这部分可能会更好打磨。 特别是顺序和不同应该是旗帜什么的。 有可能还会发生其他边缘条件。 当然,我只需要写这个ONCE,而且对于性能来说,没有什么能够阻止你对性能数据有更精确的接收。

 @Stateless public class GenericBean implements ILocal, IRemote { ... /* (non-Javadoc) * @see com.aaa.ejb.acesvehicle.beans.interfaces.IAcesVehicleBean#getGenericEJBData(java.lang.String, java.util.Map, java.util.Map) */ @Override public List<Map<String, Object>> getGenericEJBData(String tableName,Map<String, Object> desiredFields, Map<String, Object> criteria){ try { List<Map<String,Object>> dataFieldKeyValuePairs = new ArrayList<Map<String,Object>>(); StringBuilder sql = new StringBuilder("SELECT DISTINCT "); int selectDistinctLength = sql.length(); for(Object key : desiredFields.keySet()){ if(desiredFields.get(key)!=null) { sql.append(key).append(", "); } } sql.setLength(sql.length()-2);//Remove last COMMA. int fieldsLength = sql.length(); sql.append(" FROM ").append(tableName).append(" WHERE "); String sep = "";//I like this, I like it a lot. for(Object key : criteria.keySet()){ sql.append(sep); sql.append(key).append(" = COALESCE(?,").append(key).append(") "); sep = "AND "; } sql.append(" ORDER BY ").append(sql.substring(selectDistinctLength, fieldsLength)); PreparedStatement ps = connection.prepareStatement(sql.toString(), ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); int criteriaCounter=1; for(Object key : criteria.keySet()){ ps.setObject(criteriaCounter++, criteria.get(key)); } ResultSet rs = ps.executeQuery(); while (rs.next()) { Map<String,Object> data = new HashMap<String,Object>(); int columnIndex = rs.getMetaData().getColumnCount(); for(int x=0;x<columnIndex;x++){ String columnName = rs.getMetaData().getColumnName(x+1); if(desiredFields.keySet().contains(columnName.toLowerCase())){ //Handle bean getters and setters with different case than metadata case. data.put(columnName.toLowerCase(), rs.getObject(x+1)); } else { data.put(columnName, rs.getObject(x+1)); } } dataFieldKeyValuePairs.add(data); } rs.close(); ps.close(); return dataFieldKeyValuePairs; } catch (SQLException sqle) { LOG.debug("National database access failed.", sqle); throw new EJBException(new DataSourceException("Database access failed. \n" + "getGenericEJBData()", sqle.getMessage())); } } 

那么这也可以试一试 。 但是,如果有人能解释,我需要了解这一点。

更新:

想象一下,您的应用程序必须在运行时从一些外部configurationdynamic创buildJava POJO实例。 这个任务可以很容易地使用一个字节码操作库来完成。 这篇文章演示了如何使用Javassist库来完成这项工作。

假设我们对dynamic创build的POJO应包含的属性进行了以下configuration:

 Map<String, Class<?>> props = new HashMap<String, Class<?>>(); props.put("foo", Integer.class); props.put("bar", String.class); 

我们来编写一个PojoGenerator,它为给定的类名和一个包含所需属性的地图dynamic生成一个Class对象:

 import java.io.Serializable; import java.util.Map; import java.util.Map.Entry; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.CtMethod; import javassist.NotFoundException; public class PojoGenerator { public static Class generate(String className, Map<String, Class<?>> properties) throws NotFoundException, CannotCompileException { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass(className); // add this to define a super class to extend // cc.setSuperclass(resolveCtClass(MySuperClass.class)); // add this to define an interface to implement cc.addInterface(resolveCtClass(Serializable.class)); for (Entry<String, Class<?>> entry : properties.entrySet()) { cc.addField(new CtField(resolveCtClass(entry.getValue()), entry.getKey(), cc)); // add getter cc.addMethod(generateGetter(cc, entry.getKey(), entry.getValue())); // add setter cc.addMethod(generateSetter(cc, entry.getKey(), entry.getValue())); } return cc.toClass(); } private static CtMethod generateGetter(CtClass declaringClass, String fieldName, Class fieldClass) throws CannotCompileException { String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); StringBuffer sb = new StringBuffer(); sb.append("public ").append(fieldClass.getName()).append(" ") .append(getterName).append("(){").append("return this.") .append(fieldName).append(";").append("}"); return CtMethod.make(sb.toString(), declaringClass); } private static CtMethod generateSetter(CtClass declaringClass, String fieldName, Class fieldClass) throws CannotCompileException { String setterName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); StringBuffer sb = new StringBuffer(); sb.append("public void ").append(setterName).append("(") .append(fieldClass.getName()).append(" ").append(fieldName) .append(")").append("{").append("this.").append(fieldName) .append("=").append(fieldName).append(";").append("}"); return CtMethod.make(sb.toString(), declaringClass); } private static CtClass resolveCtClass(Class clazz) throws NotFoundException { ClassPool pool = ClassPool.getDefault(); return pool.get(clazz.getName()); } } 

而已!

使用PojoGenerator非常简单。 让我们生成一些POJO,通过reflection所有的方法输出,设置然后得到一些属性:

 public static void main(String[] args) throws Exception { Map<String, Class<?>> props = new HashMap<String, Class<?>>(); props.put("foo", Integer.class); props.put("bar", String.class); Class<?> clazz = PojoGenerator.generate( "net.javaforge.blog.javassist.Pojo$Generated", props); Object obj = clazz.newInstance(); System.out.println("Clazz: " + clazz); System.out.println("Object: " + obj); System.out.println("Serializable? " + (obj instanceof Serializable)); for (final Method method : clazz.getDeclaredMethods()) { System.out.println(method); } // set property "bar" clazz.getMethod("setBar", String.class).invoke(obj, "Hello World!"); // get property "bar" String result = (String) clazz.getMethod("getBar").invoke(obj); System.out.println("Value for bar: " + result); } 

执行上面的操作将导致以下控制台输出:

 Clazz: class net.javaforge.blog.javassist.Pojo$Generated Object: net.javaforge.blog.javassist.Pojo$Generated@55571e Serializable? true public void net.javaforge.blog.javassist.Pojo$Generated.setBar(java.lang.String) public java.lang.String net.javaforge.blog.javassist.Pojo$Generated.getBar() public java.lang.Integer net.javaforge.blog.javassist.Pojo$Generated.getFoo() public void net.javaforge.blog.javassist.Pojo$Generated.setFoo(java.lang.Integer) Value for bar: Hello World! 

解决scheme由以下链接的第一个答案提供,这是Stack的问题之一)溢出本身。 在运行时dynamic创build表和Java类