Avatar billede jespersahner Nybegynder
18. november 2004 - 13:58 Der er 41 kommentarer og
1 løsning

ClassLoader.defineClass - eksempel efterlyses

Jeg ønsker (dynamisk) at oprette en ny klasse, og kigger på ClassLoader.defineClass.

Er er en, der har et simpelt eks. på anvendelse?
Avatar billede arne_v Ekspert
18. november 2004 - 14:04 #1
Hvad bruger du til at lave klassen med ? BCEL ?
Avatar billede jespersahner Nybegynder
18. november 2004 - 14:43 #2
->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 (?)
Avatar billede jespersahner Nybegynder
18. november 2004 - 17:51 #3
->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"?

Jeg har lidt kig på: http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html
Avatar billede arne_v Ekspert
18. november 2004 - 18:56 #4
Jeg tror stadig at BCEL er nemmere.

Jeg kan prøve at lave et eksempel.

Men noget helt andet. Når du nu har lavet din dynamiske klasse - hvad så ?
Avatar billede jespersahner Nybegynder
18. november 2004 - 20:33 #5
->arne_v: Kender ikke BCEL, men er lydhør :-)

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?
Avatar billede arne_v Ekspert
18. november 2004 - 20:37 #6
ClassLoader.findClass

Jeg arbejder på et eksempel
Avatar billede arne_v Ekspert
18. november 2004 - 20:38 #7
Problemet er når du har createt din klasse og instantieret et objekt af den
type - hvad så ?
Avatar billede jespersahner Nybegynder
18. november 2004 - 21:13 #8
->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.
Avatar billede simonvalter Praktikant
18. november 2004 - 21:29 #9
istedet for becl kunne du også bruge asm toolkit.
der er en artikel her som bla. også viser hvordan man implementerer et interface
http://www.onjava.com/pub/a/onjava/2004/10/06/asm1.html
Avatar billede arne_v Ekspert
18. november 2004 - 21:34 #10
import java.util.*;

import org.apache.bcel.*;
import org.apache.bcel.generic.*;

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();
    }
}
Avatar billede arne_v Ekspert
18. november 2004 - 21:35 #11
Eksemplet viser, hvordan du kan lave dynamisk beans med BCEL.

Eksemplet viser også at det ikke hjælper dig spor da tilgang via reflection
er langsommere end tilgang til HashMap.
Avatar billede jespersahner Nybegynder
18. november 2004 - 21:38 #12
->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)
Avatar billede arne_v Ekspert
18. november 2004 - 21:42 #13
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 ?
Avatar billede jespersahner Nybegynder
18. november 2004 - 21:42 #14
->arne_v: En sidebem.: Er byte-koden svarende til en kompileret .class-fil platformsuafhængig?
Avatar billede arne_v Ekspert
18. november 2004 - 21:43 #15
ja
Avatar billede arne_v Ekspert
18. november 2004 - 21:58 #16
Hvis alle de dynamiske beans implementerede samme interface så man kunne:

I o = (I)Class.forName("Beanx", true, cl);

så var det hurtigt.

Og det bruges meget i mange sammenhænge. Men lige netop i data opbevarings
sammenhæng synes jeg ikke at det er specielt brugbart.
Avatar billede jespersahner Nybegynder
18. november 2004 - 23:59 #17
->arne_v: I forhold til BCEL-løsningen er din egen kode i http://eksperten.dk/spm/410887 vel også en mulighed?
Avatar billede arne_v Ekspert
19. november 2004 - 00:01 #18
Ja.

Simplere men også mindre elegant og langsommere.

Men med samme problem med hvad bagefter.
Avatar billede jespersahner Nybegynder
19. november 2004 - 00:12 #19
->arne_v: Givetvis, men kan man ikke gå via en String indeholdende den relevante kode uden at skrive til en ekstern fil?
Avatar billede arne_v Ekspert
19. november 2004 - 07:59 #20
Måske. Jeg har aldrig set det, men det var da en ret logisk mulighed.
Avatar billede jespersahner Nybegynder
19. november 2004 - 17:10 #21
->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 (?)
Avatar billede arne_v Ekspert
19. november 2004 - 19:43 #22
Det var også en mulighed.

Den kan ikke kaldes på en String.

Generelt er jeg skeptisk overfor Java kode som bruger SUN's interne klasser.

Men det er selvfølgelig hurtigere end exec.

BCEL er dog endnu hurtigere.
Avatar billede jespersahner Nybegynder
20. november 2004 - 02:35 #23
->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"};
   
    // 1. forsøg
    starttid = System.currentTimeMillis();
    Runtime.getRuntime().exec("c:/j2sdk1.4.2_04/bin/javac Testint.java").waitFor();
    sluttid = System.currentTimeMillis();
    System.out.println("1. forsøg "+ (sluttid-starttid)*0.001 +" sek.");
   
    // 2. forsøg
    starttid = System.currentTimeMillis();
    (new com.sun.tools.javac.Main()).compile(s);
    sluttid = System.currentTimeMillis();
    System.out.println("2. forsøg "+ (sluttid-starttid)*0.001 +" sek.");
       
    // 3. forsøg
    starttid = System.currentTimeMillis();
    sun.tools.javac.Main compiler2=new sun.tools.javac.Main(System.out,"c:/j2sdk1.4.2_04/bin/javac");
    compiler2.compile(s);
    sluttid = System.currentTimeMillis();
    System.out.println("3. forsøg "+ (sluttid-starttid)*0.001 +" sek.");
   
    // 4. forsøg
    starttid = System.currentTimeMillis();
    ByteArrayOutputStream o=new ByteArrayOutputStream();
    sun.tools.javac.Main compiler3=new sun.tools.javac.Main(o,"c:/j2sdk1.4.2_04/bin/javac");
    compiler3.compile(s);
    sluttid = System.currentTimeMillis();
    System.out.println("4. forsøg "+ (sluttid-starttid)*0.001 +" sek.");
    System.out.println(o);
    }
}

- 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.
Avatar billede jespersahner Nybegynder
20. november 2004 - 18:41 #24
->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.
Avatar billede arne_v Ekspert
20. november 2004 - 20:09 #25
Fordelen ved com.sun.tools.javac.Main() er nok mest når den skal kaldes flere gange.
Avatar billede arne_v Ekspert
20. november 2004 - 20:11 #26
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
Avatar billede jespersahner Nybegynder
20. november 2004 - 20:26 #27
->arne_v: Smid lige et svar.
Avatar billede jespersahner Nybegynder
20. november 2004 - 20:30 #28
->arne_v: Skal lige høre, om "Soot" siger dig noget i den sammenhæng? Så noget kode der så ret tilgængelig ud.
Avatar billede arne_v Ekspert
20. november 2004 - 20:34 #29
new sun.tools.javac.Main(System.out,"c:/j2sdk1.4.2_04/bin/javac")

1. argument er der hvor fejl beskeder udskrives
2. argument er det som udskrives ved instruktion til brugeren om hvordan programmet skal køres

byte c = 's';

skal være:

byte c = (byte)'s';

og det burde alle 4 fejle med.
Avatar billede arne_v Ekspert
20. november 2004 - 20:35 #30
svar
Avatar billede arne_v Ekspert
20. november 2004 - 20:39 #31
Jeg har ikke hørt om Soot før.

Det var nemt at finde via Google.

Umiddelbart ser da da ud som om at det er for kode optimering - ikke for kode
generering.

Men derfor kan der selvfølgelig godt være kode genererings funktionalitet
i det.
Avatar billede jespersahner Nybegynder
20. november 2004 - 20:40 #32
Avatar billede arne_v Ekspert
20. november 2004 - 20:41 #33
For en liste over BCEL alterantiver se:

http://java-source.net/open-source/bytecode-libraries
Avatar billede arne_v Ekspert
20. november 2004 - 20:43 #34
OK - den kan generere byte code.

Men er den kode pænere end BCEL ??
Avatar billede jespersahner Nybegynder
20. november 2004 - 20:57 #35
->arne_v: Nja, det kan vist diskuteres :-)

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å.
Avatar billede arne_v Ekspert
20. november 2004 - 21:03 #36
HelloWorld a la BCEL ser ud som:

import org.apache.bcel.generic.*;
import org.apache.bcel.classfile.*;
import org.apache.bcel.*;
import java.io.*;

public class HelloWorldCreator implements Constants {
  private InstructionFactory _factory;
  private ConstantPoolGen    _cp;
  private ClassGen          _cg;

  public HelloWorldCreator() {
    _cg = new ClassGen("HelloWorld", "java.lang.Object", "HelloWorld.java", ACC_PUBLIC | ACC_SUPER, new String[] {  });

    _cp = _cg.getConstantPool();
    _factory = new InstructionFactory(_cg, _cp);
  }

  public void create(OutputStream out) throws IOException {
    createMethod_0();
    createMethod_1();
    _cg.getJavaClass().dump(out);
  }

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

    InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 0));
    il.append(_factory.createInvoke("java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
    InstructionHandle ih_4 = il.append(_factory.createReturn(Type.VOID));
    method.setMaxStack();
    method.setMaxLocals();
    _cg.addMethod(method.getMethod());
    il.dispose();
  }

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

    InstructionHandle ih_0 = il.append(_factory.createFieldAccess("java.lang.System", "out", new ObjectType("java.io.PrintStream"), Constants.GETSTATIC));
    il.append(new PUSH(_cp, "Hello world !"));
    il.append(_factory.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));
    InstructionHandle ih_8 = il.append(_factory.createReturn(Type.VOID));
    method.setMaxStack();
    method.setMaxLocals();
    _cg.addMethod(method.getMethod());
    il.dispose();
  }

  public static void main(String[] args) throws Exception {
    HelloWorldCreator creator = new HelloWorldCreator();
    creator.create(new FileOutputStream("HelloWorld.class"));
  }
}
Avatar billede arne_v Ekspert
20. november 2004 - 21:05 #37
Og det kan godt skrives lidt pænere.

Det er output fra et lille tool der kommer med BCEL som kan generere
BCEL kode fra en .class fil (en god måde at lære BCEL på !).
Avatar billede simonvalter Praktikant
21. november 2004 - 01:39 #38
becl er ikke ligefrem det nemmeste at arbejde med så hvis du vil have det lidt nemmere kan du kigge på javassist
http://www.csg.is.titech.ac.jp/~chiba/javassist/

et eksempel:
ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("TempClass");
        CtMethod m = CtNewMethod.make("public double returnCount(double count) { return count; }", cc);
        cc.addMethod(m);

        CtClass interface1 = pool.makeInterface("TempInterface");
        CtMethod m1 = CtNewMethod.make("public double returnCount(double count);", interface1);
        interface1.addMethod(m1);

        CtClass interfaces[] = {interface1};
        cc.setInterfaces(interfaces);

Class c = cc.toClass();
Avatar billede jespersahner Nybegynder
21. november 2004 - 10:05 #39
->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
Avatar billede arne_v Ekspert
21. november 2004 - 15:02 #40
Den er jo så mere compiler orienteret hvor BCEL og Soot er assembler orienteret.

Understøtter den fuld syntax ?
Avatar billede simonvalter Praktikant
22. november 2004 - 19:57 #41
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.
Avatar billede arne_v Ekspert
22. november 2004 - 20:24 #42
Der er åbenlyst politik i det.

BCEL - Apache - Geronimo
ASM - ObjectWeb - Jonas
JavaAssist - JBoss - JBoss

Men det ser faktisk nemt ud !
Avatar billede Ny bruger Nybegynder

Din løsning...

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.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester