2010-02-23 8 views
21

J'ai essayé de trouver des informations à ce sujet mais suis venu les mains vides:des classes Création dynamique avec Java

Je crois qu'il est possible de créer une classe dynamique en Java en utilisant la réflexion ou proxies, mais je ne peux pas savoir Comment. J'implémente un cadre de base de données simple où je crée les requêtes SQL en utilisant la réflexion. La méthode obtient l'objet avec les champs de base de données en tant que paramètre et crée la requête en fonction de cela. Mais ce serait très utile si je pouvais aussi créer l'objet lui-même de façon dynamique, donc je n'aurais pas besoin d'avoir un simple objet wrapper de données pour chaque table.

Les classes dynamiques ne nécessiteraient que des champs simples (String, Integer, Double), par ex.

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

Est-ce possible et comment est-ce que je ferais ceci?

EDIT: Voici comment j'utiliser:

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

Je ne pense pas que Java est l'outil idéal pour cela, Groovy serait mieux adapté l'OMI. –

+0

Personnellement, je ne vois pas le problème d'avoir un modèle Java qui correspond à votre modèle de base de données. De même, lors de la création de requêtes SQL, veillez à utiliser PreparedStatements pour éviter l'injection SQL plutôt que de créer des chaînes SQL. – JeeBee

Répondre

16

Il est possible de générer des classes (via cglib, asm, javassist, bcel), mais vous ne devriez pas le faire de cette façon. Pourquoi?

  • le code qui est à l'aide de la bibliothèque devrait attendre le type Object et obtenir tous les champs en utilisant la réflexion - pas une bonne idée
  • java est statiquement langage typé, et que vous voulez introduire dynamique frappe - c'est pas l'endroit.

Si vous voulez simplement les données dans un format non défini, vous pouvez le retourner dans un tableau, comme Object[], ou Map<String, Object> si vous voulez les nom et l'obtenir à partir de là - il vous fera économiser beaucoup de peine avec génération de classe inutile dans le seul but de contenir certaines données qui seront obtenues par réflexion. Ce que vous pouvez faire à la place est d'avoir des classes prédéfinies qui contiendront les données, et les passeront comme arguments aux méthodes d'interrogation. Par exemple:

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

vous pouvez donc utiliser la réflexion sur le passé expectedResultClass pour créer un nouvel objet de ce type et le remplir avec le résultat de la requête.

Cela dit, je pense que vous pouvez utiliser quelque chose qui existe, comme un cadre ORM (Hibernate, EclipseLink), JdbcTemplate de printemps, etc.

+1

Mon idée était d'éviter d'avoir un grand nombre de classes qui ne contiennent que des champs simples. Il serait beaucoup plus simple de créer les classes à la volée. Beaucoup moins de code et à peu près aussi clair que d'avoir toutes ces classes. – Makis

+2

@Makis mais _how_ utiliserez-vous les classes générées? Vous ne pouvez pas leur lancer, vous ne pouvez pas savoir ce que sont leurs champs, en écrivant le code. – Bozho

+0

Comme je l'ai écrit, je vais utiliser la réflexion pour déterminer les champs de cette classe - c'est toute l'information dont j'ai besoin. Je vais regarder le type, le nom et la valeur de chaque champ. Je vais ajouter l'exemple à ma question ci-dessus. – Makis

17

Il existe de nombreuses façons d'y parvenir (par exemple, les procurations, ASM), mais l'approche la plus simple, celui que vous pouvez commencer lorsque le prototypage est :

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

Je suggère cette approche d'ailleurs, car il semble que les classes que vous générez sont triviales. –

+1

Je ne trouve pas sun.tools.javac.Main ni com.sun.tools.javac. Où pourrais-je les trouver? – Makis

+0

Essayez: importer com.sun.tools.javac.Main et dites-moi si cela fonctionne –

0

Cela est possible, mais (je crois) vous avez besoin quelque chose comme ASM ou BCEL.

Alternativement, vous pourriez utiliser quelque chose avec plus de puissance (comme Groovy).

0

Je ne voudrais pas essayer non plus. Une classe EST à la fois données et code, quel type de code envisagez-vous d'associer à vos données dynamiques?

Ce que vous voulez probablement est une collection - ou peut-être Hibernate.

Vous pouvez jouer beaucoup de tours avec la collection pour l'obtenir pour faire ce que vous voulez. Au lieu de placer des objets directement dans la collection, vous pouvez les placer dans des méta-objets avec des données qui garantissent leur type ou qui ne sont pas nuls. Vous pouvez envelopper toute votre collection dans une classe qui renforce la sécurité du type, l'exhaustivité et les relations. J'ai même donné à mes collections la possibilité de prendre des classes "Validator" pour valider les données assignées.

Les validations, la structure et les entrées initiales peuvent provenir d'une base de données, de XML ou de code.

1

Il faudra quelques minutes pour créer une classe de modèle de données pour chaque table, que vous pouvez facilement mapper à la base de données avec un ORM comme Hibernate ou en écrivant vos propres DAO JDBC. C'est beaucoup plus facile que de plonger profondément dans la réflexion.

Vous pouvez créer un utilitaire qui interroge la structure de base de données pour une table et crée la classe de modèle de données et le DAO pour vous. Vous pouvez également créer le modèle en Java et créer un utilitaire pour créer le schéma de la base de données et DAO à partir de celui-ci (en utilisant la réflexion et Java 5 Annotations pour aider). N'oubliez pas que javaFieldNames est différent de database_column_names en général.

0

Je crée ArrayList de classe comme

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