Динамическое создание классов с помощью Java
Я попытался найти информацию об этом, но пришел с пустыми руками:
Я понимаю, что можно создать класс динамически в Java, используя отражение или прокси, но я не могу узнать, как это сделать. Я реализую простую базу данных, где я создаю SQL-запросы с использованием отражения. Метод получает объект с полями базы данных в качестве параметра и создает запрос на основе этого. Но было бы очень полезно, если бы я мог также создать объект сам по себе динамически, поэтому у меня не было бы необходимости иметь простой объект оболочки данных для каждой таблицы.
Динамическим классам нужны только простые поля (String
, Integer
, Double
), например
public class Data {
public Integer id;
public String name;
}
Возможно ли это и как я это сделаю?
EDIT: Вот как я буду использовать это:
/** Creates an SQL query for updating a row 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;
}
Ответы
Ответ 1
Можно создавать классы (через cglib, asm, javassist, bcel), но вы не должны этого делать. Почему?
- код, который использует библиотеку, должен ожидать тип
Object
и получить все поля с помощью отражения - не очень хорошая идея.
- java - это статически типизированный язык, и вы хотите ввести динамическую типизацию - это не место.
Если вам просто нужны данные в формате undefined, вы можете вернуть их в массив, например Object[]
или Map<String, Object>
, если вы хотите, чтобы они были названы, и получите его оттуда - это спасет вас большая проблема с ненужным генерированием классов с единственной целью - содержать некоторые данные, которые будут получены при отражении.
Вместо этого вы можете иметь предопределенные классы, которые будут хранить данные, и передавать их в качестве аргументов методам запросов. Например:
public <T> T executeQuery(Class<T> expectedResultClass,
String someArg, Object.. otherArgs) {..}
Таким образом, вы можете использовать отражение переданного expectedResultClass
для создания нового объекта этого типа и заполнить его результатом запроса.
Тем не менее, я думаю, вы могли бы использовать что-то существующее, например, инфраструктуру ORM (Hibernate, EclipseLink), spring JdbcTemplate
и т.д.
Ответ 2
Существует много разных способов достижения этого (например, прокси, 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();
}
}
}
Ответ 3
Потребуется несколько минут, чтобы создать класс модели данных для каждой таблицы, который вы можете легко сопоставить с базой данных с ORM, например Hibernate, или путем написания своих собственных DAO JDBC. Это намного легче, чем глубоко погрузиться в размышления.
Вы можете создать утилиту, которая запрашивает структуру базы данных для таблицы, и создает для вас класс модели данных и DAO. В качестве альтернативы вы можете создать модель на Java и создать утилиту для создания схемы базы данных и DAO из нее (используя рефлексию и аннотации Java 5 для оказания помощи). Не забывайте, что javaFieldNames обычно отличаются от имени_базы_база_класса.
Ответ 4
Это возможно, но (я считаю) вам нужно что-то вроде ASM или BCEL.
В качестве альтернативы вы можете использовать что-то с большей мощностью (например, Groovy).
Ответ 5
Я бы тоже этого не пробовал. Класс IS и данные и код, какой код вы планируете связывать с вашими динамическими данными?
Возможно, вам нужна коллекция - или, возможно, Hibernate.
Вы можете сыграть много трюков с коллекцией, чтобы заставить ее делать то, что вы хотите. Вместо того, чтобы размещать объекты непосредственно в коллекции, вы можете обернуть их в метаобъекты данными, которые обеспечивают их тип или не являются нулевыми. Вы можете обернуть всю свою коллекцию в классе, который обеспечивает безопасность, полноту и отношения типов. Я даже дал своим коллекциям возможность использовать классы "Validator" для проверки назначенных данных.
Валидации, структура и исходные записи могут поступать из базы данных, из XML или из кода.
Ответ 6
Я создаю ArrayList класса, например
Type classType = new TypeToken<ArrayList<MyModelClass>>() {
}.getType();
ArrayList<MyModelClass> arrangements = new Gson().fromJson(
jObj.getJSONArray(Keys.MODEL_CLASS_LIST).toString(),
classType);