设计模式之代理模式

代理模式

代理模式是很常用的一种设计模式,主要目的是对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,代理只是个代理,它不会完成实际的业务逻辑,而是一层皮而已,但是对于客户端来说,它必须表现得就是客户端需要的真实实现。

代理模式是对象的结构模式。代理模式为其他对象提供一种代理以控制对这个对象的访问。
简单来说,在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

我们不必在目标对象内写清楚每一件事,而是可以交给代理对象代理从而对目标对象进行扩展。

静态代理

角色作用
抽象角色声明真实对象和代理对象的共同接口。
代理角色代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能够代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色代理角色所代表的真实对象,是我们最终要引用的对象。

在使用过程中我们需要注意:

  1. 代理角色,真实角色需要实现同一个抽象角色(接口)
  2. 代理角色需要持有真实角色的引用

经常使用的Java线程就是静态代理的典例,我们可以发现Thread implements Runnable,这其实就是一个代理角色,而我们new Runnable,这就是一个真实角色,赋值给Thread,这是不是就让代理角色持有真实角色的引用。因此,Thread和Runnable这种关系也是静态代理。

举个栗子

我们打算结婚,但是婚礼的细节我们不想管,这时候我们找到婚庆公司,让他们帮我们包揽婚礼的细节,这就是“代理模式”。既然是代理模式,那么就应该有一个代理角色和真实角色。例子中的“我们”就是真实角色,“婚庆公司”就是代理角色。

静态代理要怎么实现呢?

Step 1:声明抽象角色。

public interface Marry {
    public void marry();
}

Step 2:真实角色实现抽象角色接口。

@Override
public void marry() {
    System.out.println(this.getClass().getSimpleName() + "结婚啦");
}

Step 3:代理角色持有真实角色引用,实现抽象角色接口,附加其他操作。

private Walidake walidake;

public WeddingCompany(Walidake walidake) {
    this.walidake = walidake;
}

@Override
public void marry() {
    System.out.println("婚礼筹备");
    
    walidake.marry();
    
    System.out.println("婚礼结束");
    
}

Step 4:实际使用中,调用代理角色的方法对真实角色进行代理。

public static void main(String[] args) {
    Walidake walidake = new Walidake();
    WeddingCompany weddingCompany = new WeddingCompany(walidake);
    weddingCompany.marry();
}

动态代理

动态代理不同于静态代理的特点是它更为灵活,因为动态代理就是在运行期间动态生成代理类。我们沿用上面的例子,假设有五百个不一样的人要结婚,都交给婚庆公司来操办,那么按照静态代理的思路来做,我们需要写五百个真实角色,并且代理角色持有这五百个真实角色。这显然不合逻辑。这时候动态代理就应运而生了。
动态代理分两种,一种是基于接口实现的Java Proxy(Java自带的),一种是基于继承实现的cglib代理。下面会分别给出一个小demo,并且从源码解析角度来解析二者动态代理的实现。

Java Proxy

public class WeddingCompany implements InvocationHandler{

    private Object object;
    
    public WeddingCompany(Object object) {
        this.object = object;
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getProxy(){
        return (T)Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(), 
                object.getClass().getInterfaces(), 
                this);
    }
    

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if ("marry".equals(method.getName())) {
            System.out.println("婚礼筹备");
            
            method.invoke(object, args);
            
            System.out.println("婚礼结束");
        }
        return null;
    }

}

InvocationHandler相当于一个处理器,在invoke方法中我们能够操作真实对象,可以附加其他操作。而我们通过Proxy.newProxyInstance(…)方法生成代理。下面invoke参数的解释说明。

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

实现InvocationHandler接口并附加操作后,获取代理角色。

//第一个人
Walidake walidake = new Walidake();
Marry marry = new WeddingCompany(walidake).getProxy();
marry.marry();

System.out.println();

//第二个人
Other other = new Other();
Marry marry2 = new WeddingCompany(other).getProxy();
marry2.marry();

运行结果:

在这里插入图片描述

源码分析

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    //将我们传入的接口进行复制
    final Class<?>[] intfs = interfaces.clone();

    //得到proxy代理类
    Class<?> cl = getProxyClass0(loader, intfs);
    //取得proxy代理类的构造器
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    //取得InvocationHandler对象,即代理处理类对象
    final InvocationHandler ih = h;
    //判别是否为public类型
    if (!Modifier.isPublic(cl.getModifiers())) {
        //访问控制
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                cons.setAccessible(true);
                return null;
            }
        });
    }
    //反射构造proxy类对象
    //由此可以推测,proxy的构造方法应该是 public Proxy(InvocationHandler handler)
    return cons.newInstance(new Object[]{h});
}

我们大体上走完了一个proxy代理流程(了解到其实也是通过反射来实现对类的代理)那么我们又得想,那如何能得到proxy的Class,点击进入getProxyClass0(loader, intfs):

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    //接口最多不得超过65535
    //字节码文件中严格规定,这点不必纠结
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    //从缓存中取得proxy类
    return proxyClassCache.get(loader, interfaces);
}

我们继续追踪下去:

if (supplier != null) {
    // supplier might be a Factory or a CacheValue<V> instance:supplier可能是个Factory或者CacheValue
    //然后发现从其中取到了proxy类,说明应该定位到该方法内
    V value = supplier.get();
    if (value != null) {
        return value;
    }
}

定位到supplier.get()方法内:

//判断从valueFactory得到的proxy Class是否为空
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
return value;

点击进入ProxyClassFactory中的apply(key, parameter)内(注意:该类是Proxy的内部类):

@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
    for (Class<?> intf : interfaces) {
        Class<?> interfaceClass = null;
        try {
            //通过类加载器加载class到内存
            interfaceClass = Class.forName(intf.getName(), false, loader);
        } catch (ClassNotFoundException e) {
        }
        if (interfaceClass != intf) {
            throw new IllegalArgumentException(
                intf + " is not visible from class loader");
        }
        //判断class是否接口
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException(
                interfaceClass.getName() + " is not an interface");
        }
        //没有重复定义的接口
        if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
            throw new IllegalArgumentException(
                "repeated interface: " + interfaceClass.getName());
        }
    }
 //proxy Class所在包
    String proxyPkg = null; 
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

    //判断非public类是否在同一包下
    for (Class<?> intf : interfaces) {
        int flags = intf.getModifiers();
        if (!Modifier.isPublic(flags)) {
            accessFlags = Modifier.FINAL;
            String name = intf.getName();
            int n = name.lastIndexOf('.');
            String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
            if (proxyPkg == null) {
                proxyPkg = pkg;
            } else if (!pkg.equals(proxyPkg)) {
                throw new IllegalArgumentException(
                    "non-public interfaces from different packages");
            }
        }
    }

    if (proxyPkg == null) {
        // 等价于com.sun.proxy.
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }
 long num = nextUniqueNumber.getAndIncrement();
    //proxyPkg == null时,等价于com.sun.proxy.$proxy0,num会根据动态代理类的数量增加
    String proxyName = proxyPkg + proxyClassNamePrefix + num;

    //得到生成的字节码
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);
    try {
        //本地方法调用
        return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw new IllegalArgumentException(e.toString());
    }
}

一般的分析到这里就结束了,而我们应该更关注的是proxy类是如何组织生成的。因此,继续进入ProxyGenerator.generateProxyClass方法中。

public static byte[] generateProxyClass(final String name,
        Class[] interfaces) {
    ProxyGenerator gen = new ProxyGenerator(name, interfaces);
    //生成字节码细节
    final byte[] classFile = gen.generateClassFile();
    
    //将proxy代理类写入到硬盘中
    if (saveGeneratedFiles) {
        java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {
            public Void run() {
                try {
                    FileOutputStream file = new FileOutputStream(
                            dotToSlash(name) + ".class");
                    file.write(classFile);
                    file.close();
                    return null;
                } catch (IOException e) {
                    throw new InternalError("I/O exception saving generated file: "+ e);
                }
            }
        });
    }

    return classFile;
}

查看更多细节:

private byte[] generateClassFile() {
    //生成proxy代理类的hashcode,equals,toString方法
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);

    //添加各个接口的方法
    //这就是为什么我们能够通过代理调用接口方法实现的原因
    for (int i = 0; i < interfaces.length; i++) {
        Method[] methods = interfaces[i].getMethods();
        for (int j = 0; j < methods.length; j++) {
            addProxyMethod(methods[j], interfaces[i]);
        }
    }

    //检查返回类型
    for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
        checkReturnTypes(sigmethods);
    }

    //编译成class的相关内容
    try {
        methods.add(generateConstructor());

        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {
                fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;", ACC_PRIVATE
                                | ACC_STATIC));

                methods.add(pm.generateMethod());
            }
        }

        methods.add(generateStaticInitializer());

 } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception");
    }

    if (methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    }
    if (fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    }

    //将组织好的class文件写入到文件中
    cp.getClass(dotToSlash(className));
    cp.getClass(superclassName);
    for (int i = 0; i < interfaces.length; i++) {
        cp.getClass(dotToSlash(interfaces[i].getName()));
    }

    //设置为只读模式
    cp.setReadOnly();

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);

    try {
        //以下是class文件的结构,想深入了解的话可以看深入Java虚拟机
        //或者可以留意我后面相应的文章
        // u4 magic;
        dout.writeInt(0xCAFEBABE);
        // u2 minor_version;
        dout.writeShort(CLASSFILE_MINOR_VERSION);
        // u2 major_version;
        dout.writeShort(CLASSFILE_MAJOR_VERSION);

        cp.write(dout); // (write constant pool)// u2 access_flags;
        dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
        // u2 this_class;
        dout.writeShort(cp.getClass(dotToSlash(className)));
        // u2 super_class;
        dout.writeShort(cp.getClass(superclassName));

        // u2 interfaces_count;
        dout.writeShort(interfaces.length);
        // u2 interfaces[interfaces_count];
        for (int i = 0; i < interfaces.length; i++) {
            dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
        }

        // u2 fields_count;
        dout.writeShort(fields.size());
        // field_info fields[fields_count];
        for (FieldInfo f : fields) {
            f.write(dout);
        }

        // u2 methods_count;
        dout.writeShort(methods.size());
        // method_info methods[methods_count];
        for (MethodInfo m : methods) {
            m.write(dout);
        }

        // u2 attributes_count;
        dout.writeShort(0); // (no ClassFile attributes for proxy classes)

    } catch (IOException e) {
   throw new InternalError("unexpected I/O Exception");
    }

    return bout.toByteArray();
}

到这里,我们就解析完了Java Proxy。从代码中我们也不难看出真实角色要实现抽象角色的目的,就是为了底层能够生成class字节码从而生成代理角色,对真实角色进行代理。

cglib

cglib是一个强大的高性能的代码生成包,可以为那些没有接口的类创建模仿(moke)对象。上一小节我们说到Java Proxy是通过生成字节码,再把类加载进内存后实现Proxy进行动态代理的。同样地,cglib也是通过生成操作字节码的技术实现动态代理的。但与前者不同的是它并不直接操作字节码,而是通过一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。因此,cglib包要依赖于asm包,需要一起导入。
依然沿用上面的例子,这次我们不使用接口实现的方式。
实现细节:

public class WeddingCompany implements MethodInterceptor {

    
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz) {
        Enhancer en = new Enhancer();     
         //进行代理     
         en.setSuperclass(clazz);     
         en.setCallback(this);     
         //生成代理实例     
         return (T)en.create();     
     } 

    @Override
    public Object intercept(Object object, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {
        Object result = null;
        
        if ("marry".equals(method.getName())) {
            System.out.println("婚礼筹备");
            
            //通过继承的方法实现代理,因此这里调用的是invokeSuper
            result = methodProxy.invokeSuper(object, args);
            
            System.out.println("婚礼结束");
        }
        return result;
    }

}

MethodInterceptor是方法拦截器,我们能在这里做真实对象的附加操作,object就是我们的真实对象,method、args就是真实对象调用的方法和参数,methodProxy是方法代理。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使 用,因为它更快。在这个方法中,我们可以在调用原方法之前或之后注入自己的代码。

运行结果:
在这里插入图片描述

总结

我们把代理分为两种:静态代理和动态代理。

应用场景

静态代理主要用来处理少部分类的托管或者扩展。静态代理对于被代理的对象很固定,我们只需要去代理一个类或者若干固定的类,数量不是太多的时候,可以使用,而且其实效果比动态代理更好。
动态代理在运行期间动态生成代理类,需要消耗的时间会更久一点。优点是可以做很多类的扩展(譬如可以通过动态代理实现aop,hibernate使用cglib来代理单端多对一和一对一关联等)。而且如果一个类的接口发生了变化,那么静态代理这时候修改起来就很麻烦了。这就是说动态代理灵活的原因。

动态代理主流的实现有两种,一种是基于接口的Java Proxy的代理机制,一种是基于继承的cglib代理机制。两种也都有其应用场景。(视乎具体业务逻辑而言)

代理模式更多的应用场景(了解即可)

  1. 创建开销大的对象时候,比如显示一幅大的图片,我们将这个创建的过程交给代理去完成,我们称之为虚代理(Virtual Proxy)。
  2. 为网络上的对象创建一个局部的本地代理,比如要操作一个网络上的一个对象(网络性能不好的时候,问题尤其突出),我们将这个操纵的过程交给一个代理去完成,我们GoF称之为远程代理(Remote Proxy)。
  3. 对对象进行控制访问的时候,比如在Jive论坛中不同权限的用户(如管理员、普通用户等)将获得不同层次的操作权限,我们将这个工作交给一个代理去完成,我们称之为保护代理(Protection Proxy)。
  4. 当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。是指当调用真实的对象时,代理处理另外一些事。如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它;或当第一次引用一个持久对象时,将它装入内存;或在访问一个实际对象前,检查是否已经锁定它,以确保其他对象不能改变它。它们都是通过代理在访问一个对象时附加一些内务处理。又称智能引用(Smart Reference)代理。

在所有种类的代理模式中,虚拟(Virtual)代理、远程(Remote)代理、智能引用代理(Smart Reference Proxy)和保护(Protect or Access)代理是最为常见的代理模式。

在这里插入图片描述
我们发现,代理模式说白了就是做 “方法包装” 或做 “方法增强”。在面向切面编程中,算了还是不要吹捧这个名词了,在 AOP 中,其实就是动态代理的过程。比如 Spring 中,我们自己不定义代理类,但是 Spring 会帮我们动态来定义代理,然后把我们定义在 @Before、@After、@Around 中的代码逻辑动态添加到代理中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值