说透常见设计模式之代理模式

JS设计模式之代理模式 Star这个类就是明星,StarProxy这个类就是明星经纪人,假如我们是商家,现在我们想请这个明星来参加我们的活动,我们开始和经纪人谈价格,talk出价1w,此时经纪人会做一个判断,如果价格大于等于1w,这个活动就比较适合,可以参加,代理就会去通知明星演出,如果小于1w的话,就不会,这个就是简单的代理模式,我们要访问明星,需要经过经纪人(也就是代理),才能去访问。其实就是给一个对象提供一个代理,如果要访问这个对象必须经过这个代理,先说,这个模式的优点是什么。 阅读详情

代理模式

定义

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

说简单点,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。

通俗的来讲代理模式就是我们生活中常见的中介。

为什么需要

  • 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类

代理模式结构图:

分类

两种:1.静态代理 2 动态代理

静态代理

interface Subject {
    void request();
}

class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject");
    }
}

class Proxy implements Subject {
    private Subject subject;

    public Proxy(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void request() {
        System.out.println("begin");
        subject.request();
        System.out.println("end");
    }
}

public class ProxyTest {
    public static void main(String args[]) {
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}

优点:可以在不修改目标对象的前提下扩展目标对象的功能

缺点:如果需要代理多个类,每个类都会有一个代理类,会导致代理类无限制扩展;如果类中有多个方法,同样的代理逻辑需要反复实现、应用到每个方法上,一旦接口增加方法,目标对象与代理对象都要进行修改

所以静态代理不怎么常用

动态代理

定义

代理类在程序运行时创建的代理方式被成为动态代理。也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。

关键点就是怎么动态生成代理类?

分类

常见的分为两种:

  • jdk动态代理
  • cglib动态代理

jdk动态代理

package com.pandacase.proxy.dynaimc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author: changjiu.wang
 * @Date: 2020/5/24 11:53
 */

interface Subject {
    void request();
}

class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject");
    }
}

class DynamicProxy implements InvocationHandler {

    /**
     * 反射代理目标类(被代理,解耦的目标类)
     */
    private Object target;

    /**
     * 可以通过构造器动态设置被代理目标类,以便于调用指定方法
     * @param subject
     */
    public DynamicProxy(Object subject){
        this.target = subject;
    }

    /**
     * 代理过程中的扩展点
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("brfore call specific method >>" + method.getName());
        //MethodAccessor.invoke()
        Object result = method.invoke(target, args);
        System.out.println("after call specific method>>" + method.getName());
        return result;
    }

}

public class ProxyTest {

    public static void main(String[] args) throws Exception{
       /* Subject realSubject = new RealSubject();
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        //1.0 获取代理类的类对象,主要设置相同的ClassLoader去加载目标类实现的接口Subject类
        Class<?> proxyClass = Proxy.getProxyClass(ProxyTest.class.getClassLoader(), new Class[]{Subject.class});
        //2.0 得到代理类后,就可以通过代理类的处理器句柄来得到构造器
        final Constructor<?> con = proxyClass.getConstructor(InvocationHandler.class);
        //3.0 获取具体执行方法的句柄处理器,目的通过构造器传入被代理目标类对象,注入到代理类处理器句柄中进行代理调用
        final InvocationHandler handler = new DynamicProxy(realSubject);
        //4.0 通过构造器创建代理类对象
        Subject subject = (Subject)con.newInstance(handler);

        //5.0 最后调用方法
        subject.request();*/

        //设置生成代理类文件到本地
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        Subject subject2 = (Subject) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),
                new Class[]{Subject.class}, new DynamicProxy(new RealSubject()));
        //调用代理类方法
        subject2.request();
    }
}

jdk动态代理原理

动态代理的关键点就是动态生成代理类

jdk通过 Proxy.newProxyInstance 创建动态代理类

通过设置系统属性,把生成的代理类保存下来

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

动态生成的代理类

package com.sun.proxy;

import com.pandacase.proxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void hello(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.pandacase.proxy.Subject").getMethod("hello", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

jdk动态代理源码分析(网上借鉴)

从Proxy源码对JDK动态代理进行深入的剖析。Proxy类对外提供了4个静态方法,分别为:

public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces);
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
public static boolean isProxyClass(Class<?> cl);
public static InvocationHandler getInvocationHandler(Object proxy);

1.getProxyClass

getProxyClass 方法返回代理类的Class实例。这个代理类就是类加载器loader定义的、实现了一些列接口 interfaces的。如果之前已经为这个loader和interfaces创建过代理类,那么直接返回这个代理类的Class实例。如果没有,则动态创建并返回。

public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException {
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    return getProxyClass0(loader, intfs);
}

getProxyClass 方法并没有JDK动态代理的核心逻辑:第二行将接口的Class数组interfaces进行克隆。3-6行是类加载器和接口访问权限的校验(这里虚拟机的安全性相关逻辑,不是我们JDK代理技术的关注点,所以不做过多解释)。关键的逻辑就最后一行代码,调用 getProxyClass0 方法去获取代理类的Class实例。

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

getProxyClass0 也没有包含JDK动态代理的核心逻辑:2-4行只是对接口的个数进行了简单的校验,不能超过65535,我们在实际应用中一般也不会出现这种情况。最后一行代码是去缓存对象 proxyClassCache 中获取代理类的Class实例。proxyClassCache是Proxy类的静态变量

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

思考:1、为什么JDK动态代理中要求目标类实现的接口数量不能超过65535个?

proxyClassCache 是类 java.lang.reflect.WeakCache 的实例,通过类名就可以看出来这个类是用来做缓存的。Proxy 调用 WeakCache 提供的构造函数,传入 KeyFactory 实例和 ProxyClassFactory 实例(这两个实例的用途后面会讲到)。在分析 WeakCache 的 get 方法源码之前,我们先来大概介绍下 WeakCache 缓存的数据结构。

WeakCache 缓存的数据结构是(key,sub-key)->(value)。这个结构和Redis里面的hash结构很类似,根据一级的键(key)、二级的键(sub-key)为索引,去检索出值(value)。对应到 WeakCache 类代码里面,就是一个ConcurrentMap 实例map,这个map的key就是一级键,map的value又是个 ConcurrentMap 实例,这个子map的key是二级键,子map的value就是缓存的的值。上面图中的箭头就表示着对应关系,一目了然。图中下半部分是JDK动态代理缓存的键值生成规则。下面我们看下 WeakCache 的get方法源码。

1 public V get(K key, P parameter) {
2    Objects.requireNonNull(parameter);
3    expungeStaleEntries();
4    Object cacheKey = CacheKey.valueOf(key, refQueue);
5    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
6    if (valuesMap == null) {
7        ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());
8        if (oldValuesMap != null) {
9            valuesMap = oldValuesMap;
10        }
11    }
12    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
13    Supplier<V> supplier = valuesMap.get(subKey);
14    Factory factory = null;
15    while (true) {
16        if (supplier != null) {
17            V value = supplier.get();
18            if (value != null) {
19                return value;
20            }
21        }
22        if (factory == null) {
23            factory = new Factory(key, parameter, subKey, valuesMap);
24        }
25        if (supplier == null) {
26            supplier = valuesMap.putIfAbsent(subKey, factory);
27            if (supplier == null) {
28                supplier = factory;
29            }
30        } else {
31            if (valuesMap.replace(subKey, supplier, factory)) {
32                supplier = factory;
33            } else {
34                supplier = valuesMap.get(subKey);
35            }
36        }
37    }
38 }

我们的调用语句是proxyClassCache.get(loader, interfaces),也就是说形参key是类加载器loader,parameter是接口的Class数组interfaces。

第2行代码是对形参parameter(interfaces)进行非空的校验,如果为空则抛出空指针异常。第3行代码是去除缓存中的陈旧数据,这里不是我们的关注点,就不详细介绍了。

第4行是根据形参key(loader)计算出缓存的一级键cacheKey,这里我们不去看具体的生成逻辑,只需要大概知道一级键是根据形参key(loader)算出来的,这里可以用一个数学函数表达式描述这个关系:key=f(loader)。

第5行代码是根据一级键查出值,这个值的Map实例valuesMap。由于之前没有为这个loader和interfaces创建过代理类,所以valuesMap为null,6-11行代码会被执行,这几行代码就是给valueMap一个初始值,然后结合上面算出来的一级键cacheKey塞进缓存实例map里面。

第12行根据key(loader)和parameter(interfaces)计算出缓存的二级键subKey。这里的subKeyFactory是Proxy调用WeakCache提供的构造函数时,传入的KeyFactory实例(上面提到过)。KeyFactory是Proxy的内部类,我们简单看下KeyFactory的apply方法,看下是怎么生成二级键的。

public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
    switch (interfaces.length) {
        case 1: return new Key1(interfaces[0]);
        case 2: return new Key2(interfaces[0], interfaces[1]);
        case 0: return key0;
        default: return new KeyX(interfaces);
    }
}

通过上面的代码,只需要大概知道二级键是根据interfaces计算出来的(classLoader这个参数根本没用到)。这里可以用一个数学函数表达式描述这个关系:sub-key=g(interfaces)。

我们继续上面的WeakCache.get方法分析,第13行代码是根据二级键subKey从valuesMap获取值supplier,这个值supplier也就是我们缓存数据的值。由于valuesMap是新建的,所以supplier为null。

15-37行是个循环,第一次进入的时候,factory和supplier都为null。所以22-30行代码将被执行。第23行代码是调用Factory构造函数创建一个实例factory(Factory是WeakCache的内部类),这个构造函数就是简单把传入的参数赋值给factory实例的字段。接下来26-29将构造的二级键subKey和factory塞进valuesMap,并将factory赋给supplier(Factory类继承Supplier类)。到这里缓存数据的初始化就算告一段落了,一级键是根据loader计算出来的cacheKey,二级键是根据interfaces计算出来的subKey,值是new的一个factory(Supplier实例)。

第一遍的循环并没有创建代理类,只是做了一些初始化的工作,下面继续执行这个循环体(15-37行)。这次supplier不为null了(就是上面的Factory实例factory),所以进入16-21行的代码块,第17行实质就是调用factory.get方法。这个方法返回的value也就是动态代理类的Class实例。紧接着第19行就把这个value返回。下面来看下Factory的get方法的源码。

1 public synchronized V get() {
2    Supplier<V> supplier = valuesMap.get(subKey);
3    if (supplier != this) {
4        return null;
5    }
6    V value = null;
7    try {
8        value = Objects.requireNonNull(valueFactory.apply(key, parameter));
9   } finally {
10        if (value == null) {
11            valuesMap.remove(subKey, this);
12        }
13    }
14    assert value != null;
15    CacheValue<V> cacheValue = new CacheValue<>(value);
16    if (valuesMap.replace(subKey, this, cacheValue)) {
17        reverseMap.put(cacheValue, Boolean.TRUE);
18    } else {
19        throw new AssertionError("Should not reach here");
20    }
21    return value;
22 }

第2行代码是根据二级键subKey得到值supplier,也就是我们在上面的WeakCache的get方法中创建的Factory实例factory。

接下来的几行代码没什么好讲的,直接看第8行代码,这行代码调用了valueFactory.apply方法创建动态代理类并将结果赋值给变量value。

9-14行针对创建代理类失败的情况下做的处理和判断逻辑。如果创建代理类成功,则继续执行后面的代码。

第15行代码把生成的代理类的Class实例(即value变量)进行缓存。缓存的值并不是直接的value,而是由value构造的一个CacheValue实例cacheValue,由于CacheValue实现了接口Value,而Value接口继承了Supplier接口,所以cacheValue就是Supplier的实例。这里我们不需要去深究CacheValue的数据结构,只需要知道缓存的值是根据代理类的Class实例去计算的,这里可以用一个数学函数表达式描述这个关系:value=h($ProxyX.class)。

第16行代码将二级键subKey和cacheValue放入valuesMap(valuesMap的类型是ConcurrentMap<Object, Supplier<V>>)。第18行是记录缓存状态的。方法的最后将代理类的Class实例value返回。

这个方法的主要逻辑是对缓存的操作,动态代理类的创建动作是通过调用valueFactory.apply得到的。这里的valueFactory是在构造WeakCache时传入的参数,上面提到的ProxyClassFactory实例。由于Factory是WeakCache的内部类,所以在Factory的get方法中可以使用这个实例valueFactory。下面我们就来看下ProxyClassFactory的apply方法。


1 public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
2    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
3    for (Class<?> intf : interfaces) {
4        Class<?> interfaceClass = null;
5        try {
6            interfaceClass = Class.forName(intf.getName(), false, loader);
7        } catch (ClassNotFoundException e) {}
8        if (interfaceClass != intf) {
9            throw new IllegalArgumentException(intf + " is not visible from class loader");
10        }
11        if (!interfaceClass.isInterface()) {
12            throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");
13        }
14        if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
15            throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());
16        }
17    }
18    String proxyPkg = null;
19    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
20    for (Class<?> intf : interfaces) {
21        int flags = intf.getModifiers();
22        if (!Modifier.isPublic(flags)) {
23            accessFlags = Modifier.FINAL;
24            String name = intf.getName();
25            int n = name.lastIndexOf('.');
26            String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
27            if (proxyPkg == null) {
28                proxyPkg = pkg;
29            } else if (!pkg.equals(proxyPkg)) {
30                throw new IllegalArgumentException("non-public interfaces from different packages");
31            }
32        }
33    }
34    if (proxyPkg == null) {
35        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
36    }
37    long num = nextUniqueNumber.getAndIncrement();
38    String proxyName = proxyPkg + proxyClassNamePrefix + num;
39    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
40    try {
41        return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
42    } catch (ClassFormatError e) {
43        throw new IllegalArgumentException(e.toString());
44    }
45 }

第2-17行代码,是几个简单的校验工作:1.接口interfaces是否对类加载器loader可见(即接口interfaces是由类加载器loader加载的或者由loader的上一级类加载器加载的,这点需要读者了解JVM的类加载知识),2.参数interfaces是否都是接口,3.参数interfaces里面有没有重复数据。

第18-26行代码,是为即将生成的代理类计算出访问标志和包名。具体的约束和规则如下:

1.如果所有的接口的访问标志都是public,那么生成的代理类的访问标志是final public,否则是final。

2.对于访问标志不是public的接口,它们必须要在同一个包下,否则抛出异常。

3.如果存在访问标志不是public的接口,那么生成的代理类的包名就是这些接口的包名。否则包名是默认的ReflectUtil.PROXY_PACKAGE(即com.sun.proxy)。

第37-38行代码,是计算代理类的权限定名。代理类的简单名称生成规则前面介绍过,是特定前缀"$Proxy"加上一个序列数(0,1,2,3...)。

第39行调用ProxyGenerator的静态方法generateProxyClass去创建代理类的字节码。这个方法逻辑就是根据一些固化的规则(比如代理类里面要实现接口的方法,实现Object的equals、hashCode、toString方法,提供一个形参为InvocationHandler实例的构造函数等等),依据JAVA虚拟机规范中定义的Class类文件结构去生成字节码的。

第41行代码,调用Proxy的本地方法defineClass0将生成的代理类字节码加载到虚拟机,并返回代理类的Class实例。

到目前为止,JDK动态代理类的创建流程就全部结束了,我们说的是首次创建代理类的情况,现在我们回头来看下WeakCache的get方法,如果之前已经为类加载器loader和接口interfaces创建过了代理类,那么调用这个方法的时候是个什么样子呢?答案就是根据一级键和二级键直接从缓存中取到代理类的Class实例。这里就不再逐行分析代码了,读者自己理解下。最后用一张简单概要的时序图描绘一下Proxy的getProxyClass0方法生成代理类的调用流程。

2. newProxyInstance

有了上面对动态代理类的创建过程的系统理解,现在来看newProxyInstance方法就容易多了,它就是使用反射机制调用动态代理类的构造函数生成一个代理类实例的过程。


1 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException {
2    Objects.requireNonNull(h);
3    final Class<?>[] intfs = interfaces.clone();
4    final SecurityManager sm = System.getSecurityManager();
5    if (sm != null) {
6        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
7    }
8    Class<?> cl = getProxyClass0(loader, intfs);
9    try {
10        if (sm != null) {
11            checkNewProxyPermission(Reflection.getCallerClass(), cl);
12        }
13        final Constructor<?> cons = cl.getConstructor(constructorParams);
14        final InvocationHandler ih = h;
15        if (!Modifier.isPublic(cl.getModifiers())) {
16            AccessController.doPrivileged(new PrivilegedAction<Void>() {
17                public Void run() {
18                    cons.setAccessible(true);
19                    return null;
20                }
21            });
22        }
23        return cons.newInstance(new Object[]{h});
24    } catch (IllegalAccessException | InstantiationException e) {
25        throw new InternalError(e.toString(), e);
26    } catch (InvocationTargetException e) {
27        Throwable t = e.getCause();
28        if (t instanceof RuntimeException) {
29            throw (RuntimeException) t;
30        } else {
31            throw new InternalError(t.toString(), t);
32        }
33    } catch (NoSuchMethodException e) {
34        throw new InternalError(e.toString(), e);
35    }
36 }

第2-7行代码,克隆interfaces,并进行权限相关校验,和前面getProxyClass方法类似,这里不做赘述。

第8行getProxyClass0方法获取代理类的Class实例,这个方法在上面也详细介绍过了。

第10-12行也是进行权限校验,检查调用者是否对这个代理类有访问权限。

第13-23行就是构造代理类实例的过程。先获取代理类的构造函数,接着对其访问权限进行判断,如果不是public,则将其设置可访问的。最后利用反射机制调用构造方法,传入参数InvocationHandler的实例h,创建代理类实例并返回。

3. isProxyClass

这个方法用于判断一个类是不是代理类。

public static boolean isProxyClass(Class<?> cl) {
    return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}

Proxy.class.isAssignableFrom(cl)这句话简而言之就是判断cl所表示的类是不是Proxy或者Proxy的子类。因为所有的代理类都集成Proxy,所以这个条件必须满足。满足这个条件也不能保证就是代理类,因为可能存在人为地编写一个类继承Proxy这种情况。proxyClassCache.containsValue(cl)这个方法是检查缓存中是否存在这个Class实例cl。我前面分析过,但凡生成的代理类都会被缓存,所以这个方法才是检测一个类是否是代理类的唯一标准。

4. getInvocationHandler

这个方法用于获取代理类中的InvocationHandler实例。这个方法没有什么太多的逻辑,基本就是判断下传入的对象是否是代理类,以及一些访问权限的校验。当这些都合法的情况下,返回InvocationHandler实例。

public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException {
    if (!isProxyClass(proxy.getClass())) {
        throw new IllegalArgumentException("not a proxy instance");
    }
    final Proxy p = (Proxy) proxy;
    final InvocationHandler ih = p.h;
    if (System.getSecurityManager() != null) {
        Class<?> ihClass = ih.getClass();
        Class<?> caller = Reflection.getCallerClass();
        if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),ihClass.getClassLoader())) {
            ReflectUtil.checkPackageAccess(ihClass);
        }
    }
    return ih;
}

cglib动态代理(参考网上)

和JDK动态代理不同,Cglib动态代理是基于Java的继承机制实现的。我们先来看一下Cglib动态代理的UML图。

从上图中可以看出,Cglib生成的动态代理类ProxyObject继承于目标类 TargetSubject,并且持有一个类型为MethodInterceptor 接口的实例引用,这个引用需要我们自己实现,这一点和JDK动态代理的InvocationHandler接口是一样的。我们还是以文章开头的例子来看,用Cglib是如何实现的。

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。简单的实现举例:


//这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理
public class SayHello {
 public void say(){
  System.out.println("hello everyone");
 }
}

该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。

public class CglibProxy implements MethodInterceptor{
 private Enhancer enhancer = new Enhancer();
 public Object getProxy(Class clazz){
  //设置需要创建子类的类
  enhancer.setSuperclass(clazz);
  enhancer.setCallback(this);
  //通过字节码技术动态创建子类实例
  return enhancer.create();
 }
 //实现MethodInterceptor接口方法
 public Object intercept(Object obj, Method method, Object[] args,
   MethodProxy proxy) throws Throwable {
  System.out.println("前置代理");
  //通过代理类调用父类中的方法
  Object result = proxy.invokeSuper(obj, args);
  System.out.println("后置代理");
  return result;
 }
}

具体实现类

public class DoCGLib {
 public static void main(String[] args) {
  CglibProxy proxy = new CglibProxy();
  //通过生成子类的方式创建代理类
  SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
  proxyImp.say();
 }
}

Cglib动态代理的源码分析

可以看到Cglib在运行时生成的代理类是继承于我们自己的目标类ProxyText,从而实现代理的功能的。那么Cglib的代码到底是怎么生成的,我们继续往下看:

ProxyTest proxyTest = (ProxyTest) enhancer.create();

这段代码是我们调用Cglib生成代理类的代码,我们跟进去看一下,具体是如何实现的。


public class Enhancer extends AbstractClassGenerator {

   //创建代理类
   public Object create() {
      this.classOnly = false;
      this.argumentTypes = null;
      return this.createHelper();
    }
    //帮助类
    private Object createHelper() {
      this.preValidate();
      Object key = KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID);
      this.currentKey = key;
      //调用父类的create接口
      Object result = super.create(key);
      return result;
    }
}

我们看到具体的实现是调用父类的create接口实现,继续跟进去看看

public abstract class AbstractClassGenerator<T> implements ClassGenerator {
    /**
    * 创建动态代理对象
    */
     protected Object create(Object key) {
        try {
            ClassLoader loader = this.getClassLoader();
            Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;
            AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
            if (data == null) {
                Class var5 = AbstractClassGenerator.class;
                synchronized(AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache);
                        data = new AbstractClassGenerator.ClassLoaderData(loader);
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }

            this.key = key;
            //此处是关键,跟进去会发现最终调用的是下面的generate方法产生动态代理的class对象
            Object obj = data.get(this, this.getUseCache());
            //通过反射生成具体的代理对象
            return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);
        } catch (RuntimeException var9) {
            throw var9;
        } catch (Error var10) {
            throw var10;
        } catch (Exception var11) {
            throw new CodeGenerationException(var11);
        }
    }
    /**
    * 该方法式最终产生动态代理class的地方
    */
    protected Class generate(AbstractClassGenerator.ClassLoaderData data) {
        Object save = CURRENT.get();
        CURRENT.set(this);

        try {
            ClassLoader classLoader = data.getClassLoader();
            if (classLoader == null) {
                throw new IllegalStateException("ClassLoader is null while trying to define class " + this.getClassName() + ". It seems that the loader has been expired from a weak reference somehow. " + "Please file an issue at cglib's issue tracker.");
            } else {
                String className;
                synchronized(classLoader) {
                    className = this.generateClassName(data.getUniqueNamePredicate());
                    data.reserveName(className);
                    this.setClassName(className);
                }
                Class gen;
                if (this.attemptLoad) {
                    try {
                        gen = classLoader.loadClass(this.getClassName());
                        Class var25 = gen;
                        return var25;
                    } catch (ClassNotFoundException var20) {
                        ;
                    }
                }
                //此处是关键,调用DefaultGeneratorStrategy的generate方法生成class文件的二进制流,具体是通过asm生成的,感兴趣的同学可以跟进去再看看
                byte[] b = this.strategy.generate(this);
                className = ClassNameReader.getClassName(new ClassReader(b));
                ProtectionDomain protectionDomain = this.getProtectionDomain();
                //以下代码通过代理类class文件的二进制流生成具体的class对象
                synchronized(classLoader) {
                    if (protectionDomain == null) {
                        gen = ReflectUtils.defineClass(className, b, classLoader);
                    } else {
                        gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
                    }
                }

                Class var8 = gen;
                return var8;
            }
        } catch (RuntimeException var21) {
            throw var21;
        } catch (Error var22) {
            throw var22;
        } catch (Exception var23) {
            throw new CodeGenerationException(var23);
        } finally {
            CURRENT.set(save);
        }
    }
}

总结

CgLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CgLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CgLib合适,反之,使用JDK方式要更为合适一些。同时,由于CgLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

JDK动态代理是在运行时JDK根据class文件的格式动态拼装class文件,并加载到jvm中生成代理对象的。而Cglib动态代理是通过ASM库来操作class文件动态生成代理类的。同时你应该了解到:JDK动态代理是基于java中的接口实现的,Cglib是基于java中的继承实现的。

DCN交换机通过boot模式升级 LIC导入成功后,输入enable进入特权模式,把网线接到通讯网口上,配置好对应interface vlan地址,在enable模式用 copy tftp://x.x.x.x/xxxxxx_nos.img nos.img待提示write ok时,reload设备完成升级。交换机启动后,在显示以下信息时输入“Ctrl+B”,一定得是手动按“Ctrl+B”进入的boot模式才能正常升级。升级交换机成功,在BootROM模式下,执行命令run或reboot,回到CLI配置界面。# 交换机通过boot模式升级。 阅读详情

相关推荐

告别手动改代码!用SSC工具5分钟搞定EtherCAT从站动态模块配置(含Excel模板)

本文详细介绍了如何利用SSC工具和Excel模板实现EtherCAT从站动态模块配置的自动化流程。通过零代码修改的动态配置方案,大幅提升工业自动化产线的灵活性和效率,配置时间从平均4小时缩短至8分钟,错误率下降96%。文章包含Excel模板设计、实战演示和避坑指南,助力工程师快速掌握这一高效工具。

weixin_28339967的博客 573

Proxy源码解析。

/** * Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。 * * 创建某一接口 Foo 的代理: * * InvocationHandler handler = new MyInvocationHandler(...); * Class proxyClass = Proxy.getProxyClass( * ...

孤芳不自赏 1420

从零构建:如何用MATLAB和SDR硬件搭建你的第一个OFDM卫星通信原型系统

本文详细介绍了如何利用MATLAB和SDR硬件从零构建OFDM卫星通信原型系统。通过核心组件选型、开发环境配置、OFDM算法实现到系统集成与优化的完整流程,帮助通信工程学生和DIY爱好者快速掌握软件无线电技术在卫星通信中的应用。特别推荐使用HackRF One和MATLAB工具箱实现高性价比的OFDM系统验证。

pz890123456的博客 379

Class的isAssignableFrom方法详解

Class的isAssignableFrom方法 Class类的isAssignableFrom是个不常用的方法,感觉这个方法的名字取得不是很好,所以有必要在此解析一下,以免在看源码时产生歧义,这个方法的签名如下: public native boolean isAssignableFrom(Class<?> cls); 由方法签名可见是一个本地方法,即C代码编写的。 以下是JDK中...

IT云清 2019

Halcon实战:5分钟搞定工业零件中的圆孔检测(附完整代码)

本文详细解析了如何利用Halcon的hough_circle算法在5分钟内高效完成工业零件中的圆孔检测。通过优化图像采集、智能预处理和参数化边缘检测,解决了金属反光、油污干扰等工业场景常见问题,并提供了完整的代码示例和实战案例,显著提升检测精度和效率。

weixin_29061997的博客 242

医疗影像配准与融合: 使用MATLAB对医学影像进行配准和融合,实现不同影像模态之间的对准和结构信息融合等

医疗影像配准与融合是医学影像处理的重要技术,它用于将不同来源、不同时间或不同模态的医学影像进行配准和融合,以获得更加完整、准确的患者解剖信息。基于MATLAB的医疗影像配准与融合可以帮助医生更准确地诊断疾病,制定更有效的治疗方案。医疗影像配准与融合是医学影像处理的关键技术,它对于提高医学影像诊断和治疗的准确性至关重要。MATLAB是一种功能强大的数学和科学计算软件,它可以为医疗影像配准与融合提供强大的工具和支持。

走向CTO的路上... 717

class.isAssignableFrom的用法

A.isAssignableFrom(B)方法 B是不是A的子类或者子接口。如果是则返回true,如果不是则返回false。

ngc_1277 909

【C2000系列DSP的Bootloader详解】如何打包源文件为库文件且加密仅授权使用,一步步教你如何实现

如果想要在 TI 的 CCS 开发环境中为 C2000 系列 MCU 生成 lib 库文件,并对这些库文件进行加密保护,确保只有授权方(持有密钥)才能调用和使用,以此保护源代码的知识产权。

昔时扬尘处 177

常见的设计模式之代理模式

概念 代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问。这种类型的设计模式属于结构型模式 代理模式一般包含三个角色: 抽象主题角色:主要职责是声明真实主题与代理的共同接口方法,该类可以是接口也可以是抽象类 真实主题角色:该类也被称为被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象 代理主题角色:也被称为代理类,其内部持有真实主题角色的引用,因此具备完全的对真实主题角色的代理权。客户端调用代理对象的方法,同时也调用被代理对象的方法。 分类

香烟,瓜子,矿泉水 415

第32篇:Vibe Coding时代:LangGraph + Redis 任务状态持久化实战,解决服务重启后任务状态丢失问题

LangGraph + Redis任务状态持久化方案 本文针对Agent任务状态丢失问题,提出基于Redis的持久化解决方案。当服务重启时,传统内存存储方式会导致任务状态丢失,影响用户体验和运维排查。 核心方案 使用Redis Hash存储任务状态,key格式为agent:task:{task_id} 设计完整状态字段包括task_id、status、progress等关键信息 实现task_store模块提供CRUD操作接口 在Agent执行各节点更新Redis中的任务状态 技术实现 通过Celery执行

sundong_81的博客 44

基于 STM32 设计的 HC05 蓝牙音箱

本项目介绍如何使用 STM32 微控制器和 HC05 蓝牙模块设计一个简单的蓝牙音箱。该音箱可以接收来自手机或其他蓝牙设备的音频数据,并通过扬声器播放。该项目展示了如何使用 STM32 微控制器和 HC05 蓝牙模块设计一个简单的蓝牙音箱。易于实现功能实用可扩展性强如果您需要开发一个蓝牙音箱,该项目可以作为参考。

走向CTO的路上... 2473

设计模式之代理模式ProxyPattern(六)

1、什么是代理模式?代理模式是23种设计模式中的一种。代理模式是一种结构型设计模式,它允许为其他对象提供一个替代品或占位符,以控制对这个对象的访问。2、代理模式的角色构成抽象主题(Subject):定义了真实主题和代理主题的共同接口,这样代理类可以通过实现该接口来代理真实主题。真实主题(Real Subject):定义了代理所代表的真实对象。代理(Proxy):持有对真实主题的引用,并实现了与真实主题一样的接口,客户端通过代理来访问真实主题,同时可以在访问真实主题前后进行一些额外操作。

徐泗空的博客 1466

【AUTOSAR实战】:CAN发送邮箱忙导致报文发送丢帧的问题调试

本文档主要讨论了CAN总线通信中可能出现的TC377传输错误的分析和解决方案。文章提供了如何通过检查CAN传输和接收过程、识别传输缓冲区的类型和状态以及选择适当的内存保护功能来检测和解决此类错误的见解。经过多次尝试和分析,得出的结论是,修改CANIF缓冲的尺寸并重新分配报文给各个buffer并不能解决问题。此外,发送中断被抢占可能导致报文无法发送,最终也将此因素排除。本文档还提到了一些可能导致发送流程调度出现问题的因素,例如CANIF中报文引用Buffer不合理。

weixin_46481662的博客 3141

设计模式之代理模式(ProxyPattern)

设计模式之代理模式(ProxyPattern) 文章目录设计模式之代理模式(ProxyPattern)一.代理模式(ProxyPattern)二.编程实例1.图片读取案例2.String类访问修饰符[]读写操作区分案例 一.代理模式(ProxyPattern) 代理模式是创建一个代理类,来帮助我们处理真实类,一般来说,这种代理类有点像明星的经纪人,如果你想接触到明星,必须先和他们的经纪人打交道。...

Worthy_Wang的博客 477

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

23种设计模式之代理模式(Proxy Pattern)

本篇文章将23种设计模式中的代理模式,此篇文章为一天学习一个设计模式系列文章,后面会分享其他模式知识。

小威的博客 2万+

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

设计模式之代理模式

代理模式是一种结构型设计模式,指为其他对象提供一种代理以控制对这个对象的访问。在代理模式中,代理对象充当了目标对象的替身,客户端对目标对象的访问实际上是通过代理对象来进行的。代理对象可以在客户端和目标对象之间增加额外的逻辑和功能,而客户端并不需要知道它访问的是代理对象而不是真正的目标对象。

FallingGod的博客 2709

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

DesignPattern设计模式

本文主要介绍了设计模式相关内容。包括软件设计的SOLID原则,设计模式的分类,单例模式的思想与实现,工厂模式的思想与实现,建造者模式的思想与实现,代理模式的思想与实现,责任链模式的思想与实现等内容。供个人学习使用。

专注Java和MySQL领域知识分享 1885

[DesignPattern]ProxyPattern

代理模式玩过扮白脸、扮黑脸的游戏吗?这就是代理要做的:控制和管理访问。还记得状态模式的那台糖果机吗?现在我们需要远程监控糖果机。(在开始编码之前,需要收集需求)。远程代理 远程代理就好比“远程对象的本地代表”。何谓“远程对象“?这是一种对象,活在不同的Java虚拟机(JVM)堆中。 你的客户对象所做的就像是在做远程方法调用,但其实只是调用本地堆中的”代理“上的方法,再由代理处理所有网络通信的低层细节

xy_cpp的博客 417

《设计模式》代理模式

定义代理模式就是为被访问的目标对象提供一个代理,此时代理对象充当访问对象和目标对象之间的媒介,通过代理对象实现对目标对象的访问。被代理的对象可以是远程对象、创建开销大的对象以及需要安全控制的对象,Java 中的代理按照代理的生成时机不同分为静态代理和动态代理,静态代理就是在编译期就生成代理对象,而动态代理是在 Java 运行时动态生成,而动态代理又分为 JDK 动态代理和 Cglib 动态代理两种。代理模式的角色组成抽象主题类(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法。

华仔仔的博客 4001

java设计模式之 代理模式【Proxy Pattern】 详解

首先我们举个例子说明一下什么是策略模式: 什么是代理模式呢?我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道 被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,代理人虽然不能干活,但是被 代理的人能干活呀。 比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理,表现在程序上时这样的: 先定义一种类型的女人: package com.cbf4lif...

蛋蛋淡淡定的庄园 299

代理模式 (ProxyPattern)

文章目录代理模式 (ProxyPattern)代理模式的定义代理模式的优点代理模式的缺点代理模式的结构代理模式的实现普通代理强制代理动态代理AOP使用场景 代理模式 (ProxyPattern) 代理模式的定义 为其他对象提供一种代理以控制对这个对象的访问。 代理模式的优点 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。 具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完

有问题的请私信或关注v【技术印记】留言 415

【设计模式】介绍常见的设计模式

本期内容主要介绍常见的设计模式。

爱编程爱运动的博客 4887

设计模式Python版 代理模式

介绍了GOF设计模式中的代理模式,用Python实现代理模式示例。介绍了远程代理,虚拟代理和虚拟代理Python示例。

分享、交流、合作 1498

设计模式—代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。 意图:为其他对象提供一种代理以控制对这个对象的访问。 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来...

dmxhcwk90096748的博客 99

【JS常见设计模式】代理模式

最近整理了一下关于js中设计模式相关的知识,如果有什么不足或错误请指出,互相学习,共同进步

weixin_46857470的博客 141
上一篇: 聊透常见设计模式之单例模式
下一篇: 【数据结构】什么是哈希表?为什么哈希表的查询时间复杂度是O
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值