Java动态代理机制

一、前言

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

动态代理看起来好像是个什么高大上的名词,但其实并没有那么复杂,直接从字面就很容易理解。动态地代理,可以猜测一下它的含义,在运行时动态地对某些东西代理,代理它做了其他事情。

二、InvocationHandler和Proxy

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

InvocationHandler:

/**
 * {@code InvocationHandler} is the interface implemented by
 * the <i>invocation handler</i> of a proxy instance.
 *
 * <p>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 {@code invoke}
 * method of its invocation handler.
 *
 * @author      Peter Jones
 * @see         Proxy
 * @since       1.3
 */

代理对象的invocation handler必须要实现InvocationHandler接口。

每个代理对象都有一个与之关联的invocation handler。当通过代理对象调用一个方法时,这个方法会调用invocation handler的invoke方法。

InvocationHandle接口的唯一一个方法invoke:

/**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param   proxy the proxy instance that the method was invoked on
     *
     * @param   method the {@code Method} instance corresponding to
     * the interface method invoked on the proxy instance.  The declaring
     * class of the {@code Method} object will be the interface that
     * the method was declared in, which may be a superinterface of the
     * proxy interface that the proxy class inherits the method through.
     *
     * @param   args an array of objects containing the values of the
     * arguments passed in the method invocation on the proxy instance,
     * or {@code null} if interface method takes no arguments.
     * Arguments of primitive types are wrapped in instances of the
     * appropriate primitive wrapper class, such as
     * {@code java.lang.Integer} or {@code java.lang.Boolean}.
     *
     * @return  the value to return from the method invocation on the
     * proxy instance.  */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

该方法的作用是处理基于代理对象的方法调用。看一下invoke方法的三个参数:

proxy:代理对象,通过这个代理对象调用方法

method:对应于通过代理对象调用的那个接口方法

args:通过代理对象调用方法时传进来的参数

Proxy:

/**
 * {@code 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方法。

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {..........}

方法返回一个基于参数中接口的代理对象,该代理对象会将方法调用发送到指定的invocation handler上。看一下该方法的三个参数:

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

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

h:一个InvocationHandler对象,当代理对象在调用方法的时候,会关联到该InvocationHandler对象上

介绍完了InvocationHandler接口和Proxy类的概念,捋一下这两个之间的关联:

先通过Proxy类的newProxyInstance获取代理对象,然后基于该代理对象的方法调用会触发与该代理对象关联的invocation handler中的invoke方法。

通过代码演示一下动态代理模式是什么样的?

首先定义一个要被代理的对象(真实对象):Subject类型的接口,为其声明两个方法

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

接口的实现类RealSubject:

public class RealSubject implements Subject {
    @Override
    public void getNews() {
        System.out.println("北京要实行垃圾分类了");
    }

    @Override
    public void hello(String str) {
        System.out.println("hello:" + str);
    }
}

接着定义代理对象:

public class ProxyFactory{
    //真实对象
    private Object realObj;

    public ProxyFactory(Object realObj){
        this.realObj = realObj;
    }

    /*获取代理对象*/
    public Object getProxyInstance(){
        Object proxyInstance = Proxy.newProxyInstance(realObj.getClass().getClassLoader(), realObj.getClass().getInterfaces(), new InvocationHandleImpl());
        System.out.println("获取到的代理对象:" + proxyInstance.getClass().getName());
        return proxyInstance;
    }

    //InvocationHandler的实现
    class InvocationHandleImpl implements InvocationHandler{

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("invoke中的代理对象:" + proxy.getClass().getName());
            //在代理真实对象之前添加一些自己的操作
            System.out.println("===before");
            System.out.println("method:" + method);
            Object invokeResult = method.invoke(realObj, args);
            //在代理真实对象之后添加一些自己的操作
            System.out.println("===after");
            return invokeResult;
        }
    }

}

测试:

public class Test {
    public static void main(String[] args) {
        Subject realSubject = new RealSubject();
        System.out.println("=====不使用动态代理=====");
        realSubject.getNews();
        realSubject.hello("world");
        System.out.println();

        System.out.println("=====使用动态代理=====");
        ProxyFactory proxyFactory = new ProxyFactory(realSubject);
        //获取代理对象
        Subject proxyInstance = (Subject) proxyFactory.getProxyInstance();
        //基于代理对象的方法调用
        proxyInstance.getNews();
        proxyInstance.hello("world");
    }
}

结果:使用动态代理在运行的时候才切入改变类的方法

=====不使用动态代理=====
北京要实行垃圾分类了
hello:world

=====使用动态代理=====
获取到的代理对象:com.sun.proxy.$Proxy0
invoke中的代理对象:com.sun.proxy.$Proxy0
===before
method:public abstract void com.hy.spring.service.Subject.getNews()
北京要实行垃圾分类了
===after
invoke中的代理对象:com.sun.proxy.$Proxy0
===before
method:public abstract void com.hy.spring.service.Subject.hello(java.lang.String)
hello:world
===after

可以看到返回的代理对象的类名是:com.sun.proxy.$Proxy0,和invocation handler中invoke方法的参数proxy是同一个代理对象

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

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

 

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

method:public abstract void com.hy.spring.service.Subject.getNews()
method:public abstract void com.hy.spring.service.Subject.hello(java.lang.String)

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

三、动态代理的使用场景

动态代理的好处我们从例子就能看出来,它比较灵活,可以在运行的时候才切入改变类的方法,而不需要预先定义它。

动态代理一般我们比较少去手写,但我们用得其实非常多。在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。

这种场景的使用是动态代理最佳的落地点,可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。

参考:

java的动态代理机制详解

你真的完全了解Java动态代理吗?看这篇就够了

理解java的三种代理模式

posted @ 2019-07-03 12:31  吹灭读书灯  阅读(481)  评论(0编辑  收藏  举报