美文网首页
JAVA动态代理

JAVA动态代理

作者: GrooveWind | 来源:发表于2017-03-14 21:49 被阅读0次

    从静态代理开始

    假设我们有个业务类,类的接口和实现是这么定义的:

    public interface Player {
        void play();
    }
    
    public PlayerImpl implements Player {
        @Override
        public void play(){
            System.out.println("playing...");
        }
    }
    

    如果我想在玩游戏之前和游戏结束之后,记录日志,可是不允许改动PlayerImpl的代码,该怎么办呢?
    这时候可以构造一个代理类来做这件事情:

    public PlayerProxy implements Player {
        private Player player;
        public PlayerProxy(){
            this.player = new PlayerImpl();
        }
        @Override
        public void play(){
            beforeLog();
            player.play();
            afterLog();
        }
    
        private void beforeLog(){
            System.out.println("init...");
        }
        private void afterLog(){
            System.out.println("destroy...");
        }
    }
    

    这就是静态代理,我们可以在不改动源码的情况下,通过代理对类功能进行扩展,很符合开闭原则吧:)

    动态代理

    静态代理的方式很直观,但是缺点也很明显——它只能针对特定类进行代理,扩展性很差,如果有一百个类要代理,难道要写一百个代理类吗?显然我们应该拥有更加通用和高效的解决方案,就是动态代理啦。

    JDK动态代理

    JDK动态代理,需要实现InvocationHandler接口;下面我们用JDK代理的方式重写上面的代理类:

    public PlayerProxyFactory implements InvocationHandler {
    
        private Object target;
    
        public PlayerProxyFactory(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before();
            Object result = method.invoke(target, args);
            after();
            return result;
        }
    
        public Object buildProxy(){
            return Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    this
            );
        }
        // 省略beforeLog和afterLog方法
    }
    

    实际使用:

    public static void main(String[] args) {
        Player player = new PlayerImpl();
        // 构造实现
        PlayerProxyFactory proxy = new PlayerProxyFactory(player);
        // 生成代理
        Player playerProxy = (Player) proxy.buildProxy();
        playerProxy.play();
    }
    

    可以看出来,动态代理可以通过接口自动构造代理类(其实就是一个工厂)。

    cglib动态代理

    JDK InvocationHandler虽好,却仍有一个缺陷,无法代理未实现接口的类。如果刚好有这样的需求,该怎么解决呢?这就得靠cglib了。
    cglib是一个在运行期动态生成字节码的工具。我承认我第一次看到这个介绍的时候被吓到了,不过撇开原理不说,使用起来与InvocationHandler非常类似:

    public class CGLibProxy implements MethodInterceptor {
        @Override
        public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            before();
            Object result = proxy.invokeSuper(target, args);
            after();
            return result;
        }
        public <T> T getProxy(Class<T> clazz) {
            return (T) Enhancer.create(clazz, this);
        }
        // 省略beforeLog和afterLog方法
    }
    

    实际使用:

    public static void main(String[] args) {
        Player playerProxy = new CGLibProxy().getProxy(PlayerImpl.class);
        playerProxy.play();
    }
    

    JAVASSIST

    Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

    Javassist是一个十分简单粗暴的工具,它的用法简单来看是这样的:

    public static void main(String[] args) throws Exception {  
        ClassPool pool = ClassPool.getDefault();  
        //创建Programmer类       
        CtClass cc= pool.makeClass("com.samples.Programmer");  
        //定义code方法  
        CtMethod method = CtNewMethod.make("public void code(){}", cc);  
        //插入方法代码  
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");  
        cc.addMethod(method);  
        //保存生成的字节码  
        cc.writeFile("f://myProxy");
    }
    

    这么灵活,写个代理还不是分分钟......

    JAVA动态代理比较

    JDK动态代理 CGLIB JAVASSIST
    实现原理 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理 利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理 通过修改字节码生成类来处理
    目标类是否需要实现接口

    相关文章

      网友评论

          本文标题:JAVA动态代理

          本文链接:https://www.haomeiwen.com/subject/tniigttx.html