„Code“ -Generierung in Java

Werbung

Effective Java
Programm schreibt Programm
„Code“
-Generierung in Java
Michael Hunger
Wir alle sind es gewohnt, Java als relativ statische Sprache zu betrachten. Wir schreiben Code, dieser wird compiliert und steht dann über
den ClassLoader zur Ausführung zur Verfügung. In anderen Sprachen
ist es üblich, ein Programm auch nach dem initialen Laden noch zu
verändern, man denke nur an JavaScript oder die Metaprogrammierung
von Ruby und Groovy. Wir können das im begrenzten Rahmen auch,
mittels Reflection, Virtual Proxies oder den neuen MethodHandle-APIs.
Einige Ansätze gehen sogar noch viel weiter, wie JRebel immer wieder
beeindruckend beweist.
Warum denn eigentlich?
Für diverse Anforderungen ist es schon praktisch, ausführbare Java-Klassen zur Lauf- oder Ladezeit zu erzeugen oder zu modifizieren. Insbesondere wenn dynamisches
oder nutzergeneriertes Verhalten nicht nur interpretiert, sondern effizient (inkl. JIT) von der JVM ausgeführt werden soll.
Man denke nur an dynamische Ausdrücke (siehe SpEL-Artikel von Thomas Darimont [Dari15]) und Datenbankabfragen,
die zur Laufzeit compiliert werden sollen. Oder die flexiblen
Proxies von Sven Ruppert und Heinz Kabutz [KabRup15a/b].
Ein weiteres Beispiel sind Regeln von Rule- oder BPM-Engines
oder andere externe DSLs.
Weitere Anwendungsfälle sind die Generierung von
Metadaten-Repräsentationen als Klassen, zum Beispiel für
Protokollformate, oder Platzhalter für Datenbank-Schemata
(Criteria API). Spannend ist auch die Generierung von internen DSLs mit fluent Interfaces und Methoden, zum Beispiel
aus Grammatiken oder von Parsern wie zum Beispiel bei
ANTLR.
E
Quellcodegenerierung
Wer bei der Umsetzung einer solchen Anforderung einmal
mit Code-Erzeugung mittels des Verknüpfens von Strings angefangen hat, ist schnell auf die häufige Duplikation von ähnlichen Aufrufen und Fragmenten gestoßen und hat sich über die
fehlende Typsicherheit und Nutzung von Literalen geärgert.
Oft führt das zum kurzfristigen Refactoring in einer internen
DSL, die dann angenehmer zu nutzen, aber natürlich nicht direkt zielführend für die eigentliche Aufgabe ist.
Daher ist es sinnvoll zu wissen, welche nützlichen Tools es in
diesem Bereich schon gibt.
Bytecode-Generierung
Für manche Anwendungsfälle, besonders aber in Bibliotheken
oder Java-Agenten, wo man schnell zur Lauf- oder Ladezeit Integrationscode generieren muss, bietet sich eher die BytecodeGenerierung an. Genauso bei Anreicherung von existierenden
Systemen zur Laufzeit zum Beispiel mit den klassischen Querschnittsfunktionalitäten wie Auditing oder Sicherheitschecks
mittels AspectJ ist Bytecode unabdingbar.
Die JVM ist eine Stack-Maschine. Ihr Bytecode ist nicht sonderlich schwer zu lesen, man muss nur gut aufpassen, was auf
den Stack bewegt wird und was wieder herunterkommt. Er ist
langatmiger als Assembler besonders wegen der Typ- und Methodenliterale.
Zum Glück muss man keinen reinen Bytecode manuell eingeben. Viele der Bytecode-Generatoren machen es dem Nutzer
aber viel einfacher, da sie diesen entweder über eine DSL oder
über das Umwandeln von Code-Fragmenten erzeugen.
Einen anderen, sehr beeindruckenden Weg mit extrem effizientem Ergebnis hatte ich vor einer Weile mit dem Gespann
aus dem Truffle-AST-Framework und dem Graal-Compiler
[Hung14] vorgestellt, das uns hoffentlich in Java 9 dann offiziell beehren wird.
Aus all diesen Gründen möchte ich heute einmal verschiedene Ansätze zur Codegenerierung für Java vorstellen.
Toolübersicht
Die Liste der verfügbaren Tools in Tabelle 1 ist erstaunlich lang
und bei Weitem nicht vollständig. Auch wenn es keine alltägliche Aufgabe ist, scheint der Schuh doch oft genug gedrückt zu
haben. Verschiedene Autoren haben eine Menge Bibliotheken
mit unterschiedlichen Ansätzen veröffentlicht, um den diversen Anforderungen gerecht zu werden.
Mit einem Generierungsansatz kann man den Aufwand zur Pflege eines solchen Codes, der aus einer wohldefinierten Quelle reproduziert werden kann, deutlich reduzieren. Auch wird durch einen solchen „SingleSource“-Ansatz das etwaige AuseinanderlauName
Art der Generierung
fen von manuell gepflegtem Code vermieden.
ASM
Bytecode
Auch wenn das endgültige Ziel JavaCGLib
Bytecode
Bytecode darstellt, führt der „Umweg“
ByteBuddy
Bytecode
über Quellcode genauso zum Ziel und
Janino
Bytecode,Compiler
kann teilweise sogar verständlicher sein.
Seit Java 6 ist in der tools.jar des JDK ein
AspectJ
Bytecode, Weaving
Laufzeitcompiler [JavaCompiler] enthalten,
JavaAssist
Bytecode
der genutzt werden kann, um Quellcode
JavaPoet
Quellcode
in Bytecode zu transformieren. Dieser
Groovy
Metaprogrammierung
kann dann direkt über ClassLoader oder
mittels Instrumentierung geladen werden.
javax.tools.JavaCompiler
Bytecode
Sowohl die JavaDoc des Compilers als
Reflection
Laufzeitinteraktion
auch Heinz Kabutz in seinem Newsletter
XText
Quellcode
180 [Kabutz10a] beschreiben im Detail, wie
dieser genutzt wird.
Tabelle 1: Ansätze zur Codegenerierung für Java
www.javaspektrum.de
aktiv?
ja
nein
ja
kaum
ja
ja
ja
ja
ja
ja
ja
beschrieben
ja
ja
ja
nein
minimal
ja
ja
nein
nein
nein
nein
55
Effective Java 
AspectJ
AspectJ [AspectJ] wird schon seit 2001 eingesetzt, um Java-Systeme nachträglich durch dynamische und Querschnittsfunktionalitäten anzureichern. Es kann zur Compile- oder Ladezeit
generierten Bytecode in existierende Klassen hineinweben und
damit alle Aspekte der Ausführung beeinflussen. Von Feldzugriff über Instanziierung bis zur Methodenausführung.
In Aspekten werden die AspektJ-Bestandteile definiert. Man
kann mit Erweiterungsmethoden neue Funktionalität definieren.
Über sogenannte Pointcuts werden die Stellen (Join Points) festgelegt, an denen Funktionalität angebunden werden soll. Und
Advices legen fest, welche Funktionalität an einem Pointcut auf
welche Art und Weise aktiv wird (davor, danach, stattdessen).
AspectJ hatte lange Jahre eine sehr starke Anwendung und
Verbreitung (u. a. im Spring-Framework), es ist jetzt aber eher
in den Hintergrund getreten.
ASM
ASM [ASMIntro] ist eines der ältesten (seit 2000) und bewährtesten Tools zur Bytecode-Manipulation. Wegen seiner Kompaktheit und Geschwindigkeit wird es innerhalb vieler anderer
Frameworks eingesetzt. Es liegt mittlerweile in Version 5 vor.
Mit ASM kann direkt Bytecode erzeugt werden und es können auch Transformationen existierender Klassen und Methoden vorgenommen werden. Diese basieren auf einem extrem
schnellen Bytecode-Scanner, der ereignisgesteuert über ein Visitor-API Informationen an den Aufrufer zurückliefert, aber
auch erlaubt, währenddessen den Bytecode zu modifizieren.
Dabei werden gängige Transformationen und Analysen schon
mitgeliefert.
Ausgehend vom Classreader wird im ClassVisitor für jeden
Aspekt der Klasse (Felder, Konstruktor, Methoden, Annotationen usw.) eine Callback-Methode aufgerufen, wie visitField
oder visitMethod. Einige dieser Methoden geben wieder eigene
Visitoren (wie MethodVisitor, InstructionAdapter) zurück, die dann
weiterhin aufgerufen werden, um tiefere Details zu instrumentieren.
ASM bringt in neueren Versionen das Tool ASMifier mit, das
für eine gegebene Klasse die notwendigen ASM-Aufrufe generiert, um eine ebensolche Klasse zu erzeugen. Es wird mittels
java -cp asm-all-<version>.jar org.objectweb.asm.util.ASMifier
java.lang.Integer > IntegerVisitor.java
aufgerufen.
Prinzipiell läuft die Erzeugung von Bytecode mit ASM so ab:
HErzeugung eines ClassWriter.
HAufruf von cw.visit() und Übergabe von FQN, Superklassen,
Interfaces, Modifiern usw.
HFür jedes Feld: Aufruf von cw.visitField(modifier, name, typ, …)
und Zuweisung an einen FieldVisitor.
HAufruf von fv.visit*() zur Übergabe des Initialisierungscodes.
HFür jede Methode: Aufruf von cw.visitMethod(modifier, name,
type descriptor, signatur, exceptions) und Zuweisung an einen
MethodVisitor.
HAufruf von mv.visit*() zur Übergabe von Instruktionen für
den Methodenrumpf.
HAufruf von cw.visitEnd().
HAufruf von cw.toByteArray() zum Erhalten des Bytecodes.
Wenn Klassen nur partiell geändert werden sollen, dann erfolgt das über angepasste Instanzen der Visitoren die in den
entsprechenden visit*-Methoden vor, nach oder statt der Su56
perklassen-Aufrufe die entsprechenden Bytecode-Instrumentierungs-Aufrufe durchführen:
// Ausgabe des Methodennamens in jeder Methode
... extends MethodVisitor {
@Override
public void visitCode() {
super.visitFieldInsn(GETSTATIC, "java/lang/System",
"out", "Ljava/io/PrintStream;");
super.visitLdcInsn("method: "+methodName);
super.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V");
super.visitCode();
}
}
Javaassist
Javassist [JavaassistTutorial], jetzt verfügbar in Version 3.20,
macht die Generierung von Bytecode einfach, da er auch JavaQuellcode-Fragmente verarbeitet, die man den API-Methoden
als Strings übergibt, welche dann direkt umgewandelt werden.
Der generierte Bytecode kann dann an spezifischen Stellen innerhalb von Methoden oder Klassen eingefügt werden. Es gibt
auch ein reines Bytecode-API, das die direkte Manipulation erlaubt. Klassen können zur Laufzeit geändert, aber auch beim
Laden durch die JVM modifiziert werden.
Das ganze basiert auf einer objektorientierten Repräsentation von Klassen (CtClass), Methoden (CtMethod) und Feldern
(CtField), die direkt inspiziert, manipuliert und erzeugt werden
können. Es gibt einige Einschränkungen, beispielsweise können keine Methoden gelöscht (sondern nur umbenannt) und
auch nicht um Parameter ergänzt werden (stattdessen Overloading und Delegation). Neuer Code kann in Methoden am
Anfang, am Ende, an bestimmten Zeilen und als umschließender try-catch-Block eingefügt werden. Der Rumpf der Methode
kann auch komplett ersetzt werden. Im übergebenen Quellcode können Substitutionen wie $0, $1 für Parameter oder $type
für den Ergebnistyp oder $_ für das bisherige Ergebnis genutzt
werden.
Der Zugriff auf Klassendefinitionen (CtClass) erfolgt über einen ClassPool, der sich auch um weitere Aspekte wie Klassenpfade kümmert. Das Ergebnis der Manipulation kann dann auf
diverse Weise in Bytecode beziehungsweise geladene Klassen
überführt werden:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("company.Person");
// ab hier kann die Klasse modifiziert werden
cc.setSuperclass(pool.get("company.Entity"));
CtMethod m = cc.getDeclaredMethod("call");
m.insertBefore(
"{ System.out.println(\"Are you sure you want to call\"+$0+\"?\"); }");
cc.writeFile();
byte[] bytes = cc.toBytecode();
// direkt Klasse erzeugen
Class clazz = cc.toClass();
Zugriff auf die darunterliegenden Bytecode-Informationen
kann über ctClass.getClassFile() und ctMethod.getMethodInfo() erlangt werden.
Per se können Klassen nur modifiziert werden, wenn sie
noch nicht geladen wurden. Also entweder vorher oder während des Ladens mit einem ClassTransformer [Müller14]. Mit
einem Java-Agenten mit dem Instrumentation-API und redefineClasses könnte man das Neuladen einer Klasse erzwingen,
JavaSPEKTRUM 6/2015

Effective Java
ebenso mit dem Debugger-API, oder mit der Neuerzeugung
des ClassLoaders, der die Klasse bisher geladen hat. Aber natürlich nur, wenn sie den Regeln des JVM-Hot-Reloads entspricht. Das alles wird in [Müller14] gut erklärt.
Zurzeit unterstützt der Javaassist-Compiler keine Enums
und Generics, ebenso wenig innere (anonyme) Klassen. Zugriff darauf ist nur über die darunterliegenden Bytecode-APIs
möglich.
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)",System.class,"Hello, JavaPoet!")
.build();
// "HelloWorld" Klasse mit der "main" Methode
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
ByteBuddy
// .java Datei mit import Deklarationen und Package
ByteBuddy ist eine moderne, kleine, aber schnelle Integrationsbibliothek von Rafael Winterhalter [Winterhalter14b], die darauf spezialisiert ist, existierenden Code miteinander zu verbinden. Sie benutzt ASM unter der Haube, um Bytecode zu manipulieren. Um die Komplexität des Erzeugens von Bytecode zu
vermindern, wird zumeist an in statischen Methoden vorliegende Implementierungen delegiert.
ByteBuddy benutzt eine kompakte DSL, um die Verknüpfung von Klassendefinition, Ziel und die neuen Aufrufe zu beschreiben. Dabei werden oft Annotationen genutzt, um Ziele
der Anpassung zu markieren. Insofern ähnelt es etwas AspectJ,
nur dass hier eine interne Java-DSL zum Einsatz kommt.
Hier als Beispiel die Implementierung einer einfachen Absicherung von annotierten Methoden für eine notwendige Rolle:
class ByteBuddySecurityLibrary implements SecurityLibrary {
// "Speicher" für aktuellen User
public static User currentUser = User.anonymous;
@Override
public Class<? extends T> secure(Class type) {
return new ByteBuddy()
// mit @Secured annotierte Methoden
.method(isAnnotatedBy(Secured.class))
// Delegation an diese ByteBuddySecurityLibrary.intercept
.intercept(MethodDelegation.to(ByteBuddySecurityLibrary.class))
.make()
.load(type.getClassLoader(),
ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
}
}
JavaPoet
JavaPoet von Square bietet eine interne DSL zum Generieren
von Java-Code. Sie nutzt eine Builder-DSL mit fluent Interfaces, um Methoden, Parameter, Felder, Annotationen, Klassen
und Dateien zu generieren. Im Kern sind die genutzten SpecObjekte aber unveränderlich und können so partiell und mehrfach genutzt und mit neuen Informationen abgeleitet werden.
Dabei wird soweit wie möglich mit typsicheren Konstanten
und Literalen (z. B. Klassenliterale) gearbeitet:
www.javaspektrum.de
helloWorld)
.build();
javaFile.wr iteTo(System.out);
Für Methodenrümpfe und Ausdrücke werden Strings für
Code-Fragmente genutzt. Um den Komfort dabei zu erhöhen,
gibt es auch da eine kleine fluent DSL, die Ausdrücke zusammenstellt und sich beispielsweise um Einrückungen, Umbrüche und Semikolons kümmert:
private MethodSpec computeRange(String name,
int from, int to, String op) {
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 0")
.beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
.addStatement("result = result $L i", op)
.endControlFlow()
.addStatement("return result")
.build();
}
In Code-Strings kann man semantische Platzhalter nutzen, die
unterschiedlich interpretiert werden. Somit kann eine Typprüfung mit den übergebenen Parametern vorgenommen werden.
H $L für Literale,
H $S für Strings mit Anführungszeichen, Escapes und Umbrüchen,
H $T für Typen aus Klassenliteralen oder ClassName-Definitionen,
mit automatischer import-Deklaration am Dateianfang,
H $N wird genutzt, um auf Namen anderer Elemente (Spec-Objekte) der generierten Klasse oder Methode zuzugreifen,
Hfür den Quellcode eines Spec-Objektes benutzt man dieses
ebenfalls mit $L.
JavaPoet unterstützt auch die Erzeugung von Enums und inneren anonymen Klassen.
Ich persönlich fände es schön, wenn JavaPoet einige APIBequemlichkeiten mitbringen würde, das würde duplikaten
Code einsparen. Zum Beispiel TypeSpec.publicClassBuilder, addPrivateMethod, addPrivateFieldWithGetter, addIfStatement usw.
@RuntimeType
public static Object intercept(@SuperCall Callable<?> superMethod,
@Origin Method method) throws Exception {
// Abfangen des Aufrufs und Prüfung der Zugriffsrechte
Role role = method.getAnnotation(Secured.class).requiredRole();
if (currentUser.hasRole(role)) return superMethod.call();
throw new SecurityException(method, role, user);
}
// public static void main(String[] args)
//
{ System.out.println("Hello JavaPoet!"); }
MethodSpec main = MethodSpec.methodBuilder("main") JavaFile javaFile = JavaFile.builder("com.example.helloworld",
5
CGLib
CGLib ist eine schon etwas in die Jahre gekommene, abstraktere Programmierschnittstelle, um Bytecode zu erzeugen. Sie
wurde bisher zum Beispiel in Hibernate für das Anreichern
von Entitäten, für das dynamische Nachladen und andere
Funktionalitäten genutzt.
Der häufigste Anwendungsfall ist die Erzeugung von Subklassen existierender Klassen, in denen Verhalten von nichtfinalen Methoden verändert wird, ähnlich wie bei (dynamischen) Proxies, bei denen CGLib diverse Anleihen nimmt. Mittels Enhancer-API ist das relativ einfach möglich:
57
Effective Java 
// welche Klasse soll abgeleitet werden
[DynamicProxyTutorialDW] B. Goetz, Java theory and practice:
Decorating with dynamic proxies, 2005,
enhancer.setSuperclass(MyFormatter.class);
http://www.ibm.com/developerworks/library/j-jtp08305/
// welcher callback für alle Methoden
[DynamicProxyTutorialJJ] J. Jenkov, Java Reflection – Dynamic
Proxies, 2014,
Enhancer enhancer = new Enhancer();
// hier mit festem Rückgabewert
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
if(method.getDeclaringClass() != Object.class &&
method.getReturnType() == String.class) {
return "Fixed Format";
} else {
return proxy.invokeSuper(obj, args);
}
}
});
Formatter proxy = (Formatter) enhancer.create();
proxy.format(new Date()) -> "Fixed Format"
http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html
[GroovyMetaProg] http://www.groovy-lang.org/metaprogramming.html
[Hung14] M. Hunger, Dynamische Compiler mit Graal und
Truffle, in: JavaSPEKTRUM, 06/2014
[Janino] http://unkrig.de/w/Janino
[JaninoTutorial] T. Gibara, Tackling Java Performance Problems
with Janino, 2007, https://today.java.net/pub/a/today/2007/02/15/
tackling-performance-problems-with-janino.html
[JavaassistTutorial]
http://jboss-javassist.github.io/javassist/tutorial/tutorial.html
[JavaCompiler]
http://docs.oracle.com/javase/7/docs/api/javax/tools/JavaCompiler.html
Neben diesem mächtigen, aber aufwendigen MethodInterceptor
gibt es für andere Einsatzfälle alternative Callbacks, wie den
effizienten InvocationHandler sowie den simplistischen FixedValueCallback.
Die Dokumentation für CGLib ist ziemlich spärlich. Lustigerweise stammt die ausführlichste Beschreibung, das „missing Manual“ aus 2013, von Rafael Winterhalter [CGLibTutorial], dem Autor von ByteBuddy.
Fazit
Es gibt noch weitere Ansätze, wie die modellgetriebene Softwareentwicklung, die aus detailliert spezifizierten Modellen
große Teile des Basisquelltexts (und andere Artefakte) eines
Projekts generiert, der dann mittels Konfiguration, Ableitung
oder Delegation konkretisiert wird. Das geht jedoch weit über
das hinaus, was ich hier vorstelle.
Wie jede andere Methode sollte man Codegenerierung bewusst nur dann einsetzen, wenn es wirklich notwendig ist und
der Nutzen den Aufwand weit überwiegt. Dessen Einsatz zu
übertreiben schadet eher, als dass es hilft. Ein wichtiger Aspekt
ist die Wartbarkeit. Niemand will generierten Code warten.
Daher sollte dieser in jedem Build neu erzeugt und nicht in die
Versionsverwaltung eingecheckt werden.
[JavaPoetAnkündigung]
https://corner.squareup.com/2015/01/javapoet.html
[JavaPoetDokumentation]
https://github.com/square/javapoet/blob/master/README.md
[KabRup15a] H. M. Kabutz, S. Ruppert, Es werde Code! Code
statt dynamischer Proxies generieren, 2015,
https://jaxenter.de/es-werde-code-13426
[KabRup15b] H. M. Kabutz, S. Ruppert, Dynamic Proxies, entwickler.press, 2015
[Kabutz10] H. M. Kabutz, Generieren von Klassen – Teil 1 und
2, in: JavaSPEKTRUM, 03und 04/2010
[Kabutz10a] H. M. Kabutz, Generating Static Proxy Classes –
1/2, The Java Specialists' Newsletter, 19.2.2010,
http://www.javaspecialists.eu/archive/Issue180.html
[Kabutz10b] H. M. Kabutz, Generating Static Proxy Classes –
2/2, The Java Specialists' Newsletter, 1.3.2010,
http://www.javaspecialists.eu/archive/Issue181.html
[Müller14] B. Müller, Bytecode, Class-Loader und Class-Transformer, Folien zum Vortrag vom 14.8.2014,
http://www.jug-ostfalen.de/assets/wp/2014/08/jug-classloader.pdf
[OracleJavaProxy]
http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html
[Winterhalter14a] R. Winterhalter, How to make Java more
Dynamic with Runtime Code Generation, 1.7.2014,
http://zeroturnaround.com/rebellabs/how-to-make-java-more-dynamicwith-runtime-code-generation/
Links
[Winterhalter14b] R. Winterhalter, How my new Friend Byte
Buddy enables annotation-driven Java runtime code generation,
8.7.2014, http://zeroturnaround.com/rebellabs/how-my-new-friend-
[AnnotationProcessing]
byte-buddy-enables-annotation-driven-java-runtime-code-generation/
https://deors.wordpress.com/2011/10/31/annotation-generators/
[ASMIntro] D. R. Chawdhuri, Manipulating Java Class Files with
ASM 4 – Part One: Hello World!, 2012,
http://www.javacodegeeks.com/2012/02/manipulating-java-class-fileswith-asm.html
[ASMTutorial] E. Bruneton, ASM 4.0 A Java bytecode engineering library, 2007/2011,
http://download.forge.objectweb.org/asm/asm4-guide.pdf
[AspectJ] The AspectJTM Programming Guide,
https://www.eclipse.org/aspectj/doc/released/progguide/
[ByteBuddy] Tutorium, Security Library,
http://bytebuddy.net/#/tutorial
[CGLibTutorial] R. Winterhalter, cglib: The missing manual,
2013, https://github.com/cglib/cglib/wiki/Tutorial oder
Michael Hunger (Twitter @mesirii) interessiert
sich als IT-Consultant für alle Belange der Softwareentwicklung, vor allem Sprachen (Java.next, DSLs)
und Code-Qualität. Er arbeitet(e) an mehreren OpenSource-Projekten mit, ist Autor, Editor, Buch-Reviewer
und Sprecher bei Konferenzen.
E-Mail: [email protected]
http://www.javacodegeeks.com/2013/12/cglib-the-missing-manual.html
[Dari15] Th. Darimont, Beschleunigung dynamischen Codes mit
Bytecode-Generierung und Spring-Ausdrücken, in: JavaSPEKTRUM, 03/2015
58
JavaSPEKTRUM 6/2015
Herunterladen