Создание классов динамически с Java - PullRequest
27 голосов
/ 23 февраля 2010

Я пытался найти информацию об этом, но пришел с пустыми руками:

Я понимаю, что возможно динамическое создание класса в Java с использованием рефлексии или прокси, но я не могу понять, как это сделать. Я реализую простую структуру базы данных, где я создаю запросы SQL, используя отражение. Метод получает объект с полями базы данных в качестве параметра и на основании этого создает запрос. Но было бы очень полезно, если бы я мог также динамически создавать сам объект, чтобы у меня не было необходимости иметь простой объект-обертку для каждой таблицы.

Динамические классы будут требовать только простые поля (String, Integer, Double), например,

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

Возможно ли это и как мне это сделать?

РЕДАКТИРОВАТЬ: Вот как бы я использовал это:

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

Ответы [ 5 ]

26 голосов
/ 23 февраля 2010

Есть много разных способов достижения этого (например, прокси, ASM), но самый простой подход, с которым вы можете начать, когда прототипирование:

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();
      }
    }
}
19 голосов
/ 23 февраля 2010

Возможно генерировать классы (через cglib , asm , javassist , bcel ), но вы не должны этого делать это так. Почему?

  • код, который использует библиотеку, должен ожидать типа Object и получать все поля, используя отражение - не очень хорошая идея
  • Java - это язык статической типизации, и вы хотите ввести динамическую типизацию - это не то место.

Если вы просто хотите получить данные в неопределенном формате, то вы можете вернуть их в массив, например Object[] или Map<String, Object>, если хотите, чтобы они были названы, и получить их оттуда - это избавит вас от многих проблем с ненужной генерацией классов с единственной целью содержать некоторые данные, которые будут получены путем отражения.

Вместо этого вы можете иметь предопределенные классы, которые будут хранить данные и передавать их в качестве аргументов в методы запросов. Например:

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

Таким образом, вы можете использовать отражение переданного expectedResultClass, чтобы создать новый объект этого типа и заполнить его результатом запроса.

Тем не менее, я думаю, вы могли бы использовать что-то существующее, например, среду ORM (Hibernate, EclipseLink), Spring JdbcTemplate и т. Д.

3 голосов
/ 23 февраля 2010

Для создания класса модели данных для каждой таблицы потребуется несколько минут, которые вы можете легко сопоставить с базой данных с помощью ORM, например Hibernate, или написав свои собственные JDBC DAO. Это гораздо проще, чем углубляться в размышления.

Вы можете создать утилиту, которая опрашивает структуру базы данных для таблицы и создает для вас класс модели данных и DAO. В качестве альтернативы вы можете создать модель в Java и создать утилиту для создания схемы базы данных и DAO из нее (используя помощь и аннотации Java 5). Не забывайте, что javaFieldNames обычно отличаются от database_column_names.

0 голосов
/ 23 февраля 2010

Я бы тоже не попробовал. Класс - это и данные, и код. Какой код вы планируете связать с вашими динамическими данными?

То, что вы, вероятно, хотите, это коллекция - или, может быть, Hibernate.

Вы можете разыграть множество трюков с коллекцией, чтобы заставить ее делать то, что вы хотите. Вместо того, чтобы помещать объекты непосредственно в коллекцию, вы можете обернуть их в мета-объекты данными, которые гарантируют их тип или что они не равны NULL. Вы можете поместить всю коллекцию в класс, обеспечивающий безопасность типов, полноту и взаимосвязи. Я даже дал своим коллекциям возможность использовать классы «Validator» для проверки назначаемых данных.

Проверки, структура и начальные записи могут поступать из базы данных, из XML или из кода.

0 голосов
/ 23 февраля 2010

Это возможно, но (я считаю) вам нужно что-то вроде ASM или BCEL .

Кроме того, вы можете использовать что-то с большей мощностью (например, Groovy ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...