2010-02-23 20 views
21

ho cercato di trovare informazioni su questo, ma sono venuto a mani vuote:Creazione di classi in modo dinamico con Java

ho capito, è possibile creare una classe in modo dinamico in Java utilizzando la riflessione o proxy, ma non riesco a trovare fuori Come. Sto implementando un semplice framework di database in cui creo le query SQL utilizzando la reflection. Il metodo ottiene l'oggetto con i campi del database come parametro e crea la query in base a tale. Ma sarebbe molto utile se potessi anche creare l'oggetto stesso dinamicamente in modo da non avere la necessità di avere un semplice oggetto wrapper dati per ogni tabella.

Le classi dinamiche necessiterebbero solo di campi semplici (String, Integer, Double), ad es.

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

E 'possibile e come farei questo?

EDIT: Questo è come vorrei utilizzare questo:

/** 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

Non credo che Java è lo strumento giusto per questo, Groovy sarebbe più adatto IMO. –

+0

Personalmente non vedo il problema di avere un modello Java che si associa al modello di database. Inoltre, quando si creano query SQL, assicurarsi di utilizzare PreparedStatements per evitare SQL Injection, anziché creare stringhe SQL. – JeeBee

risposta

16

E 'possibile generare classi (via cglib, asm, javassist, bcel), ma non si deve farlo in quel modo. Perché?

  • il codice che utilizza la libreria deve aspettarsi tipo Object e ottenere tutti i campi utilizzando la riflessione - non è una buona idea
  • Java è staticamente tipizzato lingua, e si vuole introdurre la tipizzazione dinamica - non è il posto giusto.

Se si desidera semplicemente i dati in un formato definito, allora si può tornare in un array, come Object[] o Map<String, Object> se si desidera loro nome, e farlo da lì - ti farà risparmiare molti problemi con generazione di classi non necessarie al solo fine di contenere alcuni dati che saranno ottenuti dalla riflessione.

Quello che puoi fare è avere classi predefinite che manterranno i dati e li passeranno come argomenti ai metodi di interrogazione. Per esempio:

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

Così si può utilizzare la riflessione sul passato expectedResultClass per creare un nuovo oggetto di quel tipo e popolarlo con il risultato della query.

Detto questo, penso che si potrebbe usare qualcosa di già esistente, come un quadro ORM (Hibernate, EclipseLink), di primavera JdbcTemplate, ecc

+1

La mia idea era di evitare un numero elevato di classi con solo campi semplici. Sarebbe molto più semplice creare le classi al volo. Molto meno codice e quasi chiaro come avere tutte quelle classi. – Makis

+2

@Makis ma _how_ userete le classi generate? Non puoi trasmettere loro, non puoi sapere quali sono i loro campi, quando scrivi il codice. – Bozho

+0

Come ho scritto, userò la riflessione per determinare i campi di quella classe - sono tutte le informazioni di cui ho bisogno. Guarderò il tipo, il nome e il valore di ogni campo. Aggiungerò l'esempio alla mia domanda sopra. – Makis

17

Ci sono molti modi diversi per raggiungere questo obiettivo (ad esempio proxy, ASM), ma l'approccio più semplice, quella che si può iniziare con quando la prototipazione è :

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

Suggerisco comunque questo approccio perché sembra che le classi che stai generando siano banali. –

+1

Non riesco a trovare sun.tools.javac.Main né com.sun.tools.javac. Dove potrei trovarli? – Makis

+0

Prova: import com.sun.tools.javac.Main e dimmi se funziona –

0

Questo è possibile, ma (credo) è necessario qualcosa come ASM o BCEL.

In alternativa, è possibile utilizzare qualcosa con maggiore potenza (come Groovy).

0

non vorrei provare questo sia. Una classe IS sia dati che codice, che tipo di codice stai pianificando di associare ai tuoi dati dinamici?

Quello che probabilmente vuoi è una collezione - o forse Hibernate.

Puoi giocare un sacco di trucchi con la raccolta per farlo fare ciò che vuoi. Invece di posizionare gli oggetti direttamente nella raccolta, puoi racchiuderli in meta-oggetti con dati che assicurano il loro tipo o che non sono nulli. Puoi avvolgere tutta la tua collezione in una classe che impone sicurezza, completezza e relazioni al tipo. Ho persino dato alle mie raccolte la possibilità di prendere lezioni di "Validator" per convalidare i dati assegnati.

Le convalide, la struttura e le voci iniziali possono provenire da un database, da XML o da codice.

1

Ci vorranno un paio di minuti per creare una classe del modello di dati per ogni tabella, che è possibile mappare facilmente al database con un ORM come Hibernate o scrivendo i propri DAO JDBC. È molto più facile che scavare profondamente nella riflessione.

È possibile creare un'utilità che interroga la struttura del database per una tabella e crea per sé la classe del modello di dati e DAO. In alternativa, è possibile creare il modello in Java e creare un'utilità per creare lo schema del database e DAO da quello (utilizzando la reflection e le annotazioni Java 5 come ausilio). Non dimenticare che javaFieldNames è diverso da database_column_names in genere.

0

sto creando ArrayList di classe come

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