Avatar billede kernelx Juniormester
05. april 2013 - 18:42 Der er 7 kommentarer og
1 løsning

CGLIB eller ASM - add interceptor to instance at runtime

Hi,

jeg har en class:
-----------------
public class Foo {

    private String fooValue;

    // getters and setters

}

Nu har jeg en instance af denne class og invoker en methode:
-----------
Foo foo = new Foo(); // uden cglib enhancer
myInstanceTransformer.addMethodInterceptorToInstance(foo, new MethodInterceptor(){
    Object intercept( Object proxy, Method method, MethodProxy fastMethod, Object args[] )throws Throwable{
        System.out.println("foo bar abc");
    }
});
foo.getFoo(); // output: foo bar abc
-----------

Spørgsmål:
Er det muligt at forandre en class @ runtime i det man adder fields og forandrer methods - uden proxy?

Hvis ja: Hvordan kan jeg så ovenstående code til at virke med CGLIB (hvis det ikke er muligt med CGLIB, så: hvordan kan jeg løse det med ASM)?

Mange tak for svar!

Med venlig hilsen
KernelX
Avatar billede arne_v Ekspert
06. april 2013 - 05:34 #1
Du kan lave en proxy klasse som delegater de oprindelige metode og selv udfoerer de nye metoder.

Du kan lave en ny klasse med de gamle metoder og de nye metoder. Og hvis den gamle klasse ikke loades (i samme classloader) kan den nye endda have samme navn som den gamle.

Er det det sidste du vil?

Og hvis det skal give nogen mening saa skal de nye metode vel vaere i et kendt interface. Korrekt?
Avatar billede kernelx Juniormester
06. april 2013 - 12:46 #2
Ja, det er det sidste, som jeg vil.

Jeg har en class:
-----------------
@Entity
public class FooEntity {

    @Id
    private Long id;

    @Column
    private String fooBar;

    // getter og setter

}
-----------------

Nu vil jeg @ runtime lave en ny class som hedder det samme (FooEntity) og som har samme fields og methoder som udfører original coden plus ting som jeg har tilføjet i form af en interceptor.

Hvis jeg så laver
new FooEntity()
så skal classloaderen bruges som kender den nye class.

Jeg vil forså og selv være i stand til at lave det som en JPA2 provider laver.

Hvis jeg debugger en entity efter at jeg har lavet new FooEntity() så hat denne instance extra fields.
Avatar billede arne_v Ekspert
08. april 2013 - 00:28 #3
Mit forslag: javassist
Avatar billede arne_v Ekspert
08. april 2013 - 00:30 #4
Demo:


package hack;

public class FooEntity {
    private Long id;
    private String fooBar;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getFooBar() {
        return fooBar;
    }
    public void setFooBar(String fooBar) {
        this.fooBar = fooBar;
    }
}



package hack;

public interface FooHack {
    public void print();
}



package hack;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class ClassHack {
    public static void main(String[] args) throws NotFoundException, CannotCompileException {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("hack.FooEntity");
        cc.addInterface(pool.get("hack.FooHack"));
        cc.addMethod(CtNewMethod.make("public void print() { System.out.println(id + \" \" + fooBar); }", cc));
        cc.toClass();
        FooEntity o = new FooEntity();
        o.setId(1L);
        o.setFooBar("ABC");
        FooHack o2 = (FooHack)o;
        o2.print();
    }
}


Interfacet er kun for at kunne kalde print uden brug af reflection.
Avatar billede arne_v Ekspert
08. april 2013 - 00:41 #5
Du kan ogsaa tilfoeje fields og tilfoeje funktionalitet til eksisterende metoder.


package hack;

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

public class ClassHack2 {
    public static void main(String[] args) throws NotFoundException, CannotCompileException {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("hack.FooEntity");
        cc.addField(new CtField(CtClass.doubleType, "x", cc));
        cc.addInterface(pool.get("hack.FooHack"));
        cc.addMethod(CtNewMethod.make("public void print() { System.out.println(id + \" \" + fooBar + \" \" + x); }", cc));
        cc.getDeclaredMethod("setFooBar").insertBefore("x += 123.456;");
        cc.toClass();
        FooEntity o = new FooEntity();
        o.setId(1L);
        o.setFooBar("ABC");
        o.setFooBar("DEF");
        FooHack o2 = (FooHack)o;
        o2.print();
    }
}
Avatar billede kernelx Juniormester
17. april 2013 - 20:56 #6
Jeg bruger maven til at manage mine projekter. Er det bedre at lave det @runtime, eller er det bedre at skrive et maven plugin som ændrer en eksisterene klasse?

Mange tak! Husk at skrive et eller andet som svar!
Avatar billede arne_v Ekspert
18. april 2013 - 02:28 #7
Jeg ved ikke om runtime eller compile time modifikation passer bedst med din kontekst.

Koden ovenfor viser hvordan det kan goeres runtime.

Hvis det skulle goeres compile time ville jeg kigge paa AspectJ. Jeg er sikker paa at der er AspectJ support til Maven.
Avatar billede arne_v Ekspert
18. april 2013 - 02:28 #8
svar
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

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