Hibernate底层技术简介 拦截方法

    技术2022-05-11  129

    (1)利用反射机制

    下面有类Monkey,接口Carrier,Monkey类没有实现Carrier接口,自然也就没有对应的transport()和getNum()方法。

    package  com.weportal.asm; public   class  Monkey  {}

     

    package  com.weportal.asm; public   interface  Carrier  {    public void transport();    public int getNum();}

    下面的类MonkeyProxy通过反射技术的Proxy代码模式,完成了Mokey对Carrier接口的动态实现。

    package  com.weportal.cglib; import  java.lang.reflect.Method; import  net.sf.cglib.proxy.InvocationHandler; import  net.sf.cglib.proxy.Proxy; public   class  MonkeyProxy  implements  InvocationHandler  {    private int proxyNum = 0;    public Object invoke(Object proxy, Method method, Object[] args)            throws Exception {        if (method.getName().equalsIgnoreCase("transport")) {            proxyNum++;        }        if (method.getName().equalsIgnoreCase("getNum")) {            return new Integer(proxyNum);        }        return null;    }    public void test() {        ReflectCarrier cr = (ReflectCarrier) Proxy.newProxyInstance(this                .getClass().getClassLoader(),                new Class[] { ReflectCarrier.class }this);        cr.transport();        cr.transport();        System.out.println("由Monkey传送了" + cr.getNum() + "个货物");    }    public static void main(String[] args) {        MonkeyProxy mp = new MonkeyProxy();        mp.test();    }}

    程序运行结果如下:

    由Monkey传送了2个货物

    从上面的代码可以看出,方法invoke(Object proxy, Method method, Object[] args)拦截了Carrier接口方法的调用,修改或者插入了自己的代码。在这个过程中需要接口的帮助,使proxy代理有针对性的实现对象的转换,生成一个代理接口的Proxy对象。

    (2)和ASM技术相比,反射技术的Proxy并没有改变类的字节码,而ASM可以直接操作字节码,使得ASM的代码可以直接修改Class类,通过字节码拦截Class的方法,现举例如下。

    Work代表一个工人,不断的生产零件。

    package  com.weportal.asm; public   class  Worker  {    //public int num;    public void produce(){        System.out.println("Worker生产了1个零件!");        //num++;    }}

    方法produce()并没有记录生产的零件个数,下面使用字节码的方式来增强这一方法。

    package  com.weportal.asm; import  org.objectweb.asm.Attribute; import  org.objectweb.asm.ClassAdapter; import  org.objectweb.asm.ClassVisitor; import  org.objectweb.asm.CodeVisitor; import  org.objectweb.asm.Constants; import  org.objectweb.asm.Type; public   class  WorkerClassVisitor  extends  ClassAdapter  implements  Constants  {    /**     * @param ClassVisitor cv     */    public WorkerClassVisitor(ClassVisitor cv) {        super(cv);    }    private static final String WORKER = Type.getType(Worker.class)            .getInternalName();    private String className;    /* (non-Javadoc)     * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String[], org.objectweb.asm.Attribute)     */    public CodeVisitor visitMethod(int access, String name, String desc,            String[] exceptions, Attribute attrs) {        CodeVisitor cd = cv.visitMethod(access, name, desc, exceptions, attrs);        if ("produce".equals(name))            return new WorkerCodeVisitor(cd, className);        return cd;    }    public void visit(int version, int access, String name, String superName,            String[] interfaces, String sourceFile) {        this.className = name;        cv.visitField(ACC_PUBLIC, "num""I"nullnull);        super.visit(version, access, name, superName, interfaces, sourceFile);    }}

    在visitMethod()中,将CodeVisitor指向了子类WorkerCodeVisitor。

    package  com.weportal.asm; import  org.objectweb.asm.CodeAdapter; import  org.objectweb.asm.CodeVisitor; import  org.objectweb.asm.Constants; public   class  WorkerCodeVisitor  extends  CodeAdapter  implements  Constants {    /**     * @param CodeVisitor cv     */    public WorkerCodeVisitor(CodeVisitor cv) {        super(cv);    }    private String className;    /**     * @param cd     * @param className     */    public WorkerCodeVisitor(CodeVisitor cv, String className) {        super(cv);        this.className = className;            }    public void visitInsn( int opcode) {        if( opcode==RETURN) {          //ALOAD 0: this          //DUP          //GETFIELD Worker.num: int          //ICONST_1          //IADD          //PUTFIELD Worker.num: int                      cv.visitVarInsn(ALOAD,0);          cv.visitInsn(DUP);          cv.visitFieldInsn(GETFIELD, className,              "num""I");          cv.visitInsn(ICONST_1);          cv.visitInsn(IADD);          cv.visitFieldInsn(PUTFIELD, className,                  "num""I");        }        cv.visitInsn(opcode);     }}

    visitInsn()方法拦截了方法字节码的最后一行,即Return指令。在return之前,添加了注释中的指令。注意插入的指令实际上是num++;的字节码,这一点可以把Worker类中的注释去掉,使用byteCode view在Eclipse中即可看到。下面是测试代码。

    package  com.weportal.asm; import  java.lang.reflect.Field; import  java.lang.reflect.Method; public   class  WorkerReflectTest  {    public static String CLASSNAME = "com.weportal.asm.Worker";    public static void main(String[] args) throws Exception {        WorkerReflectTest wt = new WorkerReflectTest();        Class cc = wt.loadClass(CLASSNAME);        Object obj = cc.newInstance();        Method produce = obj.getClass().getMethod("produce"null);        produce.invoke(obj, null);        produce.invoke(obj, null);        produce.invoke(obj, null);        Field fd = obj.getClass().getField("num");        System.out.println("Worker已经生产了" + fd.getInt(obj) + "个零件!");    }    private Class loadClass(String className) throws ClassNotFoundException {        ClassLoader cl = new VisitorClassLoader(getClass().getClassLoader(),                className);        return cl.loadClass(className);    }}

    程序运行结果如下:

    Worker生产了1个零件!Worker生产了1个零件!Worker生产了1个零件!Worker已经生产了3个零件!

    WorkerReflectTest中使用的VisitorClassLoader是针对这个demo设计的ClassLoader。

    参考《精通Hibernate》刘洋 著


    最新回复(0)