2010-02-23 18 views
21

He tratado de encontrar información sobre esto, pero he venido con las manos vacías:clases creación dinámica con Java

tengo entendido que es posible crear una clase dinámica en Java utilizando la 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), p.

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

¿Es esto posible y cómo podría hacerlo?

EDIT: Esta es la forma en que yo 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; 
} 
+0

No creo que Java es la herramienta adecuada para que, maravilloso sería más adecuado de la OMI. –

+0

Personalmente, no veo el problema de tener un modelo de Java que se corresponda con su modelo de base de datos. Además, al crear consultas SQL, asegúrese de utilizar PreparedStatements para evitar la inyección SQL, en lugar de crear cadenas SQL. – JeeBee

Respuesta

16

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

  • el código que está utilizando la biblioteca debe esperar tipo Object y obtener todos los campos utilizando la reflexión - no es una buena idea
  • Java se escribe de forma estática idioma, y ​​se desea introducir tipado dinámico - no es el lugar.

Si simplemente desea que los datos en un formato definido, entonces usted puede devolverlo en una matriz, como Object[] o Map<String, Object> si desea que los nombraron, y obtener de allí - que 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> T executeQuery(Class<T> expectedResultClass, 
     String someArg, Object.. otherArgs) {..} 

De este modo se puede utilizar la reflexión sobre la expectedResultClass pasado para crear un nuevo objeto de ese tipo y rellenarla con el resultado de la consulta.

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

+1

Mi idea era evitar tener una gran cantidad de clases que solo tengan campos simples. Sería mucho más simple crear las clases sobre la marcha. Mucho menos código y casi tan claro como tener todas esas clases. – Makis

+2

@Makis pero _how_ va a utilizar las clases generadas? No puede transmitirles, no puede saber cuáles son sus campos, al escribir el código. – Bozho

+0

Mientras escribía, utilizaré la reflexión para determinar los campos de esa clase; esa es toda la información que necesito. Veré el tipo, nombre y valor de cada campo. Agregaré el ejemplo a mi pregunta anterior. – Makis

17

Hay muchas maneras diferentes para lograr esto (por ejemplo, servidores proxy, ASM), pero el método más sencillo, que se puede empezar con cuando prototipos es :

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

Sugiero este enfoque por cierto porque parece que las clases que está generando son triviales. –

+1

No puedo encontrar sun.tools.javac.Main ni com.sun.tools.javac. ¿Dónde podría encontrar esto? – Makis

+0

Pruebe: import com.sun.tools.javac.Main y dígame si eso funciona –

0

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

De forma alternativa, puede usar algo con más potencia (como Groovy).

0

no intentaría esto tampoco. Una clase ES datos y códigos, ¿qué tipo de código planea asociar con sus datos dinámicos?

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

Puede jugar un montón de trucos con la colección para que haga lo que quiera. 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.

1

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 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 de 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.

0

estoy creando ArrayList de clase como

Type classType = new TypeToken<ArrayList<MyModelClass>>() { 
               }.getType(); 
             ArrayList<MyModelClass> arrangements = new Gson().fromJson(
               jObj.getJSONArray(Keys.MODEL_CLASS_LIST).toString(), 
               classType);