【问题标题】:Create simple POJO classes (bytecode) at runtime (dynamically)在运行时(动态)创建简单的 POJO 类(字节码)
【发布时间】:2011-03-03 08:42:58
【问题描述】:

我有以下场景..

我正在编写一些工具,对数据库运行用户输入的查询并返回结果..

最简单的方法是将结果返回为:List<String[]>,但我需要更进一步。

我需要创建(在运行时)一些具有某些名称的 POJO(或 DTO),并为其创建字段、setter 和 getter,并使用返回的数据填充它,然后将其返回给用户其中.class文件生成...

所以这里的想法是如何在运行时(动态)创建简单的类(字节码) 我做了一个基本的搜索,发现了很多 lib including Apache BCEL 但我想我需要更简单的东西......

你怎么看?

谢谢。

【问题讨论】:

  • BCEL 非常简单。我使用过 BCEL(7-8 年前)和 ASM(最近)。我猜,由于 Eclipse 集成,ASM 更简单。
  • 下面的所有答案都很棒......但我曾经使用 ASM.. 我已经从他们的网站下载了 asm-guide-examples.jar 并在我的案例附近找到了一个示例用过。

标签: java bytecode bcel


【解决方案1】:

如果您使用CGLib,那么使用 getter 和 setter 创建一个简单的 POJO 很容易:

public static Class<?> createBeanClass(
    /* fully qualified class name */
    final String className,
    /* bean properties, name -> type */
    final Map<String, Class<?>> properties){

    final BeanGenerator beanGenerator = new BeanGenerator();

    /* use our own hard coded class name instead of a real naming policy */
    beanGenerator.setNamingPolicy(new NamingPolicy(){
        @Override public String getClassName(final String prefix,
            final String source, final Object key, final Predicate names){
            return className;
        }});
    BeanGenerator.addProperties(beanGenerator, properties);
    return (Class<?>) beanGenerator.createClass();
}

测试代码:

public static void main(final String[] args) throws Exception{
    final Map<String, Class<?>> properties =
        new HashMap<String, Class<?>>();
    properties.put("foo", Integer.class);
    properties.put("bar", String.class);
    properties.put("baz", int[].class);

    final Class<?> beanClass =
        createBeanClass("some.ClassName", properties);
    System.out.println(beanClass);
    for(final Method method : beanClass.getDeclaredMethods()){
        System.out.println(method);
    }

}

输出:

类 some.ClassName
public int[] some.ClassName.getBaz()
public void some.ClassName.setBaz(int[])
public java.lang.Integer some.ClassName.getFoo()
public void some.ClassName.setFoo(java.lang.Integer)
public java.lang.String some.ClassName.getBar()
public void some.ClassName.setBar(java.lang.String)

但问题是:您无法针对这些方法进行编码,因为它们在编译时不存在,所以我不知道这对您有什么好处。

【讨论】:

  • 最后一句要高亮!
  • 这个库以最小的努力完成了动态创建 pojo 的工作。我们的案例是根据业务逻辑生成一个pojo,并将数据写入csv文件。
【解决方案2】:

好吧This may also given a try。但是如果有人可以解释,我需要了解这一点。

更新:

假设您的应用程序必须在运行时从一些外部配置动态创建 Java POJO 实例。使用字节码操作库之一可以轻松完成此任务。这篇文章演示了如何使用 Javassist 库来完成。

假设我们对动态创建的 POJO 应包含的属性进行以下配置:

Map<String, Class<?>> props = new HashMap<String, Class<?>>();
props.put("foo", Integer.class);
props.put("bar", String.class);

让我们编写一个 PojoGenerator,它为给定的类名和一个映射动态生成一个 Class 对象,其中包含所需的属性:

import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

public class PojoGenerator {

public static Class generate(String className, Map<String, Class<?>>  properties) throws NotFoundException,
        CannotCompileException {

    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.makeClass(className);

    // add this to define a super class to extend
    // cc.setSuperclass(resolveCtClass(MySuperClass.class));

    // add this to define an interface to implement
    cc.addInterface(resolveCtClass(Serializable.class));

    for (Entry<String, Class<?>> entry : properties.entrySet()) {

        cc.addField(new CtField(resolveCtClass(entry.getValue()), entry.getKey(), cc));

        // add getter
        cc.addMethod(generateGetter(cc, entry.getKey(), entry.getValue()));

        // add setter
        cc.addMethod(generateSetter(cc, entry.getKey(), entry.getValue()));
    }

    return cc.toClass();
}

private static CtMethod generateGetter(CtClass declaringClass, String fieldName, Class fieldClass)
        throws CannotCompileException {

    String getterName = "get" + fieldName.substring(0, 1).toUpperCase()
            + fieldName.substring(1);

    StringBuffer sb = new StringBuffer();
    sb.append("public ").append(fieldClass.getName()).append(" ")
            .append(getterName).append("(){").append("return this.")
            .append(fieldName).append(";").append("}");
    return CtMethod.make(sb.toString(), declaringClass);
}

private static CtMethod generateSetter(CtClass declaringClass, String fieldName, Class fieldClass)
        throws CannotCompileException {

    String setterName = "set" + fieldName.substring(0, 1).toUpperCase()
            + fieldName.substring(1);

    StringBuffer sb = new StringBuffer();
    sb.append("public void ").append(setterName).append("(")
            .append(fieldClass.getName()).append(" ").append(fieldName)
            .append(")").append("{").append("this.").append(fieldName)
            .append("=").append(fieldName).append(";").append("}");
    return CtMethod.make(sb.toString(), declaringClass);
}

private static CtClass resolveCtClass(Class clazz) throws NotFoundException {
    ClassPool pool = ClassPool.getDefault();
    return pool.get(clazz.getName());
}
}

就是这样!

使用 PojoGenerator 非常简单。让我们生成一些 POJO,通过反射输出它的所有方法,设置然后获取一些属性:

public static void main(String[] args) throws Exception {

Map<String, Class<?>> props = new HashMap<String, Class<?>>();
props.put("foo", Integer.class);
props.put("bar", String.class);

Class<?> clazz = PojoGenerator.generate(
        "net.javaforge.blog.javassist.Pojo$Generated", props);

Object obj = clazz.newInstance();

System.out.println("Clazz: " + clazz);
System.out.println("Object: " + obj);
System.out.println("Serializable? " + (obj instanceof Serializable));

for (final Method method : clazz.getDeclaredMethods()) {
    System.out.println(method);
}

// set property "bar"
clazz.getMethod("setBar", String.class).invoke(obj, "Hello World!");

// get property "bar"
String result = (String) clazz.getMethod("getBar").invoke(obj);
System.out.println("Value for bar: " + result);

}

执行上面会产生如下控制台输出:

Clazz: class net.javaforge.blog.javassist.Pojo$Generated
Object: net.javaforge.blog.javassist.Pojo$Generated@55571e
Serializable? true
public void     net.javaforge.blog.javassist.Pojo$Generated.setBar(java.lang.String)
public java.lang.String     net.javaforge.blog.javassist.Pojo$Generated.getBar()
public java.lang.Integer     net.javaforge.blog.javassist.Pojo$Generated.getFoo()
public void     net.javaforge.blog.javassist.Pojo$Generated.setFoo(java.lang.Integer)
Value for bar: Hello World!

【讨论】:

  • @v.ladynev 请看一下先生。我尝试了这段代码,它运行成功,但我不知道字节码去了哪里。我的意思是那里没有物理 pojo 或 .class 文件。
  • 尝试使用 jackson writeValueAsString 将生成的类序列化为 json 字符串并返回 net.javaforge.blog.javassist.Pojo$Generated - 知道出了什么问题吗?
  • 这个答案是从这里逐字逐句复制的:blog.javaforge.net/post/31913732423/…
  • @PureSpider 是的,这就是为什么通过答案开头的链接提到它的原因。以及我逐字复制它的原因是因为该链接可能会过期。所以是的答案是逐字复制的。
【解决方案3】:

我过去曾为此使用过 ASM。我喜欢的是 ASMifier,它可以创建代码来生成一个类。例如我在 Java 代码中创建了一个通用 POJO,其中包含 Java 中每种类型的一个字段,并使用 ASMifier 创建 Java 代码以从字节码创建它,并将其用作模板来生成任意 POJO。

正如@Michael 建议的那样,您可能希望添加一种非反射方式来获取任意字段。例如

public Set<String> fieldNames();
public Object getField(String name);
public void setField(String name, Object name);

为什么要这样做?有一些方法可以使使用Map&lt;String, Object&gt; 样式的对象比使用常规地图更有效。

另一种方法是使用 Velocity 生成 Java 源代码并使用 Compiler API 编译代码。使用起来很痛苦,所以我在这里为它写了一个包装 Essence JCF 使用这种方法的唯一优势是您可以轻松调试生成的代码。 (该库可以选择将 java 代码保存到调试器可以找到的位置,以便在您进入生成的代码时)

【讨论】:

  • 哦不.. 生成源代码然后编译它对我来说不是一个选项.. 绝对!我不想做任何调试它是一个简单的 POJO
  • 你能给我一个 ASM 示例的链接吗?
  • 你真的需要getter和setter吗?如果不使用地图可能会更简单。
【解决方案4】:

调用者 会对动态生成的类执行什么操作,因此他的代码无法知道该类?访问它的唯一方法是通过反射。返回List&lt;String[]&gt; 或Map&lt;String, String&gt; 实际上是一种更简洁、更实用的设计。

【讨论】:

  • 为什么要自己反思?他用来探索类的 setter 和 getter [使用他自己的 IDE],就好像它是一个带有 JAR 文件的第三方库只包含 .class 文件.. 正确吗?
  • 例如,您可以在一些类似 xml 的绑定中使用它作为数据存储。与 Map 相比,它确实减少了占用空间
  • 有些库使用反射作为他们预期的发现策略。但是,如果您不使用其中之一,Map&lt;String, Object&gt; 会更干净。
【解决方案5】:

我也讨厌编写 getter 和 setter。我更喜欢使用 POJO,甚至是声明为嵌套类的 POJO。

还有另一种方法可以做到这一点,即使使用旧的服务器和技术并且不引入 Springs(我们使用 JBoss 4.2 和 JBoss 不完整的 EJB 3.0)。扩展 org.apache.commons.beanutils.BeanMap,您可以将 POJO 包装在一个 bean map 中,当您获取或放置时,您可以使用反射操作字段。如果 getter 或 setter 不存在,我们只需使用字段操作来获取它。显然它不是真正的豆子,所以这完全没问题。

package com.aaa.ejb.common;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.collections.set.UnmodifiableSet;
import org.apache.log4j.Logger;

/**
 * I want the bean map to be able to handle a POJO. 
 * @author gbishop
 */
public final class NoGetterBeanMap extends BeanMap {

    private static final Logger LOG = Logger.getLogger(NoGetterBeanMap.class);

    /**
     * Gets a bean map that can handle writing to a pojo with no getters or setters.
     * @param bean
     */
    public NoGetterBeanMap(Object bean) {
        super(bean);
    }

    /* (non-Javadoc)
     * @see org.apache.commons.beanutils.BeanMap#get(java.lang.Object)
     */
    public Object get(Object name) {
        Object bean = getBean();
        if ( bean != null ) {
            Method method = getReadMethod( name );
            if ( method != null ) {
                try {
                    return method.invoke( bean, NULL_ARGUMENTS );
                }
                catch (  IllegalAccessException e ) {
                    logWarn( e );
                }
                catch ( IllegalArgumentException e ) {
                    logWarn(  e );
                }
                catch ( InvocationTargetException e ) {
                    logWarn(  e );
                }
                catch ( NullPointerException e ) {
                    logWarn(  e );
                }
            } else {
                if(name instanceof String) {
                    Class<?> c = bean.getClass();
                    try {
                        Field datafield = c.getDeclaredField( (String)name );
                        datafield.setAccessible(true);
                        return datafield.get(bean);
                    } catch (SecurityException e) {
                        throw new IllegalArgumentException( e.getMessage() );
                    } catch (NoSuchFieldException e) {
                        throw new IllegalArgumentException( e.getMessage() );
                    } catch (IllegalAccessException e) {
                        throw new IllegalArgumentException( e.getMessage() );
                    }
                }
            }
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.apache.commons.beanutils.BeanMap#put(java.lang.Object, java.lang.Object)
     */
    public Object put(Object name, Object value) throws IllegalArgumentException, ClassCastException {
        Object bean = getBean();
        if ( bean != null ) {
            Object oldValue = get( name );
            Method method = getWriteMethod( name );
            Object newValue = null;
            if ( method == null ) {
                if(name instanceof String) {//I'm going to try setting the property directly on the bean.
                    Class<?> c = bean.getClass();
                    try {
                        Field datafield = c.getDeclaredField( (String)name );
                        datafield.setAccessible(true);
                        datafield.set(bean, value);
                        newValue = datafield.get(bean);
                    } catch (SecurityException e) {
                        throw new IllegalArgumentException( e.getMessage() );
                    } catch (NoSuchFieldException e) {
                        throw new IllegalArgumentException( e.getMessage() );
                    } catch (IllegalAccessException e) {
                        throw new IllegalArgumentException( e.getMessage() );
                    }
                } else {
                    throw new IllegalArgumentException( "The bean of type: "+ 
                            bean.getClass().getName() + " has no property called: " + name );
                }
            } else {
                try {
                    Object[] arguments = createWriteMethodArguments( method, value );
                    method.invoke( bean, arguments );
                    newValue = get( name );
                } catch ( InvocationTargetException e ) {
                    logInfo( e );
                    throw new IllegalArgumentException( e.getMessage() );
                } catch ( IllegalAccessException e ) {
                    logInfo( e );
                    throw new IllegalArgumentException( e.getMessage() );
                }
                firePropertyChange( name, oldValue, newValue );
            }
            return oldValue;
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.apache.commons.beanutils.BeanMap#keySet()
     */
    public Set keySet() {
        Class<?> c = getBean().getClass();
        Field[] fields = c.getDeclaredFields();
        Set<String> keySet = new HashSet<String>(super.keySet());
        for(Field f: fields){
            if( Modifier.isPublic(f.getModifiers()) && !keySet.contains(f.getName())){
                keySet.add(f.getName());
            }
        }
        keySet.remove("class");
        return UnmodifiableSet.decorate(keySet);
    }
}

棘手的部分是分解 POJO 以返回,但反射可以帮助您:

/**
* Returns a new instance of the specified object.  If the object is a bean, 
* (serializable, with a default zero argument constructor), the default 
* constructor is called.  If the object is a Cloneable, it is cloned, if the
* object is a POJO or a nested POJO, it is cloned using the default 
* zero argument constructor through reflection.  Such objects should only be
* used as transfer objects since their constructors and initialization code
* (if any) have not have been called. 
* @param obj
* @return A new copy of the object, it's fields are blank.
*/
public static Object constructBeanOrPOJO(final Object obj) {

    Constructor<?> ctor = null;
    Object retval = null;
    //Try to invoke where it's Serializable and has a public zero argument constructor.
    if(obj instanceof Serializable){
        try {
            ctor = obj.getClass().getConstructor((Class<?>)null);
            if(ctor.isAccessible()){
                retval = ctor.newInstance();
                //LOG.info("Serializable class called with a public constructor.");
                return retval;
            }
        } catch (Exception ignoredTryConeable) {
        }
    }
    //Maybe it's Clonable.
    if(obj instanceof Cloneable){
        try {
            Method clone = obj.getClass().getMethod("clone");
            clone.setAccessible(true);
            retval = clone.invoke(obj);
            //LOG.info("Cloneable class called.");
            return retval;
        } catch (Exception ignoredTryUnNestedClass) {
        }
    }
    try {
        //Maybe it's not a nested class.
        ctor = obj.getClass().getDeclaredConstructor((Class<?>)null);
        ctor.setAccessible(true);
        retval = ctor.newInstance();
        //LOG.info("Class called with no public constructor.");
        return retval; 
    } catch (Exception ignoredTryNestedClass) {
    }
    try {
       Constructor[] cs = obj.getClass().getDeclaredConstructors();
       for(Constructor<?> c: cs){
          if(c.getTypeParameters().length==0){
             ctor = c;
             ctor.setAccessible(true);
             retval = ctor.newInstance();
             return retval;
          }
       }
       //Try a nested class class.
       Field parent = obj.getClass().getDeclaredField("this$0");
       parent.setAccessible(true);
       Object outer = (Object) parent.get(obj);
       //ctor = (Constructor<? extends Object>) obj.getClass().getConstructors()[0];//NO, getDECLAREDConstructors!!!
       ctor = (Constructor<? extends Object>)         obj.getClass().getDeclaredConstructor(parent.get(obj).getClass());
       ctor.setAccessible(true);
       retval = ctor.newInstance(outer);
       //LOG.info("Nested class called with no public constructor.");
       return retval;
    } catch (Exception failure) {
        throw new IllegalArgumentException(failure);
    }
}

从 bean、可克隆或 POJO 获取通用 bean 的示例代码:

public List<Object> getGenericEJBData(String tableName, Object desiredFields, Object beanCriteria){
    NoGetterBeanMap desiredFieldMap = new NoGetterBeanMap(desiredFields);
    NoGetterBeanMap criteriaMap = new NoGetterBeanMap(beanCriteria);
    List<Object> data = new ArrayList<Object>();
    List<Map<String, Object>> mapData = getGenericEJBData(tableName, desiredFieldMap, criteriaMap);
    for (Map<String,Object> row: mapData) {
        Object bean = NoGetterBeanMap.constructBeanOrPOJO(desiredFields);//Cool eh?
        new NoGetterBeanMap(bean).putAll(row);//Put the data back in too!
        data.add(bean);
    }
    return data;
}

EJB 的使用示例:

IGenericBean genericRemote = BeanLocator.lookup(IGenericBean.class);

//This is the minimum required typing.
class DesiredDataPOJO {
    public String makename="";//Name matches column and return type. 
}
class CriteriaPOJO {
    //Names match column and contains criteria values.
    public String modelname=model,yearid=year; 
}

List<DesiredDataPOJO> data = 
    genericRemote.getGenericEJBData(ACES_VEHICLE_TABLE, new DesiredDataPOJO(), new CriteriaPOJO() );
for (DesiredDataPOJO o: data) {
    makes.add(o.makename);
}

EJB 有一个这样的接口:

package com.aaa.ejb.common.interfaces;

import java.util.List;
import java.util.Map;

import javax.ejb.Local;
import javax.ejb.Remote;

/**
 * @see
 * http://trycatchfinally.blogspot.com/2006/03/remote-or-local-interface.html
 * 
 * Note that the local and remote interfaces extend a common business interface.
 * Also note that the local and remote interfaces are nested within the business
 * interface. I like this model because it reduces the clutter, keeps related
 * interfaces together, and eases understanding.
 * 
 * When using dependency injection, you can specify explicitly whether you want 
 * the remote or local interface.  For example:
 * @EJB(beanInterface=services.DistrictService.IRemote.class)
 * public final void setDistrictService(DistrictService districtService) {
 *    this.districtService = districtService;
 * }
 */
public interface IGenericBean {

    @Remote 
    public interface IRemote extends IGenericBean {
    }

    @Local 
    public interface ILocal extends IGenericBean {
    }

    /**
     * Gets a list of beans containing data.
     * Requires a table name and pair of beans containing the fields
     * to return and the criteria to use.
     * 
     * You can even use anonymous inner classes for the criteria.
     * EX: new Object() { public String modelname=model,yearid=year; }
     * 
     * @param tableName
     * @param fields
     * @param criteria
     * @return
     */
    public <DesiredFields> List<DesiredFields> getGenericEJBData(String tableName, DesiredFields desiredFields, Object beanCriteria);

}

您可以想象 ejb 实现是什么样子的,现在我们正在构建准备好的语句并调用它们,但我们可以使用条件,或者像 hibernate 这样更酷的东西,如果我们想要的话。

这是一个粗略的例子(有些人不喜欢这部分)。在示例中,我们有一个包含第三范式数据的表。为此,bean 字段必须与表列名称匹配。 toLowerCase() 调用是为了以防这是一个正在使用的 REAL bean,这将搞砸名称匹配(MyField 与 getMyfield)。这部分可能会抛光得更好一些。特别是 order by 和 distinct 应该是 flags 或其他东西。可能还有其他边缘条件也可能发生。当然,我只需要编写一次,就性能而言,没有什么能阻止您获得更精确的性能数据接收器。

@Stateless
public class GenericBean implements ILocal, IRemote {

...

/* (non-Javadoc)
 * @see com.aaa.ejb.acesvehicle.beans.interfaces.IAcesVehicleBean#getGenericEJBData(java.lang.String, java.util.Map, java.util.Map)
 */
@Override
public List<Map<String, Object>> getGenericEJBData(String tableName,Map<String, Object> desiredFields, Map<String, Object> criteria){
    try {
        List<Map<String,Object>> dataFieldKeyValuePairs = new ArrayList<Map<String,Object>>();

        StringBuilder sql = new StringBuilder("SELECT DISTINCT ");
        int selectDistinctLength = sql.length();
        for(Object key : desiredFields.keySet()){
            if(desiredFields.get(key)!=null) {
                sql.append(key).append(", ");
            }
        }
        sql.setLength(sql.length()-2);//Remove last COMMA.
        int fieldsLength = sql.length();
        sql.append(" FROM ").append(tableName).append(" WHERE ");

        String sep = "";//I like this, I like it a lot.
        for(Object key : criteria.keySet()){
            sql.append(sep);
            sql.append(key).append(" = COALESCE(?,").append(key).append(") ");
            sep = "AND ";
        }
        sql.append(" ORDER BY ").append(sql.substring(selectDistinctLength, fieldsLength));

        PreparedStatement ps = connection.prepareStatement(sql.toString(), ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
        int criteriaCounter=1;
        for(Object key : criteria.keySet()){
            ps.setObject(criteriaCounter++, criteria.get(key));
        }
        ResultSet rs = ps.executeQuery();
        while (rs.next()) {
            Map<String,Object> data = new HashMap<String,Object>();
            int columnIndex = rs.getMetaData().getColumnCount();
            for(int x=0;x<columnIndex;x++){
                String columnName = rs.getMetaData().getColumnName(x+1);
                if(desiredFields.keySet().contains(columnName.toLowerCase())){
                    //Handle bean getters and setters with different case than metadata case.
                    data.put(columnName.toLowerCase(), rs.getObject(x+1));
                } else {
                    data.put(columnName, rs.getObject(x+1));
                }
            }
            dataFieldKeyValuePairs.add(data);
        }
        rs.close();
        ps.close();
        return dataFieldKeyValuePairs;
    } catch (SQLException sqle) {
        LOG.debug("National database access failed.", sqle);
        throw new EJBException(new DataSourceException("Database access failed. \n"
                + "getGenericEJBData()", sqle.getMessage()));
    }

}

【讨论】:

    猜你喜欢
    • 2012-08-01
    • 1970-01-01
    • 2011-10-30
    • 2019-09-29
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-04
    相关资源
    最近更新 更多