Crear clases POJO simples (bytecode) en tiempo de ejecución (dinámicamente)

Tengo el siguiente escenario …

Estoy escribiendo una herramienta que ejecuta la consulta ingresada por el usuario contra la base de datos y devuelve el resultado.

La forma más simple es devolver el resultado como: List pero necesito llevar esto un paso más allá.

Necesito crear (en tiempo de ejecución ) algunos POJO (o DTO) con algún nombre y crear campos y setters y getters para él y poblarlo con los datos devueltos y luego devolverlo al usuario con el archivo .class generado …

Entonces la idea aquí es Cómo crear una clase simple (bytecode) en tiempo de ejecución (dinámicamente) Hago una búsqueda básica y encontré muchas librerías incluyendo Apache BCEL Pero creo que necesito algo más simple …

¿Qué piensa usted de eso?

Gracias.

Crear un POJO simple con getters y setters es fácil si usa CGLib :

 public static Class< ?> createBeanClass( /* fully qualified class name */ final String className, /* bean properties, name -> type */ final Map> 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(); } 

Código de prueba:

 public static void main(final String[] args) throws Exception{ final Map> properties = new HashMap>(); 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); } } 

Salida:

clase 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)

Pero el problema es que no tiene forma de codificar contra estos métodos, ya que no existen en el momento de la comstackción, así que no sé qué bien le hará esto.

He usado ASM para esto en el pasado. Lo que me gusta es el ASMifier que puede crear código para generar una clase. Por ejemplo, creo un POJO genérico en código java con un campo de cada tipo en Java y uso ASMifier para crear el código Java para crearlo a partir de código de bytes y lo usé como una plantilla para generar un POJO arbitrario.

Como sugiere @Michael, es posible que desee agregar una forma no reflexiva para obtener campos arbitrarios. p.ej

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

¿Por qué quieres hacer esto? Hay formas en que puede hacer que los Map estilo Map sean más eficientes que utilizando un mapa normal.

Otro enfoque es generar la fuente Java usando Velocity y comstackr el código usando la API del comstackdor. Es un dolor de usar, así que escribí un envolvente aquí Essence JCF La única ventaja de lectura para usar este enfoque es que puedes depurar fácilmente tu código generado. (La biblioteca tiene la opción de guardar el código java en algún lugar donde el depurador pueda encontrarlo, de modo que cuando ingrese al código generado)

¿Qué haría la persona que llama con una clase que se genera sobre la marcha y que, por lo tanto, su código no puede saber? La única forma de acceder a ella sería a través de la reflexión. Devolver una List o Map sería en realidad un diseño mucho más limpio y más útil.

También odio escribir getters y setters. Preferiría usar POJOs, incluso POJOs declarados como clases anidadas.

Hay otra forma de hacerlo, incluso con servidores y tecnología antiguos y sin la introducción de Springs (usamos JBoss 4.2 y EJB 3.0 incompleto de JBoss). Extendiendo org.apache.commons.beanutils.BeanMap, puede envolver el POJO en un mapa de beans, y cuando obtiene o coloca puede manipular los campos usando la reflexión. Si el getter o setter no existe, solo usamos la manipulación de campo para obtenerlo. Obviamente NO es un verdadero bean, así que está perfectamente bien.

 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 keySet = new HashSet(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); } } 

La parte difícil es factorizar los POJO para regresar, pero la reflexión puede ayudarlo a usted:

 /** * 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); } } 

Código de ejemplo para obtener un frijol genérico de un frijol, un clonable o un POJO:

 public List getGenericEJBData(String tableName, Object desiredFields, Object beanCriteria){ NoGetterBeanMap desiredFieldMap = new NoGetterBeanMap(desiredFields); NoGetterBeanMap criteriaMap = new NoGetterBeanMap(beanCriteria); List data = new ArrayList(); List> mapData = getGenericEJBData(tableName, desiredFieldMap, criteriaMap); for (Map 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; } 

Ejemplo de uso con 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 data = genericRemote.getGenericEJBData(ACES_VEHICLE_TABLE, new DesiredDataPOJO(), new CriteriaPOJO() ); for (DesiredDataPOJO o: data) { makes.add(o.makename); } 

El EJB tiene una interfaz como esta:

 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  List getGenericEJBData(String tableName, DesiredFields desiredFields, Object beanCriteria); } 

Puedes imaginar cómo es la implementación de ejb, en este momento estamos creando declaraciones preparadas y llamándolas, pero podríamos usar criterios, o algo más genial como hibernate o lo que sea si quisiéramos.

Aquí hay un ejemplo aproximado (a algunos NO les va a gustar esta parte). En el ejemplo, tenemos una sola tabla con datos en 3ra forma normal. Para que esto funcione, los campos de beans deben coincidir con los nombres de las columnas de la tabla. Las llamadas a toLowerCase () están en caso de que se trate de un bean REAL, lo que arruinará la coincidencia de nombres (MyField vs. getMyfield). Esta parte probablemente podría ser pulida un poco mejor. En particular, el orden por y distinto debe ser banderas o algo así. Probablemente haya otras condiciones de borde que también pueden suceder. Por supuesto, solo tengo que escribir esto UNA VEZ, y para el rendimiento, nada le impide tener un receptor de datos mucho más preciso para el rendimiento.

 @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> getGenericEJBData(String tableName,Map desiredFields, Map criteria){ try { List> dataFieldKeyValuePairs = new ArrayList>(); 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 data = new HashMap(); int columnIndex = rs.getMetaData().getColumnCount(); for(int x=0;x 

Bueno, esto también puede dar una oportunidad . Pero necesito entender esto si alguien puede explicarlo.

ACTUALIZAR:

Imagine que su aplicación tiene que crear instancias Java POJO dinámicamente en tiempo de ejecución desde alguna configuración externa. Esta tarea se puede realizar fácilmente utilizando una de las bibliotecas de manipulación de bytecode. Este post demuestra cómo se puede hacer esto usando la biblioteca de Javassist.

Supongamos que tenemos la siguiente configuración para las propiedades que nuestro POJO creado dinámicamente debería contener:

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

Escribamos un PojoGenerator, que genera dinámicamente un objeto de clase para el nombre de clase y un mapa dados, que contiene las propiedades requeridas:

 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> 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> 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()); } } 

¡Eso es!

Usar PojoGenerator es bastante simple. Generemos algunos POJO, genere a través de la reflexión todos sus métodos, establezca y luego obtenga alguna propiedad:

 public static void main(String[] args) throws Exception { Map> props = new HashMap>(); 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); } 

La ejecución anterior dará como resultado la siguiente salida de la consola:

 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! 

La solución es proporcionada por la primera respuesta del siguiente enlace, que es 1 de preguntas de Pila) Desbordamiento en sí. Cree dinámicamente clases de tabla y java en tiempo de ejecución