源码分析与实战——Java动态代理的两种方式

SpringAOP可以说是面试必问的内容之一,这是作为JAVA高级开发者必须掌握的技能之一。

今天我结合实例、源码分析一下这一套机制。默认大家对静态代理和动态代理已经有了基础的理解。

1、动态代理

静态代理和动态代理的差别在此不再多说,网上到处是。我主要结合源码想说说动态代理,它是SpringAop的基础。

1.1 JDK自带的代理机制
1.1.1 示例

接口定义:

package com.zzmlake;

public interface Human {
    void doExec();
    void doWork();
}

实现类,也是被被代理类:

package com.zzmlake;

public class Man  implements  Human{

    @Override
    public void doExec() {
        System.out.println("I am running...");
    }

    @Override
    public void doWork() {
        System.out.println("I am codding...");
    }
}

代理类必须实现InvocationHandler接口,重写invoke方法:

package com.zzmlake;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyHandler implements InvocationHandler {
    private Object object;

    public MyHandler(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke "  + method.getName());
        method.invoke(object, args);
        System.out.println("After invoke " + method.getName());
        return null;
    }
    
    public static void main(String[] args) {
        Human human = new Man();
        MyHandler handler = new MyHandler(human);
        Human proxyHuman = (Human)Proxy.newProxyInstance(human.getClass().getClassLoader(), human.getClass().getInterfaces(), handler);

        proxyHuman.doExec();
        proxyHuman.doWork();
    }
}

这里把测试代码一起贴出来了,执行结果如下:

Before invoke doExec
I am running...
After invoke doExec
Before invoke doWork
I am codding...
After invoke doWork

Process finished with exit code 0

doExec()和doWork()这两个方法调用时,在调用前后,都各自输出一次打印,这说明了几件事情:

  1. 通过代理类代理后,每一次调用方法,其实都是调用了代理类的invoke()方法;
  2. 代理接口实现的invoke()方法的参数就是当前所执行的方法的基本信息,可以通过method.invoke(object, args);实际去执行这个方法;
  3. 在invoke()方法中,我们可以在被代理类实际的方法执行前后,多做一些额外的工作。

这里面有几个关键方法,我们来看一下。

1.1.2 InvocationHandler接口

InvocationHandler和Proxy代理的关系,让我想起Runnable和Thread,他们之间的关系几乎差不多:

  1. 定义Runnable接口实现类,重写run()方法,把线程需要执行的实际代码写在run()里面;最后通过Thread实例去执行这段代码;
  2. 定义InvocationHandler接口实现类,重写invoke()方法,把对原方法的改造写在invoke()里面;最后通过Proxy实例的方法调用去执行这段代码;

感觉是不是很像?哈哈!

InvocationHandler接口源码如下:

//这个接口和Runnable接口基本类似,就声明了一个方法,其他什么都没
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

InvocationHandler接口非常简单,就一个接口方法invoke(),invoke的中文意思就是被调用。我们通过注释看看参数说明:

  • proxy:the proxy instance that the method was invoked on. 方法被调用的代理实例。
  • method: instance corresponding to the interface method invoked on the proxy instance. 在代理实例中被调用的接口方法实例。
  • args: an array of objects containing the values of the arguments passed in the method invocation on the proxy instance.其实就是method的参数了。

简单的举个例子,如果你调用a.myfun(10, “hello”),那么此时method就是myfun方法,args这个数组里面放的就是10和"hello"。

代理对象
在InvocationHandler接口实现类MyHandler 中,我们有如下定义:

	private Object object;

这个object,就是被代理的对象。定义为Object类型,具有更好的通用性。当然,你也可以使用被代理的接口等其他类型。比如把被代理类的类型改成这样,结果完全不变:

 	private Human object;

    public SecondTest(Human object){
        this.object = object;
    }

我们可以通过构造方法对其进行初始化,就是一个简单的赋值。类型是否冲突,就得程序员自己处理了。

代理内容
为什么我们要代理呢?我们要额外做点什么事情呢?这就体现在了invoke()方法!

InvocationHandler接口实现类里面,invoke()方法必须重写。我们对Java类进行动态代理的目的,就在这个方法中实现。你可以加入一些日志打印,就像我加的System.out.println("Before invoke " + method.getName());一样。也可以加入权限校验,各种逻辑判断等等其他代码进来。

	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ......(方法执行之前做点什么,写在前)   
             
        method.invoke(object, args);
        
        ......(方法执行之后继续做点什么,写在后)
        return null;
    }

要注意的是:如果直接这么写,被代理类的任何方法被调用时,我们额外加入的代码都会被执行。比如上面例子执行过程中,invoke()里面的打印语句执行了4次。实际开发中,我们需要对特定的方法进行处理,此时可以先做判断,比如:

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("doWork")){
            System.out.println("Before invoke "  + method.getName());
            method.invoke(object, args);
            System.out.println("After invoke " + method.getName());
        }else {
            method.invoke(object, args);
        }
        return null;
    }

这里,我们加入了一个判断if(method.getName().equals(“doWork”)),表面只有方法名为doWork,才进行额外的处理;否则直接执行,不做任何修改。执行结果:

I am running...
Before invoke doWork
I am codding...
After invoke doWork

这个机制应该是很清晰明的。执行被代理类中原本的方法,用的是method.invoke()。

1.1.3 method.invoke()

首先我们要清楚,Method也是个重要的类:

public final class Method extends Executable {
    private Class<?>            clazz;
    private int                 slot;
    private String              name;
    private Class<?>            returnType;
    private Class<?>[]          parameterTypes;
    private Class<?>[]          exceptionTypes;
    private int                 modifiers;
    ......
}

我们写出一个方法,在类加载时,也会被加载到内存中;在内存中其实就是Method对象。我们可以看到,方法名称,返回类型,参数类型,异常类型等等方法的基本属性全都有了。其实,这属于Java反射的内容。

invoke()方法如下:

    @CallerSensitive
    public Object invoke(Object obj, Object... args) 
     		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

最后面一句最关键:

	return ma.invoke(obj, args);

这个invoke源码看不到,属于java底层实现。总之就是当前的方法被执行了,代理者是obj,参数是args。

1.1.4 Proxy.newProxyInstance

在测试代码中,我们使用了Proxy.newProxyInstance来得到一个代理类实例,然后用这个代理实例去调用被代理类的方法。这就是整个代理机制的核心方法。

简化后的核心代码如下:

 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)   throws IllegalArgumentException
    {
        ......
        final Class<?>[] intfs = interfaces.clone();
        .....
        //Look up or generate the designated proxy class.查找或创建一个设计好的代理类
        Class<?> cl = getProxyClass0(loader, intfs);
        
        //Invoke its constructor with the designated invocation handler.使用设计好的handler去调用构造器
        try {
            ......
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
			......
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
	        ......
		}
    }

通过第二个参数,其实我们就已经能确认这种动态代理方式的最大缺点:你必须先定义接口,通过接口才能实现动态代理能力

这里面最关键的应该有两句:

  1. Class<?> cl = getProxyClass0(loader, intfs);查找或创建一个设计好的代理类
  2. return cons.newInstance(new Object[]{h});使用构造器新建一个实例

getProxyClass0
直接看看这个方法的关键说明:

Returns the {@code java.lang.Class} object for a proxy class given a class loader and an array of interfaces. The proxy class
will be defined by the specified class loader and will implement all of the supplied interfaces. If a proxy class for the same permutation of interfaces has already been defined by the class loader, then the existing proxy class will be returned; otherwise, a proxy class for those interfaces will be generated dynamicallyand defined by the class loader.

大意是:通过给定的class loader和一组接口得到一个代理类。这个代理类将被指定的class loader定义,并且将实现所有提供的接口。如果这个代理类已经被定义过,它会被一直存放在缓存里面,此时可以直接返回定义好代理类;否则class loader将动态的创建一个。

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

调用链挺长的,我就上一段核心代码吧:

			byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
			try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }            

这就是代理类的生成代码,defineClass0是一个native方法,在Jvm底层实现。

前面已经说过,jdk自带的动态代理功能最大的问题是:你必须先定义接口,通过接口才能实现动态代理能力!

1.2 cglib实现的代理机制

cglib实现动态代理的原理和jdk自带的并不一样,不需要专门去定义接口,而是通过拦截方法的方式进行的,示例如下:

package com.zzmlake;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibTest implements MethodInterceptor {
    private Object targetObject;

    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable{
        Object obj = null;

        if(method.getName().equals("doWork")){
            System.out.println("Before intercept "  + method.getName());
            obj = method.invoke(targetObject, args);
            System.out.println("After intercept" + method.getName());
        }else{
            obj = method.invoke(targetObject, args);
        }
        return obj;
    }

    public Object createProxyObject(Object obj){
        this.targetObject = obj;

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(this);
        Object proxy = enhancer.create();

        return proxy;
    }

    static public void main(String [] args){
        CglibTest cglibTest = new CglibTest();
        Man man = (Man)cglibTest.createProxyObject(new Man());
        man.doWork();
        man.doExec();
    }
}

这里我们定义了一个类,实现了MethodInterceptor接口,重写了intercept方法。在这个方法里,我们可以实现动态修改的内容。这里我还是使用上面定义好的那个类Man,也是对doWork方法进行单独处理,执行结果和JDK方式一样:

Before intercept doWork
I am codding...
After intercept doWork
I am running...

生成代理类需要使用Enhance类来完成,这个类的说明如下:

Generates dynamic subclasses to enable method interception. This class started as a substitute for the standard Dynamic Proxy support included with JDK 1.3, but one that allowed the proxies to extend a concrete base class, in addition to implementing interfaces. The dynamically generated subclasses override the non-final methods of the superclass and have hooks which callback to user-defined interceptor implementations.

第一句就说明了Enhance的作用:生成动态子类以实现方法拦截。最核心的几个方法:

  • setSuperclass:Set the class which the generated class will extend设置需要进行动态代理的类。
  • setCallback:必须指定一个CallBack,类说明里面有:Often a single callback will be used per enhanced class。一般直接用当前类即可,MethodInterceptor的基类也是CallBack。
  • create:Generate a new class if necessary and uses the specified callbacks (if any) to create a new object instance.这个是核心的一个方法,生成我们想要的代理类实例。

可见,Cglib实现动态代理非常简单和灵活,它不需要被代理的类必须有接口,直接为被代理类生成一个子类,在子类中实现动态的修改。Cglib是基于asm实现的,非常遗憾的是,它的发展非常慢,已经跟不上JDK的发展速度了,执行的性能已经被JDK超过。

2、Spring AOP

Spring AOP我们一般称为切面编程,AOP是Aspect Orient Programming的缩写。它普遍应用于事务管理、日志、缓存等各方面。

Spring AOP是基于上面这两种动态代理来实现的,它会根据被代理类的实现方式,进行自动切换。如果被代理类有接口,则通过JDK来实现;否则使用Cglib来实现。JDK1.8以后,JDK动态代理的性能已经超过Cglib,这也是很多编码规范建议多使用接口的原因之一。

Spring AOP有4个主要的概念:

  • 切入点(Pointcut):指明切入的地方,要代理的类、方法等;
  • 通知(Advice):被代理的内容,在方法执行前、中、后要做什么事情;
  • 切面(Aspect):完成切入点和通知的开发,就是切面编程的过程;
  • 织入(Weaving):创建出代理对象的过程;
2.1 AOP切面编程

Spring AOP切面编程具体内容将在下篇详细说明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值