动态代理扩展

在讲动态代理之前,先来介绍下静态代理。

   静态代理的思想比较简单,通过实现相同的接口,代理需要代理的类。话不多说直接上例子

public class RuntimeClass implements RuntimeClassInterface {

    @Override
    public void outPut() {
        System.out.println("output");
    }
    
}
public class StaticProxy implements RuntimeClassInterface {

    private RuntimeClass runtimeClass;

    public StaticProxy(RuntimeClass runtimeClass){
        this.runtimeClass = runtimeClass;
    }
    @Override
    public void outPut() {
        System.out.println("before proxy !");
        runtimeClass.outPut();
        System.out.println("after proxy !");
    }
}
public static void main(String[] args) {
    RuntimeClass runtimeClass = new RuntimeClass();
    StaticProxy staticProxy = new StaticProxy(runtimeClass);
    staticProxy.outPut();
}


        静态代理顾名思义,就是通过确定的接口代理同样的接口实现类,模式比较单一,如果你有很多不同类型的类需要代理就要写各个类型的代理类比较繁琐。所以就有了动态代理。

java实现动态代理的方式有两种 jdk的动态代理、cglib代理

        先来讲讲jdk的动态代理,jdk的动态代理的代理处理类实现和静态代理非常相像,代理类需要实现公共接口InvocationHandler,保存真正的代理对象,在触发代理的时候会调用代理类中的invoke方法执行里面的逻辑。从下面代码可以看到invoke有三个参数分别是proxy(生成的代理类),method(需要被执行的方法),args(方法的参数数组)。

      这里比较好奇第一个参数proxy是什么用的,首先我们关注invoke方法拥有一个Object的返回参数,这个参数使得我们调用方法以后可以返回我们想要的结果而不仅限于method方法的结果,如果我们返回了proxy对象那么我们可以继续调用存在proxy的方法,这就是第一个用处连续调用,因为你进入的handler并不是代理类所以不能直接用this。第二个好处,在handler里面我们并不知道代理类里面实现了什么接口,所以通过proxy的反射我们可以获取信息。

public class RealObject  implements DynamicProxyInterface {
    @Override
    public DynamicProxyInterface doSomething() {
        System.out.println("this is doSomething");
        return  new RealObject();
    }

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println(proxy.getClass().getName());
    System.out.println("proxy:" + proxy.getClass() + " method:" + method.getName());
    method.invoke(proxied, args);
    return proxy;
}


public static void main(String[] args) {
    RealObject realObject = new RealObject();
    DynamicProxy dynamicProxy = new DynamicProxy(realObject);
    DynamicProxyInterface object = (DynamicProxyInterface) Proxy.
            newProxyInstance(RealObject.class.getClassLoader(),
                    new Class[]{DynamicProxyInterface.class}, dynamicProxy);
    DynamicProxyInterface dynamicProxyInterface =  object.doSomething().doSomething().doSomething();
    object.doSomethingElse();
}

     那么是怎么触发invoke方法执行的呢?又如何准确找到需要代理的方法呢?下面就是jdk动态代理的关键。

public class DynamicProxy implements InvocationHandler {
    private Object proxied;

    public DynamicProxy(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy:" + proxy.getClass() + " method:" + method.getName());
        method.invoke(proxied, args);
        return null;
    }

     前面两行是初始化被代理类以及代理处理类,RealObject实现了DynamicProxyInterface接口,jdk的Proxy类是实现动态代理的关键,我们这里用的是它的newProxyInstance方法来动态代理。从下面可以看到newProxyInstance方法有三个参数分别是Classloader(指定一个类加载器)、interfaces(代理类可用的接口组)、InvocationHandler(代理处理类)。

public static void main(String[] args) {
    RealObject realObject = new RealObject();
    DynamicProxy dynamicProxy = new DynamicProxy(realObject);
    DynamicProxyInterface object = (DynamicProxyInterface) Proxy.
            newProxyInstance(RealObject.class.getClassLoader(),
                    new Class[]{DynamicProxyInterface.class}, dynamicProxy);
    object.doSomething();
    object.doSomethingElse();
}

     主要介绍下第一个参数ClassLoader,Classloader是一个类加载器,classloader根据加载顺序分别是 Bootstrap CLassloder(负责加载java_home/jre/lib目录下的核心类rt.jar等或者-Xbootclasspath指定目录下的类) 、Extention ClassLoader(负责加载java_home/jre/lib/ext目录下的扩展类或者-Djava.ext.dirs指定目录下的类)、 AppClassLoader(负责加载-classpath/-Djava.class.path所指的目录下的类。开发者可以直接使用AppClassLoader。一般来说java应用的类都是有他来完成加载)参考博客https://blog.csdn.net/stven_king/article/details/50275817。newProxyInstance指定一个类加载器的时候,类加载器会根据双亲委派机制从最上层的加载器依次往下找,是否需要加载的类已经存在jvm中,如果不存在则通过该类加载器加载到jvm中。

       接下来讲讲newProxyInstance方法生成代理的过程(只挑里面的主要部分来讲)。以下是newProxyInstance源码。

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}
      我们可以看到该方法先是判断传入的代理处理类是否为空、检查接口的访问权限,然后根据接口生成一个java类,这个java类实际上是实现了这些接口的一个类,然后通过classloader加载到jvm中,并将结果保存在线程安全的ConcurrentMap中以便下一次可以直接使用。通过反射获取java类的构造器,构造器的参数是InvocationHandler,将传入的InvocationHandler放入构造器中初始化类对象,这里也解释了为什么InvocationHandler是代理处理类,因为在生成的java类中是通过InvocationHandler来进行代理类的处理。每次调用接口的方法时,实际都会去找InvocationHandler中的invoker方法,并根据反射执行被代理类中相应的方法。这就是jdk代理的全过程。

       

public class CglibProxy implements MethodInterceptor {


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before proxy.......");
        //Object object = method.invoke(obj,args);
        Object object =  proxy.invokeSuper(obj,args);
        System.out.println("after proxy.......");
        return object;
    }

    public static void main(String[] args) {
        //指定路径获取cglib生成的代理类
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\Work\\Project\\thinkinjava\\src\\main\\java\\think\\in\\java\\chapter14\\CglibCreate");
        CglibProxy cglibProxy = new CglibProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealObject.class);
        enhancer.setCallback(cglibProxy);
        RealObject realObject = (RealObject) enhancer.create();
        realObject.doSomething();
    }
}

  cglib较jdk动态代理就有所不同了,我们这里是拿其中的一种形式来举例子 Enhancer(增强器),它并不是实现代理类的接口而是直接继承代理类,然后通过拦截的方式进入回调方法intercept,这个方法有四个参数obj(生成的代理类对象)、method(实际要调用的方法信息)、args(输入的参数集合)、methodproxy(cglib生成的方法代理),methodproxy这个对象除了用来调用实际代理类的方法以外还有一个重要作用生成fastclass索引,在调用methodproxy.invokerSuper方法的时候会自动生成两个fastclass类用来索引,这就是为什么cglib的处理效率会比jdk快的原因,它不是使用反射来寻找方法的具体位置,而是通过一种索引机制,但是这样带了的另一个弊端就是生成代理类的时候会比jdk动态代理复杂。所以两个动态代理的选择需要根据实际情况而定。说回cglib,从代码中可以看到生成动态代理是通过enhancer.create()来完成的。请看下面类图。

  

当new Enhancer的时候,会初始化EnhancerKey的工厂方法,第一次就调用firstinstance,然后在调用create方法的时候先进入缓存取查找是否已经生成过这个代理类如果没有生成过会执行生成策略,这个生成策略只包括生成代理类并不会马上生成fastclass,然后会触发nextinstance初始化代理类对象,上面这个图其实少了nextinstance,最后返回这个代理对象。

    其实cglib还有很多种场景,这里只做简单了解,下面是所有的类图,如果有兴趣可以根据类图一个一个了解。


 动态代理就介绍到这,欢迎点评


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值