I et inbound callcenter, hvor identiteten på den, der ringer ind, skal bekræftes, kan kontrollen nu foregå i telefonkøen. Det understøtter fem centrale KPI'er for callcentre.
->arne_v: Jeg tramper stadig lidt rundt i dette med at læse noget ekstern information ind fra f.eks. en binær fil (eller en XML-fil). Informationen vil typisk blot være vedr. variable af simpel type og String's, så den klasse jeg vil oprette er helt simpel, idet den udelukkende vil bestå af variabler af simpel type og String-objekter men altså ingen metoder.
Jeg forestiller mig altså, at jeg allerede har læst informationen ind i "maven" på Java på den ene eller anden måde, så jeg nu er klar vil oprette en ny klasse på baggrund heraf.
Man kan sikkert skrive "tekst-definitionen" til klassen (altså svarende til en .java-fil) ned i en fil, som man derefter loader igen og compilerer, men der er vel en mere fiks og direkte måde at gøre det på, tænker jeg (?)
->arne_v: Giver det mening at "føde" ClassLoader direkte med byte-sekvensen svarende til class-file formatet for så vidt de klasser man ønsker at oprette er så simple, som jeg her antyder, eller er det "overkill"?
Med den dynamiske klasse vil jeg gerne kunne oprette objekter heraf, som beskriver de "felter" jeg indlæser fra filen (udledt fra en header-fil/XML-fil), hvor variablerne i den dynamiske klasse er af simpel type snarere end objekter selv (for at optimere læsehastighed).
Er det i øvrigt ClassLoader.defineClass() eller Class.forName() eller noget tredie jeg skal have fokus på her, hvis jeg vil oprette dynamiske klasser?
->arne_v: Jeg forklarer mig nok lidt dårligt/indforstået.
Antag at der skal læses nogle felter 'antal', 'beløb' og 'navn' fra en (binær) fil, hvor 'antal' er integer, 'beløb' er double og 'navn' er String (felt-navne og -typer angivet i en header-fil/XML-fil).
Hvis jeg kendte felternes navne og type på forhånd, kunne jeg blot direkte læse ind fra filen og gemme i 'antal', 'beløb' og 'navn' - no problem. Da jeg imidlertid ikke kender felternes navnene og type før på runtime, nemlig når jeg læser headeren/XML-filen, forestiller jeg mig flg. arbejdsgang:
1. Læs header/XML-fil 2. Dan (dynamisk) header-klasse indeholdende erklæring af 'antal', 'beløb' og 'navn' 3. Opret objekt 'o' af header-klasse 4. Benyt 'o' til at styre indlæsning i o.antal, o.beløb og o.navn 5. Foretag efterfølgende datamanipulation på baggrund af de indlæste felter (f.eks. summering)
Den helt frække variant er vel at danne et (dynamisk) interface i stedet for en klasse, idet jeg derved kan implementere dette og så helt droppe 'o.', dvs. direkte referere til antal, beløb og navn i koden.
Min øvelse kan vel sammenlignes med et database-opslag, hvor man læser alle felter fra en tabel.
Som du tidligere har forklaret, kan denne øvelse foretages ved at gå over en hash-tabel, men jeg er lidt bekymret for læsehastighed ved større datamængder, når der læses ind i objekter snarere end simple variabler.
public class CodeGenAndLoad { private static Object get(Object o, String field) throws Exception { return o.getClass().getMethod("get" + field.substring(0, 1).toUpperCase() + field.substring(1), new Class[0]).invoke(o, new Object[0]); } private static void set(Object o, String field, int value) throws Exception { o.getClass().getMethod("set" + field.substring(0, 1).toUpperCase() + field.substring(1), new Class[] { int.class }).invoke(o, new Object[] { new Integer(value) }); } private static void set(Object o, String field, double value) throws Exception { o.getClass().getMethod("set" + field.substring(0, 1).toUpperCase() + field.substring(1), new Class[] { double.class }).invoke(o, new Object[] { new Double(value) }); } private static void set(Object o, String field, Object value) throws Exception { o.getClass().getMethod("set" + field.substring(0, 1).toUpperCase() + field.substring(1), new Class[] { value.getClass() }).invoke(o, new Object[] { value }); } public static void main(String[] args) throws Exception { Map m = new HashMap(); List d1 = new ArrayList(); d1.add(new FieldDescrip("a", FieldDescrip.FIELD_INT)); d1.add(new FieldDescrip("b", FieldDescrip.FIELD_DOUBLE)); m.put("Bean1", d1); List d2 = new ArrayList(); d2.add(new FieldDescrip("c", FieldDescrip.FIELD_INT)); d2.add(new FieldDescrip("d", FieldDescrip.FIELD_STRING)); m.put("Bean2", d2); SpecialClassLoader scl = new SpecialClassLoader(m); Class c1 = Class.forName("Bean1", true, scl); Object o1 = c1.newInstance(); System.out.println(get(o1, "a")); System.out.println(get(o1, "b")); set(o1, "a", 123); set(o1, "b", 123.456); System.out.println(get(o1, "a")); System.out.println(get(o1, "b")); Class c2 = Class.forName("Bean2", true, scl); Object o2 = c2.newInstance(); System.out.println(get(o2, "c")); System.out.println(get(o2, "d")); set(o2, "c", 123); set(o2, "d", "abc"); System.out.println(get(o2, "c")); System.out.println(get(o2, "d")); } }
class FieldDescrip { public final static int FIELD_INT = 1; public final static int FIELD_DOUBLE = 2; public final static int FIELD_STRING = 3; private String name; private int typ; public FieldDescrip(String name, int typ) { this.name = name; this.typ = typ; } public String getName() { return name; } public int getTyp() { return typ; } }
class SpecialClassLoader extends ClassLoader { private Map m; public SpecialClassLoader(Map m) { this.m = m; } protected Class findClass(String name) { byte[] bc = generateClass(name); return defineClass(name, bc, 0, bc.length); } private Type typeToType(int typ) { switch(typ) { case FieldDescrip.FIELD_INT: return Type.INT; case FieldDescrip.FIELD_DOUBLE: return Type.DOUBLE; case FieldDescrip.FIELD_STRING: return Type.STRING; default: throw new RuntimeException("Bad field typ"); } } private byte[] generateClass(String name) { ClassGen cg = new ClassGen(name, "java.lang.Object", "", Constants.ACC_PUBLIC | Constants.ACC_SUPER, new String[] { }); ConstantPoolGen cpg = cg.getConstantPool(); InstructionFactory ifact = new InstructionFactory(cg, cpg); List d = (List)m.get(name); for(int i = 0; i < d.size(); i++) { FieldDescrip fd = (FieldDescrip)d.get(i); FieldGen field = new FieldGen(Constants.ACC_PRIVATE, typeToType(fd.getTyp()), fd.getName(), cpg); cg.addField(field.getField()); } InstructionList il = new InstructionList(); MethodGen constructor = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, Type.NO_ARGS, new String[] { }, "<init>", name, il, cpg); il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); il.append(ifact.createInvoke("java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL)); il.append(InstructionFactory.createReturn(Type.VOID)); constructor.setMaxStack(); constructor.setMaxLocals(); cg.addMethod(constructor.getMethod()); il.dispose(); for(int i = 0; i < d.size(); i++) { FieldDescrip fd = (FieldDescrip)d.get(i); MethodGen getter = new MethodGen(Constants.ACC_PUBLIC, typeToType(fd.getTyp()), Type.NO_ARGS, new String[] { }, "get" + fd.getName().substring(0, 1).toUpperCase() + fd.getName().substring(1), name, il, cpg); il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); il.append(ifact.createFieldAccess(name, fd.getName(), typeToType(fd.getTyp()), Constants.GETFIELD)); il.append(InstructionFactory.createReturn(typeToType(fd.getTyp()))); getter.setMaxStack(); getter.setMaxLocals(); cg.addMethod(getter.getMethod()); il.dispose(); MethodGen setter = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, new Type[] { typeToType(fd.getTyp()) }, new String[] { "value" }, "set" + fd.getName().substring(0, 1).toUpperCase() + fd.getName().substring(1), name, il, cpg); il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); il.append(InstructionFactory.createLoad(typeToType(fd.getTyp()), 1)); il.append(ifact.createFieldAccess(name, fd.getName(), typeToType(fd.getTyp()), Constants.PUTFIELD)); il.append(InstructionFactory.createReturn(Type.VOID)); setter.setMaxStack(); setter.setMaxLocals(); cg.addMethod(setter.getMethod()); il.dispose(); } return cg.getJavaClass().getBytes(); } }
->arne_v: Men tilgang via reflection er vel kun et initial-problem? Når først den dynamiske klasse er dannet - omend dette kan være langsomt - kører indlæsningen vel hurtigt? (selvfølgelig kun en fordel, hvis der skal læses store datamængder ind)
Det er hurtigt at oprette en klasse dynamisk med BCEL, det er hurtigt at oprette en instans af den klasse. Det er langsomt at kalde metoder i en sådan klasse.
Se eksemplet ovenfor. Ser CodeGenAndLoad get & set metoderne hurtige ud ?
->arne_v: En anden mulighed i forhold til at anvende Runtime.getRuntime().exec("javac ...") ved intern kompilering er at anvende: String[] s = {"Klasse.java"}; (new com.sun.tools.javac.Main()).compile(s);
Jeg ved ikke, om man kan foretage ovenstående uden at gå over en ekstern fil (?)
->arne_v: Jeg ville også mene, at com.sun.tools.javac.Main() var hurtigere end Runtime.getRuntime().exec, men flg. eksempel giver mig noget andet:
import java.io.*;
public class Javac { public static void main(String[] args) throws Exception { long starttid, sluttid; String[] s = {"f:/java/projects/diverse/Testint.java"};
- giver hos mig: 1. forsøg 0.681 sek. 2. forsøg 1.222 sek. 3. og 4. fejler med meddelelsen: f:\java\projects\diverse\Testint.java:5: Incompatible type for declaration. Explicit cast needed to convert char to byte. byte c = 's'; ^ 1 error, 1 warning
Har du en forklaring på, hvorfor - 1. er meget hurtigere end 2. - hvorfor 3. og 4. fejler, mens 2. ikke gør det
Bemærk at jeg i 4. prøver at route byte-coden ud til et ByteArrayOutputStream-objekt; jeg ved ikke om det fungerer pga. kompileringsfejlen.
->arne_v: Har set noget med com.sun.tools.javac.Main(System.out,"javac") og tænker så, at System.out kan skiftes ud med noget andet (?)
Men ellers er din metode med Runtime.getRuntime().exec("javac ...") meget ren efter min mening, idet man blot dynamisk skriver sin klasse i en .java-fil - helt som man ville skrive koden i hånden! - kompilerer efterfølgende til en .class-fil og loader med Class.forName(). Eneste anke ved denne metode er den umiddelbare nødvendighed af eksterne .java- og .class-filer. En løsning med interne objekter vil være at foretrække efter min mening. Omvendt kan jeg godt lide, at metoden trækker direkte på "javac" og ikke noget andet programmel.
Den helt frække løsning er selv at skrive byte-koden til Class-loader'en "by hand", men det er vist "overkill" her.
javac løsningen er rimelig portabel og nem at implementere
hvis performance skal i top bør du bruge BCEL eller en af de 2-3 andre lignende biblioteker- de bruges faktisk n del i J2EE application servere, AOP frameworks og lignende
Undrer mig lidt over, at Sun ikke selv tilbyder en API, hvor dynamisk generering af byte-code er lidt lettere tilgængelig. Kompilering af en dynamisk dannet String af source-kode til byte-code direkte til ClassLoader'en virker som en naturlig måde at gøre det på.
private void createMethod_0() { InstructionList il = new InstructionList(); MethodGen method = new MethodGen(ACC_PUBLIC, Type.VOID, Type.NO_ARGS, new String[] { }, "<init>", "HelloWorld", il, _cp);
private void createMethod_1() { InstructionList il = new InstructionList(); MethodGen method = new MethodGen(ACC_PUBLIC | ACC_STATIC, Type.VOID, new Type[] { new ArrayType(Type.STRING, 1) }, new String[] { "arg0" }, "main", "HelloWorld", il, _cp);
->simonvalter: Det ser ret godt ud, synes jeg. Til mit simple formål bruger jeg nok i første omgang Arne's metode med at generere source-filen og eksekvere med "javac", altså noget i stil med http://eksperten.dk/spm/410887
jeg har ikke kigget nærmere på det andet end jeg har lavet et par eksempler så jeg skal ikke kunne sige om det er godt eller dårligt men næsten hver gang at tss osv snakker om asm toolkit så dukker Bill Burke fra jboss op og reklamerer for javassist og fortæller om hvordan det kan løse de samme problemer meget nemmere da man ikke behøver kende noget til bytecode men stadig har en god performance. det bliver f.eks benyttet i jboss aop.
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.