Crear clases dinámicamente con Java

Intenté encontrar información sobre esto, pero he salido con las manos vacías:

Supongo que es posible crear una clase de forma dinámica en Java utilizando reflexión o proxies, pero no puedo averiguar cómo. Estoy implementando un marco de base de datos simple donde creo las consultas SQL usando reflexión. El método obtiene el objeto con los campos de la base de datos como un parámetro y crea la consulta en función de eso. Pero sería muy útil si también pudiera crear el objeto mismo dinámicamente para no tener la necesidad de tener un objeto contenedor de datos simple para cada tabla.

Las clases dinámicas solo necesitarían campos simples ( String , Integer , Double ), por ejemplo

 public class Data { public Integer id; public String name; } 

¿Es esto posible y cómo voy a hacer esto?

EDITAR: Así es como usaría esto:

 /** Creates an SQL query for updating a row's values in the database. * * @param entity Table name. * @param toUpdate Fields and values to update. All of the fields will be * updated, so each field must have a meaningful value! * @param idFields Fields used to identify the row(s). * @param ids Id values for id fields. Values must be in the same order as * the fields. * @return */ @Override public String updateItem(String entity, Object toUpdate, String[] idFields, String[] ids) { StringBuilder sb = new StringBuilder(); sb.append("UPDATE "); sb.append(entity); sb.append("SET "); for (Field f: toUpdate.getClass().getDeclaredFields()) { String fieldName = f.getName(); String value = new String(); sb.append(fieldName); sb.append("="); sb.append(formatValue(f)); sb.append(","); } /* Remove last comma */ sb.deleteCharAt(sb.toString().length()-1); /* Add where clause */ sb.append(createWhereClause(idFields, ids)); return sb.toString(); } /** Formats a value for an sql query. * * This function assumes that the field type is equivalent to the field * in the database. In practice this means that this field support two * types of fields: string (varchar) and numeric. * * A string type field will be escaped with single parenthesis (') because * SQL databases expect that. Numbers are returned as-is. * * If the field is null, a string containing "NULL" is returned instead. * * @param f The field where the value is. * @return Formatted value. */ String formatValue(Field f) { String retval = null; String type = f.getClass().getName(); if (type.equals("String")) { try { String value = (String)f.get(f); if (value != null) { retval = "'" + value + "'"; } else { retval = "NULL"; } } catch (Exception e) { System.err.println("No such field: " + e.getMessage()); } } else if (type.equals("Integer")) { try { Integer value = (Integer)f.get(f); if (value != null) { retval = String.valueOf(value); } else { retval = "NULL"; } } catch (Exception e) { System.err.println("No such field: " + e.getMessage()); } } else { try { String value = (String) f.get(f); if (value != null) { retval = value; } else { retval = "NULL"; } } catch (Exception e) { System.err.println("No such field: " + e.getMessage()); } } return retval; } 

Es posible generar clases (a través de cglib , asm , javassist , bcel ), pero no debería hacerlo de esa manera. ¿Por qué?

  • el código que usa la biblioteca debe esperar el tipo Object y obtener todos los campos usando la reflexión, no es una buena idea
  • java es un lenguaje estáticamente tipado, y desea introducir tipado dynamic, no es el lugar.

Si simplemente desea los datos en un formato indefinido, puede devolverlos en una matriz, como Object[] , o Map si los quiere nombrados, y obtenerlos a partir de allí – le ahorrará muchos problemas con generación de clases innecesarias con el único propósito de contener algunos datos que se obtendrán por reflexión.

Lo que puede hacer en su lugar es tener clases predefinidas que retendrán los datos y pasarlos como argumentos a los métodos de consulta. Por ejemplo:

  public  T executeQuery(Class expectedResultClass, String someArg, Object.. otherArgs) {..} 

Por lo tanto, puede usar la reflexión en el expectedResultClass pasado para crear un nuevo objeto de ese tipo y rellenarlo con el resultado de la consulta.

Dicho esto, creo que podría usar algo existente, como un marco ORM (Hibernate, EclipseLink), JdbcTemplate de spring, etc.

Hay muchas maneras diferentes de lograr esto (por ejemplo, proxies, ASM), pero el enfoque más simple, uno con el que puede comenzar cuando se crea prototipos:

 import java.io.*; import java.util.*; import java.lang.reflect.*; public class MakeTodayClass { Date today = new Date(); String todayMillis = Long.toString(today.getTime()); String todayClass = "z_" + todayMillis; String todaySource = todayClass + ".java"; public static void main (String args[]){ MakeTodayClass mtc = new MakeTodayClass(); mtc.createIt(); if (mtc.compileIt()) { System.out.println("Running " + mtc.todayClass + ":\n\n"); mtc.runIt(); } else System.out.println(mtc.todaySource + " is bad."); } public void createIt() { try { FileWriter aWriter = new FileWriter(todaySource, true); aWriter.write("public class "+ todayClass + "{"); aWriter.write(" public void doit() {"); aWriter.write(" System.out.println(\""+todayMillis+"\");"); aWriter.write(" }}\n"); aWriter.flush(); aWriter.close(); } catch(Exception e){ e.printStackTrace(); } } public boolean compileIt() { String [] source = { new String(todaySource)}; ByteArrayOutputStream baos= new ByteArrayOutputStream(); new sun.tools.javac.Main(baos,source[0]).compile(source); // if using JDK >= 1.3 then use // public static int com.sun.tools.javac.Main.compile(source); return (baos.toString().indexOf("error")==-1); } public void runIt() { try { Class params[] = {}; Object paramsObj[] = {}; Class thisClass = Class.forName(todayClass); Object iClass = thisClass.newInstance(); Method thisMethod = thisClass.getDeclaredMethod("doit", params); thisMethod.invoke(iClass, paramsObj); } catch (Exception e) { e.printStackTrace(); } } } 

Llevará un par de minutos crear una clase de modelo de datos para cada tabla, que puede asignar fácilmente a la base de datos con un ORM como Hibernate o escribiendo sus propios DAO de JDBC. Es mucho más fácil que profundizar en la reflexión.

Puede crear una utilidad que interrogue la estructura de la base de datos para una tabla, y cree la clase de modelo de datos y DAO para usted. Alternativamente, podría crear el modelo en Java y crear una utilidad para crear el esquema de la base de datos y DAO a partir de eso (usando reflection y Java 5 Annotations para ayudar). No olvide que javaFieldNames es diferente de database_column_names en general.

Yo tampoco probaría esto. Una clase ES datos y códigos, ¿qué tipo de código planea asociar con sus datos dynamics?

Lo que probablemente quieras es una colección, o tal vez Hibernate.

Puedes jugar muchos trucos con la colección para que haga lo que quieras. En lugar de colocar objetos directamente en la colección, puede envolverlos en metaobjetos con datos que garanticen su tipo o que no sean nulos. Puede envolver toda su colección en una clase que aplique seguridad, integridad y relaciones de tipo. Incluso he dado a mis colecciones la capacidad de tomar clases de “Validador” para validar los datos que se asignan.

Las validaciones, la estructura y las entradas iniciales pueden provenir de una base de datos, desde XML o desde código.

Esto es posible, pero (creo) necesita algo como ASM o BCEL .

Alternativamente, podría usar algo con más potencia (como Groovy ).