Création de classes dynamiquement avec Java


J'ai essayé de trouver des informations à ce sujet mais je suis venu les mains vides:

Je suppose qu'il est possible de créer une classe dynamiquement en Java en utilisant la réflexion ou les 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 également créer l'objet lui-même dynamiquement donc je n'aurait pas besoin d'avoir un simple objet de wrapper de données pour chaque table.

Les classes dynamiques n'auraient besoin que de champs simples(String, Integer, Double), par exemple

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

Est-ce possible et comment faire?

EDIT: Voici comment j'utiliserais ceci:

/** 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;
}
Author: Jens, 2010-02-23

5 answers

, 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 utilise la bibliothèque devrait s'attendre à taper Object et obtenir tous les champs en utilisant la réflexion-pas une bonne idée
  • java est un langage typé statiquement, et vous voulez introduire le typage dynamique - ce n'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 nommer, et l'obtenir à partir de là - cela vous évitera beaucoup de problèmes avec la génération de classe inutile dans le seul but de contenir des données qui seront obtenues par réflexion.

Ce que vous pouvez faire à la place, c'est avoir des classes prédéfinies qui contiendront les données et les passeront en tant qu'arguments aux méthodes d'interrogation. Par exemple:

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

Ainsi, vous pouvez utiliser la réflexion sur le expectedResultClass passé 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 pourriez utiliser quelque chose d'existant, comme un framework ORM (Hibernate, EclipseLink), spring's JdbcTemplate, etc.

 19
Author: Bozho, 2010-02-24 06:17:42

Il existe de nombreuses façons d'y parvenir (par exemple, les proxys, ASM), mais l'approche la plus simple, celle avec laquelle vous pouvez commencer lors du 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();
      }
    }
}
 22
Author: Amir Afghani, 2010-02-23 17:53:37

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 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 base de données et DAO à partir de cela (en utilisant la réflexion et les annotations Java 5 pour aider). N'oubliez pas que javaFieldNames est généralement différent de database_column_names.

 1
Author: JeeBee, 2010-02-23 18:54:27

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

Ce que vous voulez probablement, c'est une collection or ou peut-être Hiberner.

Vous pouvez jouer beaucoup de tours avec la collection pour l'amener à faire ce que vous voulez. Au lieu de placer des objets directement dans la collection, vous pouvez les envelopper dans des méta-objets avec des données qui garantissent leur type ou qu'ils ne sont pas nuls. Vous pouvez envelopper toute votre collection dans un classe qui applique la sécurité du type, l'exhaustivité et les relations. J'ai même donné à mes collections la possibilité de prendre des classes "Validateur" 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.

 0
Author: Bill K, 2010-02-23 18:25:33

C'est possible, mais (je crois) vous avez besoin de quelque chose comme ASMou BCEL.

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

 0
Author: Drew Wills, 2016-05-29 18:52:03