JAVA动态代理的理解

在微博中转了一篇文章:http://java.chinaitlab.com/base/926676.html,觉得意犹未尽,于是操刀写一个自己对Java动态代理的理解,和同行和同好交流。

为什么使用动态代理?

代理模式的意义在于控制(限制)对被代理对象的访问,常见的使用场合是日志、过滤、事务逻辑的拦截处理等。这样不需要修改真实对象的任何代码,就可以在调用真实对象前和真实对象后,通过在代理中增加自己的处理逻辑,比如权限控制、统计、过滤代码等。

一个常见的代理模式的类图如下所示:

proxy

可见,代理是作为客户端和真实对象的一个桥梁存在的,代理就是真实对象的一个影子,这就要求代理要实现真实对象的所有公开方法(或者接口)。如果是静态代理,这个工作量可是不小的,而且想象一下,如果真实对象的接口发生了变化会怎样?代理必须做相应的修改!但是,如果代理类获知或者被告知真实对象的类型、接口列表,代理可以根据反射构造出真实对象来,进而调用真实对象的相应方法,这就是动态代理:代理类在定义时不需要知道具体要代理哪个真实对象,我们在运行时告诉代理类这些好了。

这里的关键是,根据Java的反射机制,如果我们知道了一个对象的类型,就可以知道这个对象的classLoader,进而构造出这个对象,然后根据这个对象的接口列表,就可以调用这个对象的相应方法。因此,当使用动态代理时,需要告知动态代理我们要调用什么类型的对象一个这个对象的接口列表。

可见,动态代理的最佳使用场合是:需要对真实对象中的方法进行统一的处理时,比如需要统计每个方法的执行时间,打印出每个方法的执行日志等。

Java提供的动态代理使用方法

我们考虑一个日志的例子:在执行每个方法时,都打印出这个方法的名称。

首先定义一个接口(Java目前仅支持基于接口的动态代理):

 

public interface Subject {
    public void doSomething1();
    public void doSomething2();
}

实现这个接口的真实对象类:

public class RealSubject implements Subject{

    @Override
    public void doSomething1() {
        System.out.println("RealSubject doSomething1");
    }

    @Override
    public void doSomething2() {
        System.out.println("RealSubject doSomething2");
    }

}

下面就是核心的代理类了:

public class LogProxy implements InvocationHandler{
    private Object subject = null;

    public LogProxy(Object object){
        this.subject = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before call method:" + method.getName());
        Object obj = method.invoke(this.subject, args);
        System.out.println("after call method:" + method.getName());
        return obj;
    }

}

客户类:

public class Client {

    public static void main(String[] vargs) {
        Subject subject = (Subject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(), 
                RealSubject.class.getInterfaces(), 
                new LogProxy(new RealSubject()));

        subject.doSomething1();
        subject.doSomething2();
    }
}

在上面的代码中,我们看到如果每次使用Subject对象的时候都使用Proxy.newProxyInstance来获得一个Subject的代理,代码会显得臃肿,可以定义一个SubjectFactory类:

public class SubjectFactory {
    public static Subject getInstance(){
        return (Subject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(), 
                RealSubject.class.getInterfaces(), 
                new LogProxy(new RealSubject()));        
    }

}

然后Client就简化为:

public class Client {

    public static void main(String[] vargs) {
        Subject subject = SubjectFactory.getInstance();

        subject.doSomething1();
        subject.doSomething2();
    }
}

是不是优雅了很多?

上面代码的执行结果如下:

before call method:doSomething1
RealSubject doSomething1
after call method:doSomething1
before call method:doSomething2
RealSubject doSomething2
after call method:doSomething2

 

需要注意到的是,这里的LogProxy是可以代理任何类型的对象的,不仅仅限于Subject类型的对象。比如有一个OtherSubject接口及其实现:

public interface OtherSubject {
    public void play();
}

public class RealOtherSubject implements OtherSubject{

    @Override
    public void play() {
        System.out.println("OtherRealSubject play");
    }
    
}

public class OtherSubjectFactory {
    public static OtherSubject getInstance(){
        return (OtherSubject) Proxy.newProxyInstance(
                RealOtherSubject.class.getClassLoader(), 
                RealOtherSubject.class.getInterfaces(), 
                new LogProxy(new RealOtherSubject()));             
    }
    
}

相应的,Client如下:

public class Client {

    public static void main(String[] vargs) {
        Subject subject = SubjectFactory.getInstance();
        OtherSubject otherSubject = OtherSubjectFactory.getInstance();
        
        subject.doSomething1();
        subject.doSomething2();
        otherSubject.play();
    }
}

执行结果如下:

before call method:doSomething1
RealSubject doSomething1
after call method:doSomething1
before call method:doSomething2
RealSubject doSomething2
after call method:doSomething2
before call method:play
OtherRealSubject play
after call method:play

 

动态代理和延迟加载

动态代理的一个重要使用场合是实现延迟加载,即所谓的“虚代理”:首先使用代理获得一个对象的“缩影”(即只保留被代理对象的部分属性),只有在必要时再根据这个缩影获取全部被代理对象的属性。这样做的目的往往是为了提高性能,比如Hibernate中的延迟加载就是很好的例子。(例子待补充)

小结

有了动态代理,基本上可以忘记静态代理了:何必舍简求繁呢?

动态代理机制中,如何根据给定的对象类型和接口列表创建相应的代理类,可参考下面的参考资料的深度分析。

参考资料:

  • 权威阐述:http://docs.oracle.com/javase/7/docs/technotes/guides/reflection/proxy.html
  • Java动态代理机制分析及扩展:http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/
  • Java动态代理深度学习:http://hi.baidu.com/malecu/item/9e0edc115cb597a1feded5a0

你可能感兴趣的:(JAVA动态代理的理解)