手机版
你好,游客 登录 注册
背景:
阅读新闻

Java 动态代理介绍及用法

[日期:2016-12-11] 来源:Linux社区  作者:nvd11 [字体: ]

下面对上面的代码进行分析。

首先这个代理类要实现接口InvacationHandler, 而且不会直接提供要代理的方法(行为), 而是生成1个被代理完成(强化)的新对象。

delegate 成员对象

    //delegate means proxy
    //the object which will be delegated
    private Object delegate;

这个成员变量就是为了存放被代理的对象(Fox or wolf 对象)

bind(Object delegate) 方法

public Object bind(Object delegate){
        this.delegate = delegate;

        /**
         * This method newProxyInstance return ***one of the interfaces*** of delegate object(properties object of this class)
         * 
         * @param 
         *      1.ClassLoader loader -> usually use delegate object's class loader
         *      2.Class<?>[] interfaces -> collection of the interface which delegate object has implemented
         *      3.InvocationHandler h -> this
         * @return
         */
        return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), 
                                      delegate.getClass().getInterfaces(), this);

    }

这个bind方法就是为了绑定要代理的对象, 并return 1个被代理后的对象啦

注意 参数Object delegate就是本文例子中的狼or狐狸对象, 因为参数类型是Object, 也就是说它可以同时适用于各种类(前提是实现了某种接口)

Proxy.newProxyInstance 这个静态方法就是返回1个被代理后的对象。

第一个参数是类加载器, 通常使用被代理对象的类加载器, 通常我们就用delegate.getClass().getClassLoader()

第二个参数是被代理对象实现的接口列表, 通常我们就用delegate.getClass().getInterface()

第三个参数是这个代理类对象本身

值得注意的是, 这个方法返回的也是1个Object类型, 在client端中, 我们可以将返回对象强制转换成被代理类的1个接口类型对象, 但是不能强制转换成被代理类本身的1个对象。

至于这个静态方法里面的细节这里就不讲了。

重写方法invoke

    /**
     * This method will replace all the method owned by delegate object.
     * @param proxy -> the delegate object, never use it's method directly, otherwise will lead to Dead loop
     * @param method -> the method (once execute will fall into this invoke method) object of delegate object 
     * @param args  -> parameters of the mothod.
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result;

        if (args.length < 1){
            return method.invoke(this.delegate,args);
        }

        //bear watching
        System.out.println("bear is watching " + args[0].toString());
        result = method.invoke(this.delegate,args);
        System.out.println("bear leaved " + args[0].toString());
        return result;
    }
}

这个方法就是真正实现代理功能的关键,
它有三个参数

Object proxy对象代表被代理的对象

Method method代表要被代理的方法(实际上是所有方法)

args 就是代表方法的参数列表了。

返回值Object result 自然就是代表被代理方法的返回值了。

我们在里面先加入 熊看着狗的代码…

然后执行被代理的方法本身

然后加入熊离开的代码。

最后要返回被代理方法的返回值。

而且, 千万不要直接调用proxy的方法, 因为proxy直接调用方法会再次进入这个代理对象invoke方法, 导致死循环。

Client端代码

public static void g(){
        Dog g = new Dog("InterDogChicken");
        dynamicProxyBear bear = new dynamicProxyBear();

        Hitable<Dog> newFox = (Hitable<Dog>) bear.bind(new Fox());
        newFox.hit(g);

        Molestable<Dog> newWolf = (Molestable<Dog>) bear.bind(new Wolf());
        newWolf.molest(g);
    }

上面代码中 首先要定义1个动态代理(熊)类, 然后熊返回1个新的狐狸对象(必须强制转换为Fox or Wolf类的1个接口, 而不能是本身)
然后再执行这个新的狐狸对象的方法。

输出结果

bear is watching Dog [name=InterDogChicken]
give InterDogChicken a Sap!
give InterDogChicken a Uppercute!
bear leaved Dog [name=InterDogChicken]
bear is watching Dog [name=InterDogChicken]
wolf laugh at the dog!
wolf ruffle the dog!
bear leaved Dog [name=InterDogChicken]

这样的话无论狐狸or狼都被代理了

四,动态代理的关键点和小结

值得注意是, 上面重写的invoke的方法, 我并没有提到Fox or 狼类的方法名字。

实际上, 动态代理会利用Java反射机制拦截被代理对象的所有方法。 接管对方法里管理。

所以动态代理是非常适合增加日志监控功能的。

例如在每个方法执行前和执行后都加日志的话, 动态代理模式避免了去修改以前的业务类。

被代理对象必须至少实现1个接口才能被动态代理对象代理, 因为动态代理bind方法返回的对象必须强制转换成1个接口引用。


而且,动态代理只对被代理对象的第一层入口方法有效果。

例如下面这个对象有3个方法

void a(){}

void b(){}

void c(){
  a();
  b();
}

那么无论动态代理对象执行 a b c那个方法, 都是被拦截一次, 而不是当执行c时, c a b分别被拦截3次

五, 另1个例子, 利用动态方法实现监控功能

动态代理有时也会被利用于接管被代理对象的方法, 实现监控功能

例如下面要实现1个学生类, 它一次执行study/exam/graduate 三个方法,

学习接口:

public interface Studyable {
    public void study();
    public int exam();
    public void graduate();
}

学生类

package proxyPattern.dynamicProxy;

public class Student implements Studyable {

    private String name;
    private int examResult;

    public Student(String name, int examResult) {
        super();
        this.name = name;
        this.examResult = examResult;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", examResult=" + examResult + "]";
    }

    @Override
    public void study() {
        System.out.println(this + " is studying");
    }

    @Override
    public int exam() {
        System.out.println(this + " is examming");
        return this.examResult;
    }

    @Override
    public void graduate() {
        System.out.println(this + " graduated");
    }
}

但是我们加入一个要求, 就是当某个学生对象exam分数少于60时, graduate执行失败

有人说直接修改学生类最快, 但是项目中很多情况下你没有修改上层类的权限, 这个时候我们可以利用动态代理来实现:

动态代理类 StudentMonitor

package proxyPattern.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class StudentMonitor implements InvocationHandler{
    private Object delegate;

    private boolean flag = false;

    public Object bind(Object delegate){
        this.delegate = delegate;
        return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), 
                                      delegate.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        System.out.println("start to execute " + method.getName());
        Object result;

        if(method.getName().equals("exam")){
            result = method.invoke(this.delegate, args);
            if ((Integer)result >= 60){
                this.flag = true;
            }else{
                this.flag = false;
            }
            return result;
        }

        if(method.getName().equals("graduate")){
            if(this.flag == true){
                return method.invoke(this.delegate, args);
            }

            System.out.println(this.delegate + " exam result < 60,cannot graduate");
            return null;
        }

        return method.invoke(this.delegate, args);
    }


}

在重写的invoke方法中, 我们可以加入方法名字判断实现我们的需求

client代码

public static void h(){
        StudentMonitor sm = new StudentMonitor();

        //must be translated to a interface object of the class, cannot be translate to a class object
        Studyable st = (Studyable)sm.bind(new Student("Mike", 59));
        st.study();
        st.exam();
        st.graduate();

        Studyable st2 = (Studyable)sm.bind(new Student("bill", 61));
        st2.study();
        st2.exam();
        st2.graduate();
    }

输出

start to execute study
Student [name=Mike, examResult=59] is studying
start to execute exam
Student [name=Mike, examResult=59] is examming
start to execute graduate
Student [name=Mike, examResult=59] exam result < 60,cannot graduate
start to execute study
Student [name=bill, examResult=61] is studying
start to execute exam
Student [name=bill, examResult=61] is examming
start to execute graduate
Student [name=bill, examResult=61] graduated

本文永久更新链接地址http://www.linuxidc.com/Linux/2016-12/138192.htm

linux
相关资讯       Java动态代理 
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款