betway必威-betway必威官方网站
做最好的网站

【必威官方网站】动态代理技术详解,从动态代

        就算对于Spring的基本思维Aop是依照动态代理和CGlib那一点很已经有所认知,可是什么是动态代理却不甚明了。为了对Spring加深精晓,小编以为好好学习一下java的动态代理是那些有不可缺少的。

代办形式是Java常用的设计情势,代理类通过调用被代理类的连带措施,并对相关措施实行抓牢。加入一些非业务性代码,举例专业、日志等操作。Spring 的 AOP 就是依赖代理方式的考虑贯彻的。

代理是一种设计情势,指标是对外提供统壹的接口(这样能够隐藏被代理类不想精通的不2法门),在此基础上,能够在代理类实现逻辑中参预一些外加操作,做一些提升管理,该办法不会潜移默化被代理类的原完成。

静态代理

        在读书动态代理在此以前本身先花一点时光理解一下静态代理,从静态代理出发精通代理到底是怎么3回事,以及询问静态代理的局限性,进而精晓怎么要向上及利用动态代理技巧。

       相信使用过Spring框架的同室都领会Spring利用Aop完成注脚式事务管理以及任何的代理巩固,约等于在点子实践前后加上部分诸如时间、日志、权限调整等。如果今后我们从比较复杂的Spring Aop中跳出来,那么,有哪些轻松的主意能够对大家的主意进行坚实吗?

在触发代理格局在此以前感到采纳代理很困苦,一个类的主意为啥不直接调用,还要经过扩展的代理类,其实,当您只有3个类要兑现部分扩展的遵从时,直接在那几个类里扩大方法是很简短,当你有几十一个,几百个类要加进同样功用时,一个三个的丰硕就可怜繁琐。使用代理,一是扩大效果很便宜,直接写在代理类里就足以了;2是下落了代码的耦合度,越发惠及调度和敬重,要调治时,只要调治代理类就足以了,别的类的代码都不用动。

代办分为静态代理和动态代理二种,静态代理正是在代理类内部装有被代理对象的引用,这种措施很直接,但供给hard coding,不易扩张,无法做联合处理。

此起彼落达成

       最简易的艺术正是继续,子类承接父类一碗水端平写父类的不二等秘书技,在重写的经过总就能够对原本的格局举办抓好。上面代码便是这种代理加强思想的实现。

package cn.proxy;

public class Tank implements Moveable{

    @Override
    public void move() {
        System.out.println("tank moveing...");        
    }

}

// son
package cn.proxy;

public class LogProxyTank extends Tank {

    public void move() {
        System.out.println("tank start...");
        super.move();
        System.out.println("tank stop...");
    }

}

       能够瞥见,通过重写父类方法能够非常便宜完毕代理巩固,可是就一个日记成效的代办加强,若是涉及到九16个类呢?那将要为100个类完成子类。那还不算麻烦,就算涉及到日志、时间、权限等多少个作用的压实的时候,先时间今天志和先日志后岁月可不是贰回事,那么快要分别完结三个代理的子类。想1想那中间涉嫌到广大增加的职能和重重被代理类时,就能导致代理类爆炸。分明这种办法是很不灵便的。

结缘代码看一下,Java 常用代理形式,以及中间的界别:

动态代理第3有jdk提供的Proxy、Cglib以及Javassist,在那之中Javassist重假诺一套字节码操作库,有二种选择方法,既能够有类似Cglib的用法,也得以依据字节码结构去动态生成类,本文首要描述Proxy和Cglib。

聚拢实现

       被代理类和代理类完毕同三个接口,同时期理类不再与被代理类存在继续关系,而是代理类富含贰个被代理类类型的分子变量。

package cn.proxy;

public class LogProxy implements Moveable {

    private Moveable m;

    public LogProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() throws Exception {
        System.out.println("moveable start...");
        m.move();
        System.out.println("moveable stop...");
    }

}

       能够看见,相较于继续格局,聚合的办法贯彻代理巩固,通过传播分歧的被代理类,能够完成对不相同的被代理举办加强,可是这种办法贯彻不一致的升高还是需求写不相同的代理类,灵活性上还不是很圆满。

静态代理

  • 接口
public interface Animal {
    public void eat();
}
  • 贯彻类(被代理的类)
public class Dog implements Animal{
    @Override
    public void eat() {
        System.out.println("狗吃肉骨头");
    }
}
  • 静态代理类
public class StaticProxy implements Animal{
  //被代理对象
    private Animal dog = new Dog();
    @Override
    public void eat() {
        System.out.println("StaticProxy执行前");
        dog.eat();//被代理对象的方法
        System.out.println("StaticProxy执行后");
    }
}
  • 测试
public class Test {
    public static void main(String[] args) {
        Animal dog = new StaticProxy();
        dog.eat();
    }
}   
  • 运转结果
StaticProxy执行前
狗吃肉骨头
StaticProxy执行后

动态代理在Spring、Mybatis、Dubbo等各大框架中均有大气运用,由此熟谙动态代理是读书那几个框架源码必须调整的根底之1

动态代理

       大家都了然,贰个类要通过编排、编写翻译、加载进JVM最后才干开始展览实例化。日常这一个工作是分开进行的。那么有未有相当的大概率将这么些经过封装到三个措施里面呢?

       答案是足以的。未来光景讲一下步骤:

                                一. 首先将能够将源码保存成字符串,并将巩固的代码加进去,然后经过Java IO才具将其写入到一个java文件中

                                二. 选择JavaCompiler举办编译生成.class的二进制文件

                                3. 用到叁个类加载器(这里运用U昂CoraLClassLoader)将二进制文件加载进内部存款和储蓄器

                                4. 实例化代理对象

        基本思路正是那样,现在要实现灵活的java动态代理,难点正是怎么动态的规定对哪些类实行代理,进行什么样的代理巩固。关键就在步骤一,源码字符串不可能写死,而应当动态变化。那么那几个措施的参数就必要有被代理对象和加强的逻辑。

       被代理类很轻易领会,正是要由此那么些被代理类知道要对怎么样方法举行代理加强,通过反射就足以获得被代理类非全数办法。还有二个主要的接口是InvocationHandler,那一个接口有二个措施invoke,在那几个点子中得以定义代理逻辑以及调用被代理类的实例对象的欲代理方法来变成至关心尊崇要的法子逻辑,因为代理类是不能够一呵而就被代理类的办法逻辑的。就好像歌手(被代理类)的商贩(代理类)能够支持歌唱家去接表演、布署行程,可是无法代表艺人去讴歌同样。

       为了进一步精晓Java动态代理,能够对JDK中的Proxy类实行叁个总结的模仿,代码如下:

package cn.proxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Proxy {

    public static Object newProxyInstance(Class interfacee, InvocationHandler handler) throws Exception {

        String rt = "rn";
        // 源码
        String methodStr = "";
        Method[] methods = interfacee.getMethods();
        for (Method m : methods) {
            methodStr  =
                    rt  
            "    @Override"   rt          
            "    public void "   m.getName()   "() throws Exception {"   rt  
            "        Method md = "   interfacee.getName()  ".class.getMethod(""   m.getName()   "");"   rt  
            "        handler.invoke(this, md);"   rt  
            "    }"   rt;
        }

        String src = 
        "package cn.proxy;"   rt   rt  

        "import java.lang.reflect.Method;"   rt  
        "import cn.proxy.InvocationHandler;"   rt   rt  

        "public class TankTimeProxy implements "    interfacee.getName()   "{"   rt  

        "    private InvocationHandler handler;"   rt   rt  

        "    public TankTimeProxy(InvocationHandler handler) {"   rt  
        "        super();"   rt  
        "        this.handler = handler;"   rt  
        "    }"   rt  

        methodStr  

        "}";

        // 生成一个java源码
        String fileName = System.getProperty("user.dir")   "/src/cn/proxy/TankTimeProxy.java";
        File file = new File(fileName);
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(src);
        fileWriter.flush();
        fileWriter.close();

        // 使用jdk编译api动态编译
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjects(fileName);
        CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
        task.call();
        fileManager.close();

        // 将二进制文件加载进入内存
        URL[] urls = new URL[] {new URL("file:/"   System.getProperty("user.dir")   "src")};
        URLClassLoader cl = new URLClassLoader(urls);
        Class<?> clazz = cl.loadClass("cn.proxy.TankTimeProxy");
        System.out.println(clazz);

        // 实例化新对象
        Constructor<?> constructor = clazz.getConstructor(InvocationHandler.class);
        Object m = constructor.newInstance(handler);

        return m;
    }
}

       仔细剖判一下上边的代码能够看出,基本思路便是地方所述的四步走。通过反射本领来动态深入分析传入的被代理类,获取欲代理的情势,而各种方法的有血有肉落到实处交由InvocationHandler的有血有肉落到实处类来实现,其invoke方法成功代理加强并调用被代理类的对应的被代理方法。

       为了对InvocationHandler有八个直观的问询,写五个简便的InvocationHandler接口以及贯彻类,具体代码如下:

package cn.proxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

    void invoke(Object o, Method m);

}

package cn.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

    // 被代理类
    private Tank t;

    public TimeHandler(Tank t) {
        this.t = t;
    }

    @Override
    public void invoke(Object o, Method m) {
        long start = System.currentTimeMillis();
        System.out.println("start time: "   start);
        try {
            m.invoke(t, new Object[]{});
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("end time: "   end);
    }

}

        InvocationHandler的兑现类中有3个要留意:要有三个被代理类类型的成员变量,要透过那个变量才具过调用被代理类的相应措施来成功重大的措施效果。通过 Proxy.newProxyInstance 创制的代理对象是在jvm运营时动态变化的二个对象,它并不是InvocationHandler类型,也不是概念的那接口的等级次序,而是在运作是动态变化的三个对象。

JDK 动态代理

接口和落到实处类同上

// JDK 动态代理,实现 InvocationHandler 接口
public class DynamicProxy implements InvocationHandler{
    private Object obj;//被代理对象

    //传入被代理对象
    public Object getProxy(Object objA){
        this.obj = objA;
        //创建代理对象,并关联被代理对象
        Object objProxy = Proxy.newProxyInstance(objA.getClass().getClassLoader(), 
                          objA.getClass().getInterfaces(), this);
        return objProxy;
    }
  //使用反射技术,协助调用被代理对象的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("DynamicProxy方法执行前");
        Object objResult = method.invoke(obj, args);
        System.out.println("DynamicProxy方法执行后");
        return objResult;
    }
}
  • 测试
public class Test {
    public static void main(String[] args) {
        //JDK 动态代理
        Animal dog = new Dog();
        DynamicProxy dp = new DynamicProxy();
        Animal dogProxy = (Animal) dp.getProxy(dog);
        dogProxy.eat();
    }
}
  • 运转结果
DynamicProxy方法执行前
狗吃肉骨头
DynamicProxy方法执行后

Proxy是依靠接口生成代理类,生成的代理类达成了被代理类的接口,Cglib是生成被代理类的子类,因而被代理类无法为final,而且必须有无参构造函数。

总结

        简单了然,代理情势就是在三个格局施行前后投入代理加强的代码。大家不断研究新的点子,便是要落到实处更加好的八面玲珑,能够对轻巧的被代理类完结自由的代办加强。动态代理本事通过传播被代理类(那就能够任性传入),反射分析那个类来获得欲代理的措施。通过传播InvocationHandler的贯彻类来落到实处格局的代办巩固,能够凭借传入的落到实处类的例外来促成自由的代办,而且那几个完毕类能够实现复用,就好像机械零件同样,自由组装达成不一致格局的代办组合。Java具体的代理类Proxy与本文中模仿的代理类有稍许两样,包罗能够流传方法参数完毕对有参方法的代办,代理类被命名称为$proxy一等等。但这个是细节难题,首要的商量依然一样的。

 

===========================================================================================================================

        本文只是小编当下的学习心得计算而成,内容可能不够深切,由于水平所限,不保障全数剧情科学,应接有同学在两道三科中指正,十分多谢!

        保障各种字的原创性!

        作为多少个工程师,作者所能做的即是每天都在腾飞,面前境遇才能保险壹颗忠贞不二,那是本人人生现阶段全部的追求。"Stay hungry, stay foolish"!

===========================================================================================================================

 

Cglib 动态代理

接口和落到实处类同上

//cglib 动态代理,实现 MethodInterceptor 接口
public class CglibProxy implements MethodInterceptor{
    //被代理的对象
    private Object obj;
    //传入目标对象
    public CglibProxy(Object objA){
        this.obj = objA;
    }
    public Object getProxy(){
        //创建目标对象的子对象,通过反射对父对象的内容拦截和处理
        Enhancer e = new Enhancer();
        e.setSuperclass(obj.getClass());
        e.setCallback(this);
        //返回代理对象
        return e.create();
    }

    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("CglibProxy方法执行前");
        Object result = methodProxy.invoke(obj, args);
        System.out.println("CglibProxy方法执行后");
        return result;
    }
}
  • 测试
public class Test {
    public static void main(String[] args) {
        //cglib代理
        Animal dog = new Dog();
        CglibProxy proxy = new CglibProxy(dog);
        Animal dogProxy = (Animal) proxy.getProxy();
        dogProxy.eat();
    }
}
  • 运行结果
CglibProxy方法执行前
狗吃肉骨头
CglibProxy方法执行后

Proxy须求写二个达成InvocationHandler(实现invoke方法)的代理类(以下称Agency),注意Agency不是最平生成的动态代理类,动态代理类是Proxy.newProxyInstance进度中变化的,newProxyInstance方法会为动态代理类newInstance一个实例对象,动态代理类的最首要思索是调用agency 对象(Agency类的实例)的invoke方法,在invoke方法中再经过反射调用被代理对象的法子,扩大附加管理。上代码:

3者的风味和区别

通过代码能够很显明的来看:

  1. 静态代理,要把被代理的对象写在类里面,只好管理2个类,实行功效高,代码的耦合度异常高,复用性很差。
  2. JDK 动态代理,是代理类要促成 InvocationHandler 接口,接口里面有个(method.invoke(对象,参数) 方法,它是选拔反射试行被代理对象的主意;Java 动态代理通过 Proxy.newProxyInstance() 方法动态的获得代理对象,那些法子有八个参数:(类加载器、接口,InvocationHandler 接口的子类实例);个中有个参数是接口,也正是说,Java 动态代理只可以代理实现了接口的类,被代理的类要是未有兑现任何接口,则不能兑现 JDK 动态代理。
  3. Cglib 动态代理,和 JDK 动态代理通过接口完成分歧, Cglib 动态代理通过持续完结,通过生成子类字节码,重写被代理类的艺术,在重写的不二等秘书技中提高功效;因为 Cglib 动态代理要承继被代理的类,所以,被 final 修饰的类或措施无法兑现 Cglib 动态代理。
interface Subject { void doSomething();}interface DupSubject { void doSomethingAgain();}class Real implements Subject, DupSubject { public void doSomething() { System.out.println("=========Real doSomething========="); } @Override public void doSomethingAgain() { System.out.println("=========Real doSomethingAgain========="); }}class Agency implements InvocationHandler { private Real sub; public Object bind { this.sub = sub; Object obj = Proxy.newProxyInstance(Test.class.getClassLoader(), sub.getClass().getInterfaces; return obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强处理 System.out.println("         Agency begin       "); method.invoke(sub, args); // 后置增强处理 System.out.println("         Agency end       "); return null; }}public class Test { public static void main(String[] args) { // 设置系统参数,输出动态生成的代理类 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Agency agency = new Agency(); Real real = new Real(); // 绑定被代理对象,返回代理对象 Object obj = agency.bind; Subject sub =  ; sub.doSomething(); DupSubject dubSub = (DupSubject) ; dubSub.doSomethingAgain(); }}

程序输出内容为:

         Agency begin       =========Real doSomething=========         Agency end                Agency begin       =========Real doSomethingAgain=========         Agency end       

上生成的动态代理类:

// 默认继承Proxy类且实现被代理类的所有接口final class $Proxy0 extends Proxy implements Subject, DupSubject { private static Method m1; private static Method m3; private static Method m2; private static Method m4; private static Method m0; // 将所有method对象在初始加载代理类时缓存下来,防止每次都通过反射获取 static { try { m3 = Class.forName("Subject").getMethod("doSomething", new Class[0]); m4 = Class.forName("DupSubject").getMethod("doSomethingAgain", new Class[0]); // 省略toString/equals/hashcode等方法 ... } catch (NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage; } catch (ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage; } } public $Proxy0(InvocationHandler invocationhandler) { // 向super类也就是proxy类传入InvocationHandler对象 super(invocationhandler); } // 直接调用了之前传入的InvocationHandler对象的invoke方法 public final void doSomething() { try { super.h.invoke(this, m3, null); return; } catch  { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } // 直接调用了之前传入的InvocationHandler对象的invoke方法 public final void doSomethingAgain() { try { super.h.invoke(this, m4, null); return; } catch  { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }}

动态代理类是哪些转变的切切实实参照ProxyGenerator的 generateClassFile方法,该措施先为代理类增加equals/hashcode/toString方法,然后扩展被代理类接口的具备办法,处理进度重要为经过反射获取被代理方法的主意名、参数类型、重临值类型、至极类型,然后生成依据上述参数生成代理方法的字节码内容(字节码实现多个事:即调用属性InvocationHandler对象的invoke方法),看那些主意能够即使领略class文件的咬合,即常量池、字段表、方法表等。

留意:proxy方法之间的里边调用是不走invoke逻辑的

上代码:

interface Subject { void doSomething();}interface AnotherSubject { void doSomethingAgain();}class Real implements Subject, AnotherSubject { @Override public void doSomething() { System.out.println("=========Real doSomething========="); } @Override public void doSomethingAgain() { System.out.println("=========Real doSomethingAgain begin===="); // 调用内部方法,注意invokeSuper时该调用是否会进入增强逻辑 this.doSomething(); System.out.println("=========Real doSomethingAgain end===="); }}class Agency implements MethodInterceptor { private Object target; // 持有被代理对象 public Agency(Object target) { this.target = target; } @Override // obj对象为动态代理类的实例 // method为被代理类的方法 // methodProxy持有被代理方法、代理方法、被代理类、代理类以及方法的index索引 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("         Agency begin       "); // 直接调用被代理对象的方法 method.invoke(target, args); // 调用methodProxy的invokeSuper方法,注意如果调用methodProxy的invoke方法 // 因为传入的obj为动态代理对象,则会陷入死循环,如果为被代理对象target,则不会 proxy.invokeSuper(obj, args); System.out.println("         Agency end       "); return null; }}public class Test { public static void main(String[] args) { // 设置系统参数,输出动态生成的代理类 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:/temp/dymamicCode"); Real real = new Real(); Agency agency = new Agency; Enhancer enhancer = new Enhancer(); // 设置被代理类 enhancer.setSuperclass(real.getClass; // 设置回调MethodInterceptor对象 enhancer.setCallback; Real proxy = enhancer.create(); proxy.doSomething(); proxy.doSomethingAgain(); }}

程序输出结果为:

本文由betway必威发布于网页设计,转载请注明出处:【必威官方网站】动态代理技术详解,从动态代

Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。