【转】Java动态代理机制

作者: LiHui 分类: Java 发布时间: 2019-01-06 23:54

别人用心写的,有兴趣可访问原作者,转自:https://www.cnblogs.com/xiaoluo501395377/p/3383130.html,这篇挺详细的,学习学习

在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。

在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:

InvocationHandler:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。

接下来我们来看看Proxy这个类:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:

复制代码
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
复制代码

好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

首先我们定义了一个Subject类型的接口,为其声明了两个方法:

public interface Subject
{
    public void rent();
        public void hello(String str);
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:

复制代码
public class RealSubject implements Subject
{
    @Override
    public void rent()
    {
        System.out.println("I want to rent my house");
    }
        @Override
    public void hello(String str)
    {
        System.out.println("hello: " + str);
    }
}
复制代码

下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

复制代码
public class DynamicProxy implements InvocationHandler
{
    // 这个就是我们要代理的真实对象
    private Object subject;
        //    构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject)
    {
        this.subject = subject;
    }
        @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //  在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
                System.out.println("Method:" + method);
                //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
                //  在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
                return null;
    }

}
复制代码

最后,来看看我们的Client类:

复制代码
public class Client
{
    public static void main(String[] args)
    {
        //    我们要代理的真实对象
        Subject realSubject = new RealSubject();

        //    我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);
                System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}
复制代码

我们先来看看控制台的输出:

复制代码
$Proxy0
before rent house Method:
public abstract void com.xiaoluo.dynamicproxy.Subject.rent() I want to rent my house after rent house
before rent house Method:
public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String) hello: world after rent house
复制代码

我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号

接着我们来看看这两句 

subject.rent();
subject.hello(“world”);

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:

复制代码
public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //  在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
                System.out.println("Method:" + method);
                //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
                //  在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
                return null;
    }
复制代码

我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:

public abstract void com.xiaoluo.dynamicproxy.Subject.rent()

public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)

正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

这就是我们的java动态代理机制

 

本篇随笔详细的讲解了java中的动态代理机制,这个知识点非常非常的重要,包括我们Spring的AOP其就是通过动态代理的机制实现的,所以我们必须要好好的理解动态代理的机制。

 

评论也挺有收获的

#1楼 2013-10-23 17:19 Alexia(minmin)  
Java中动态代理的适用场景是什么?有啥作用?平时一般不怎么用吧
  

#2楼[楼主2013-10-23 19:23 xiaoluo501395377  

@ Alexia(minmin)
确实,可能我们在平常开发时会很少用到动态代理机制,但是像Spring框架的AOP其就是基于动态代理机制实现的,就像反射一样,我们很少去实际使用,但是框架基本上都是通过反射来实现的
  

#3楼 2014-09-06 20:37 cityflickr  

因为AOP所以要了解动态代理
  

#4楼 2014-10-18 23:01 空杯椰子  

client类中,newProxyInstance(handler.getClass().getClassLoader(),realSubject.getClass().getInterfaces(), handler);第一个参数应该是realSubject.getClass().getClassLoader()不?
  

#5楼 2014-10-20 17:44 ivywenyuan  

@ 空杯椰子
我也觉得,看thinking in java上的例子,就应该是realSubject.getClass().getClassLoader(),而且API上面写的是此参数“ 定义代理类的类加载器”
  

#6楼 2014-10-20 17:47 ivywenyuan  

@ xiaoluo501395377 楼主,请问newProxyInstance(handler.getClass().getClassLoader(),realSubject.getClass().getInterfaces(), handler);中第一个参数是不是应该是realSubject.getClass().getClassLoader()???
Thinking in Java中的例子中newProxyInstance的第一个参数就是要代理的真是对象的类加载器,而且API中写的也是此参数“定义代理类的类加载器”
  

#7楼 2015-01-17 02:06 青年的昆特斯  

您好,有个地方没看懂:
这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行
为什么会跳转到invoke方法?
我java学的不好,这里没看懂,请赐教
  

#8楼 2015-07-24 18:08 awarrior  

@ Ressmix
就是被代理的对象
proxy – the proxy instance that the method was invoked on
  

#9楼 2015-11-20 15:05 奔跑的小黑  

我猜想是 Proxy.newProxyInstance这个方法创建的新类,这个类实现了我们传入的接口,并在这些实现方法中调用handler中的invoke方法(当然也要传入对应的参数)。所以面向切面设计就只用在invoke里面做操作了
  

#10楼 2015-11-21 14:56 将coding进行到底  

@ awarrior
你打印一下就知道是哪个了
  

#11楼 2016-01-28 19:13 幽香染轻弦  

写的挺好,终于在这看懂了
  

#12楼 2016-02-05 11:31 awful  

@ ivywenyuan
这个问题你应该早就解决了,去看一看jvm的工作机制会更好的明白这一点,我怕自己讲不明白误了你.
  

#13楼 2016-04-16 16:54 xfma  

@ ivywenyuan
所有类的加载器都是一样的。没多大关系吧
  

#14楼 2016-06-21 21:15 我的名字最好听  

初学Java感觉这里好难理解啊
  

#15楼 2016-09-28 16:31 凌渡冰  

proxy:这个是代理对象不是真实对象
  

#16楼 2016-09-28 17:08 凌渡冰  

@ 青年的昆特斯
其实生成的代理类 内置了这个 invocationHandler
  

#17楼 2016-10-03 16:20 江湖霸主  

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

———————————————
proxy指的不是所代理的那个真实对象。如果是这样的话就没必要在handler中保存真实对象的引用了,,

在invoke中
System.out.println(“proxy.getClass():” + proxy.getClass());
输出: proxy.getClass():class com.sun.proxy.$Proxy0
  

#18楼 2017-01-08 22:28 WeaponLin  

写的太棒了。
  

#19楼 2017-01-24 15:32 jinweijie0527  

写的不错。
  

#20楼 2017-03-11 21:45 苹果环境  

很好啊,学习了
  

#21楼 2017-03-18 10:59 雪诺Snow  

讲的不错,谢谢!
  

#22楼 2017-04-10 10:17 Gonjian  

博主写得很好,赞一个! 有个小问题,希望博主改一下,我看上面评论也说了,


Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

这里的proxy是代理对象,proxy – the proxy instance that the method was invoked on 这句话可能博主理解有一点偏差。而且我们可以在invoke方法中用proxy这个参数电泳一下方法,会发现是递归自身调用,会发生StackOverFlow。
  

#23楼 2017-05-17 15:08 王小军军军  

蛮好的,看评论还发现了两个错误点,看来大家都很认真的在看
  

#24楼 2017-05-18 16:30 请叫我老明  

看了两遍,虽然没有完全看懂,但还是默默进来点个赞!!
  

#25楼 2017-08-30 19:34 小兵乙  

博主写得很好,赞一个!
照着楼主的示例敲了一遍,感觉对代理有点收获的同时,又产生了一个新的疑问:如何通过代理对象访问到被代理的真实对象?
请楼主指点~
  

#26楼 2017-09-05 10:03 bian19556  

博主写得很好,土木二三事,java动态代理在哪些应用场景中运用?如果设计合理能不能不用动态代理?
  

#27楼 2017-09-22 10:23 又是元气满满的一天  

网上找了这么多,终于在你这里看懂了!太感谢了!
  

#28楼 2017-09-22 15:01 kwpkwp  

不理解。。
  

#29楼 2017-11-28 10:36 像梦一样自由  

博主写的很好,评论的人也很认真 棒
  

#30楼 2017-11-29 11:29 echoranges  

上面纠结用哪个类加载器的,直接打印下不就知道了

System.out.println(“proxy.getClass().getClassLoader(): “+proxy.getClass().getClassLoader());
System.out.println(“realSub.getClass().getClassLoader(): “+realSub.getClass().getClassLoader());

结果:
proxy.getClass().getClassLoader(): sun.misc.Launcher$AppClassLoader@1471cb25
realSub.getClass().getClassLoader(): sun.misc.Launcher$AppClassLoader@1471cb25

这两个的类加载器都是系统类加载器吧,应该一样的,所以用哪个都可以吧

另外,一共就只有四种加载器,大家可以了解一下他们的加载机制
启动类加载器
扩展类加载器
系统类加载器
用户自定义类加载器
基本上我们能接触到的就只是系统类加载器
  

#31楼 2017-12-22 09:35 王凯的影迷朋友  

博主好棒!点赞!
  

#32楼 2018-02-24 11:03 noodleprince  

个人感觉博主有一个地方的描述不是很准确:
“InvocationHandler is the interface implemented by the invocation handler of a proxy instance. ”
这话的意思应该不是“每一个动态代理类都必须要实现InvocationHandler这个接口”,而应该是“每一个代理实例的invocation handler都要实现InvocationHandler这个接口”。
因此,举例的public class DynamicProxy implements InvocationHandler中的DynamicProxy ,其实并不能称作一个动态代理类,更准确的应该叫做一个具体的invocation handler,该invocation handler会被关联到具体的动态代理类(比如文中的$Proxy0)的实例。

在其他博客资料看到另一种实现方式,个人感觉更容易理解这个实现了InvocationHandler接口的是什么东西,当然博主的是拆分的比较细,利于初学者理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ProxyFactory{
 
    //维护一个目标对象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }
 
   //给目标对象生成代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("开始事务2");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事务2");
                        return returnValue;
                    }
                }
        );
    }
 
}


这段代码里面使用的是匿名内部类来定义具体的InvocationHandler的,这里的new InvocationHandler()其实就和博主举例的public class DynamicProxy implements InvocationHandler是一个意思。因此我认为DynamicProxy 称作一个动态代理类并不准确。

  

#33楼 2018-03-29 09:53 目标奔雷手  

这个方法最好还是返回结果,要不又返回结果的会有问题,拿不到返回的数据!
public Object invoke(Object object, Method method, Object[] args)
throws Throwable;

Object result = method.invoke(subject, args);

return result;
  

#34楼 2018-04-19 10:51 苍狼小跟班  

给我很大的启发啊,Spring中的cglib代理原来就是基于这种思想的。我公司里面的项目经理写的AOP代码,用的是注解方式,原理依旧代理模式。mark一下,准备今天也自己做一个能够动态代理的类。
  

#35楼 2018-07-31 14:40 大脸喵  

@ 空杯椰子
双亲委托模型,这里应该只要传入一个类加载器就行了,具体是哪一个类加载器加载 jvm 会帮你解决的
  

#36楼 2018-07-31 14:49 大脸喵  

@ 小兵乙
实例化 handler 的时候传入了一个真是对象的引用,这个时候handler就持有真实对象的应用,当你通过 newProxyInstance 获得一个代理类的时候传入了该 handler 对象进去,这样当代理类调用函数执行的时候,会告诉handler具体执行的哪一个函数,然后在handler内部的invoke方法,通过反射执行真实对象的该方法,不知道这样解释对不对
  

#37楼 2018-07-31 15:05 大脸喵  

感觉楼主这样的写法对动态性的体现不是很强,最多体现了实现类和代理类可以避免重复代码,但是对于代理类的复用却基本上没有什么体现,一个更理想的写法是在 DynamicProxy 类的构造函数中创建代理类,就是下面的写法:

public Object DynamicProxy(Object subject) {
this.subject = subject;

return Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(),this);
}
  

#38楼 2018-08-20 21:24 lszy24  

@ 大脸喵
一般是提供一个Bind方法来生成动态代理的对象,你的这个是构造方法,没有返回值的
  

#39楼 2018-08-24 14:48 米斯特先生  

@ 江湖霸主
proxy是不是也和方法的类型有关

如果是静态方法需要传递class对象
如果是普通方法就是该类的对象,也就是new出来的对象

OVER

纯属恶搞

浙ICP备16024533号

浙公网安备 33010802007459号