Crie classs POJO simples (bytecode) em tempo de execução (dinamicamente)

Eu tenho o seguinte cenário ..

Estou escrevendo alguma ferramenta que executa a consulta inserida pelo usuário no database e retorna o resultado.

A maneira mais simples é retornar o resultado como: List mas preciso levar isso um passo adiante.

Eu preciso criar (em tempo de execução ) alguns POJO (ou DTO) com algum nome e criar campos e setters e getters para ele e preenchê-lo com os dados retornados e, em seguida, devolvê-lo ao usuário entre com o arquivo .class gerado …

Então, a idéia aqui é Como criar class simples (bytecode) em tempo de execução (dinamicamente) Eu faço uma pesquisa básica e encontrei muitos lib incluindo Apache BCEL Mas eu acho que preciso de algo mais simples …

O que você acha daquilo?

Obrigado.

Criar um POJO simples com getters e setters é fácil se você usar o CGLib :

 public static Class createBeanClass( /* fully qualified class name */ final String className, /* bean properties, name -> type */ final Map> 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(); } 

Código de teste:

 public static void main(final String[] args) throws Exception{ final Map> properties = new HashMap>(); 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); } } 

Saída:

class 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)

Mas o problema é: você não tem como codificar esses methods, pois eles não existem em tempo de compilation, então eu não sei o que isso vai fazer de bom.

Eu usei o ASM para isso no passado. O que eu gosto é o ASMifier que pode criar código para gerar uma class. Por exemplo, eu crio um POJO genérico no código java com um campo de cada tipo em Java e uso o ASMifier para criar o código Java para criar isso a partir do código de byte e usá-lo como um modelo para gerar um POJO arbitrário.

Como sugere o @Michael, talvez você queira adicionar uma maneira não reflexiva de obter campos arbitrários. por exemplo

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

Por que você quer fazer isso? Existem maneiras de usar objects de estilo Map mais eficientes do que usar um mapa normal.

Outra abordagem é gerar a fonte Java usando o Velocity e compilar o código usando a API do Compiler. É uma dor de usar, então eu escrevi um embrulhado para ele aqui Essence JCF A única vantagem de ler para usar essa abordagem é que você pode facilmente depurar seu código gerado. (A biblioteca tem a opção de salvar o código java em algum lugar onde o depurador possa encontrá-lo quando você entrar no código gerado)

O que o chamador faria com uma class que é gerada na hora e que seu código, portanto, não pode saber? A única maneira de acessá-lo seria através da reflection. Retornar uma List ou Map seria, na verdade, um design muito mais limpo e mais utilizável.

Eu também odeio escrever getters e setters. Eu preferiria usar POJOs, até POJOs declarados como classs aninhadas.

Existe outra maneira de fazer isso, mesmo com servidores e tecnologia antigos e sem introduzir Springs (usamos o JBoss 4.2 e o EJB 3.0 incompleto do JBoss). Estendendo o org.apache.commons.beanutils.BeanMap, você pode envolver o POJO em um mapa de beans, e quando você pegar ou colocar você pode manipular os campos usando a reflection. Se o getter ou setter não existe, apenas usamos manipulação de campo para obtê-lo. Obviamente, NÃO é um bean real, então está tudo bem.

 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 keySet = new HashSet(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); } } 

A parte complicada é considerar os POJOs para retornar, mas a reflection pode ajudá-lo:

 /** * 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) obj.getClass().getConstructors()[0];//NO, getDECLAREDConstructors!!! ctor = (Constructor) 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); } } 

Exemplo de código para obter um bean genérico de um bean, um cloneable ou um POJO:

 public List getGenericEJBData(String tableName, Object desiredFields, Object beanCriteria){ NoGetterBeanMap desiredFieldMap = new NoGetterBeanMap(desiredFields); NoGetterBeanMap criteriaMap = new NoGetterBeanMap(beanCriteria); List data = new ArrayList(); List> mapData = getGenericEJBData(tableName, desiredFieldMap, criteriaMap); for (Map 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; } 

Exemplo de uso com o 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 data = genericRemote.getGenericEJBData(ACES_VEHICLE_TABLE, new DesiredDataPOJO(), new CriteriaPOJO() ); for (DesiredDataPOJO o: data) { makes.add(o.makename); } 

O EJB tem uma interface como esta:

 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 classs for the criteria. * EX: new Object() { public String modelname=model,yearid=year; } * * @param tableName * @param fields * @param criteria * @return */ public  List getGenericEJBData(String tableName, DesiredFields desiredFields, Object beanCriteria); } 

Você pode imaginar como é a implementação do ejb, agora estamos construindo declarações preparadas e chamando-as, mas poderíamos usar critérios, ou algo mais legal, como o hibernate ou o que quer que quiséssemos.

Aqui está um exemplo grosseiro (alguns não vão gostar desta parte). No exemplo, temos uma única tabela com dados na terceira forma normal. Para que isso funcione, os campos do bean precisam corresponder aos nomes das colunas da tabela. As chamadas toLowerCase () estão no caso de ser um bean REAL sendo usado, o que estragará o nome correspondente (MyField vs. getMyfield). Esta parte provavelmente poderia ser polida um pouco melhor. Em particular, a ordem por e distinta deve ser sinalizadores ou algo assim. Existem provavelmente outras condições de borda que também podem acontecer. Claro que eu só tenho que escrever uma vez, e para o desempenho, nada impede que você tenha um receptor muito mais preciso dos dados para o desempenho.

 @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> getGenericEJBData(String tableName,Map desiredFields, Map criteria){ try { List> dataFieldKeyValuePairs = new ArrayList>(); 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 data = new HashMap(); int columnIndex = rs.getMetaData().getColumnCount(); for(int x=0;x 

Bem Isso também pode dar uma chance . Mas eu preciso entender isso se alguém puder explicar.

ATUALIZAÇÃO:

Imagine que seu aplicativo tenha que criar instâncias Java POJO dinamicamente em tempo de execução a partir de alguma configuração externa. Essa tarefa pode ser facilmente executada usando uma das bibliotecas de manipulação de bytecode. Este post demonstra como isso pode ser feito usando a biblioteca Javassist.

Suponha que tenhamos a seguinte configuração para as propriedades que nosso POJO criado dinamicamente deve conter:

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

Vamos escrever um PojoGenerator, que gera dinamicamente um object Class para o nome da class e um mapa, contendo as propriedades necessárias:

 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> 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> 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()); } } 

É isso aí!

Usar o PojoGenerator é bem simples. Vamos gerar algum POJO, saída através da reflection de todos os seus methods, setar e depois obter alguma propriedade:

 public static void main(String[] args) throws Exception { Map> props = new HashMap>(); 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); } 

A execução acima resultará na seguinte saída do console:

 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! 

A solução é fornecida pela primeira resposta do link a seguir, que é 1 de ques da pilha) Estouro em si. Dinamicamente crie classs de tabela e java em tempo de execução

Intereting Posts