设计模式代理模式

Posted Kant101

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式代理模式相关的知识,希望对你有一定的参考价值。

1. 概述

大力模式的设计冬季是通过代理对象来访问真实对象,通过建立一个对象代理类,由代理对象控制原对象的引用,从而实现对真实对象的操作。

在代理模式中,代理对象主要起一个中介的作用,用于协调与连接调用者(即客户端)和被调用者(即目标对象),在一定程度上降低了系统的耦合度,同时也保护了目标对象。但缺点是在调用者与被调用者之间增加了代理对象,可能会造成请求的处理速度变慢。

 

2. 静态代理

2.1 UML图

Subject: 抽象角色,声明了真实对象和代理对象的共同接口;

Proxy: 代理角色,实现了与真实对象相同的接口,所以在任何时刻都能够代理真实对象,并且代理对象内部包含了真实对象的引用,所以它可以操作真是对象,同时也可以附加其他的操作,相当于对真实对象进行封装;

RealSubject: 真实对象,是我们最终要引用的对象;

 

2.2 代码与测试

 

3. 动态代理

从静态代理模式的示例代码可以发现,静态代理的缺点显而易见,那就是当真实类的方法越来越多的时候,这样构建的代理类的代码量是非常大的,所以就产生了动态代理。

动态代理使用单个类(代理类)为具有任意数量方法的任意类(真实类)的多个方法调用提供服务。看到这里,就可以联想到动态代理的实现与反射密不可分。

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为JAVA语言的反射机制。

3.1 JDK 动态代理(接口代理)

(1)UML图

代理对象的类是继承了 Proxy 类的子类,并且实现了目标对象所实现了的接口。

 

(2)JDK 动态代理用法

JDK代理涉及到 java.lang.reflect 包中的 InvocationHandler 接口和 Proxy 类,核心方法是

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

JDK动态代理过程中实际上代理的是接口,是因为在创建代理实例的时候,依赖的是 java.lang.reflect 包中 Proxy 类的 newProxyInstance 方法,该方法的生效恰恰需要这个参数。

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

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    

 

在JDK动态代理中,实现了 InvocationHandler 接口的类可以看作是 代理类(因为类也是一种对象,所以为了面描述关系把代理类形容成了代理对象)。JDK动态代理就是围绕实现了 InvocationHandler 的代理类进行的。比如下面的类就是一个 InvocationHandler 的实现类,同时它也是一个代理类。

package com.pattern.design.proxy.jdkproxy;

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

public class JDKDynamicProxy implements InvocationHandler 
    private Object target;
    public JDKDynamicProxy(Object target) 
        this.target = target;
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        start();
        Object result = method.invoke(target, args);
        end();
        return result;
    
    public void start() 
        System.out.println("--------- 执行了 start -----------");
    
    public void end() 
        System.out.println("---------- 执行了 end -----------");
    

代理类一个最重要的方法就是 invoke() 方法,它有3个参数:

  • Object proxy: 动态代理对象;
  • Method method: 表示最终要执行的方法,method.invoke() 用于执行被代理的方法,也就是真正的目标方法;
  • Object[] args: 这个参数就是向目标方法传递参数;

 
我们构造好了代理类,现在就要使用它来实现我们对目标对象的调用,那么如何操作呢?
目标对象

// 接口
public interface Person 
    void wakeup();
    void sleep();


// 目标类
public class Student implements Person 
    private String name;
    public Student(String name) 
        this.name = name;
    
    @Override
    public void wakeup() 
        System.out.println("学生" + name + "醒了");
    
    @Override
    public void sleep() 
        System.out.println("学生" + name + "睡了");
    

使用代理类

package com.pattern.design.proxy.jdkproxy;

import com.pattern.design.proxy.jdkproxy.JDKDynamicProxy;
import com.pattern.design.proxy.jdkproxy.Person;
import com.pattern.design.proxy.jdkproxy.Student;

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

public class JDKDynamicProxyTest 
    public static void main(String[] args) 
        ClassLoader classLoader = JDKDynamicProxy.class.getClassLoader();
        Class<?>[] interfaces = Student.class.getInterfaces();
        InvocationHandler handler = new JDKDynamicProxy(new Student("张三"));
        Object obj = Proxy.newProxyInstance(classLoader, interfaces, handler);

        System.out.println(obj.getClass());
        Person proxy = (Person) obj;
        proxy.wakeup();
        System.out.println("==========================");
        proxy.sleep();
    

如果要用 JDK 动态代理的话,就需要知道目标对象的 类加载器目标对象的接口目标对象。构造完成后,我们就可以调用 Proxy.newProxyInstance 方法,然后把类加载器、目标对象的接口、目标对象绑定上去就行了。

这里需要注意一下 Proxy 类,它就是动态代理实际用到的代理类。

 

(3)JDK 动态代理原理

JDK的动态代理为什么需要被代理类一定要实现至少一个接口?

 
Proxy 位于 java.lang.reflect 包下,这同时也表明了动态代理的本质就是反射。

我们知道,JDK的动态代理是在运行过程中生成了新的代理类,通过代理类来执行了原对象的方法。要分析动态代理运行原理,就需要获取生成的代理类来进行分析。

可以通过如下的代码产生代理类的字节码文件:

public static void main(String[] args) 
    String proxyName = "com.design.pattern.study.$Proxy0";
    Class<?>[] interfaces = new Class[]Person.class;
    int accessFlags = Modifier.PUBLIC;
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    // 将字节数组写出到磁盘
    File file = new File("$Proxy0.class");
    try 
        OutputStream outputStream = new FileOutputStream(file);
        outputStream.write(proxyClassFile);
     catch (Exception e) 
        e.printStackTrace();
    

或者

public static void main(String[] args) 
    byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]Person.class);
    try (
        FileOutputStream fos = new FileOutputStream(new File("$Proxy.class"));
    ) 
        fos.write(bytes);
        fos.flush();
     catch (Exception e) 
        e.printStackTrace();
    

 
生成的字节码文件是$Proxy0.class,IDEA直接打开字节码文件,它会自动将字节码文件翻译成JAVA代码。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.design.pattern.study;

import com.design.pattern.proxy.jdkproxy.person.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public class $Proxy0 extends Proxy implements Person 
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    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 void wakeup() throws  
        try 
            super.h.invoke(this, m3, (Object[])null);
         catch (RuntimeException | Error var2) 
            throw var2;
         catch (Throwable var3) 
            throw new UndeclaredThrowableException(var3);
        
    

    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 sleep() throws  
        try 
            super.h.invoke(this, m4, (Object[])null);
         catch (RuntimeException | Error var2) 
            throw var2;
         catch (Throwable var3) 
            throw new UndeclaredThrowableException(var3);
        
    

    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"));
            m3 = Class.forName("com.design.pattern.proxy.jdkproxy.person.Person").getMethod("wakeup");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.design.pattern.proxy.jdkproxy.person.Person").getMethod("sleep");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
         catch (NoSuchMethodException var2) 
            throw new NoSuchMethodError(var2.getMessage());
         catch (ClassNotFoundException var3) 
            throw new NoClassDefFoundError(var3.getMessage());
        
    

  • 通过源码我们发现,$Proxy0 类继承了 Proxy 类,同时实现了 Person 接口。到这里,我们就能解释为什么JDK的动态代理一定需要被代理类实现一个接口而不能通过继承来实现这个问题了:因为JAVA中不支持多继承,而JDK的动态代理再创建代理对象时,默认让代理对象继承了Proxy类,所以JDK只能通过接口去实现动态代理。
  • $Proxy0 实现了Person接口,所以重写了接口中的两个方法($Proxy0同时还重写了Object类中的 equals()、hashCode()、toString() 方法)。所以当我们调用 wakeup() 方法时,先是调用 $Proxy0.wakeup() 方法,在这个方法中直接调用了 super.h.invoke() 方法,父类是 Proxy,父类中的 h 就是我们定义的 InvocationHandler(在调用Proxy.newProxyInstance()方法时将实现了InvocationHandler接口的代理类的对象设置进去了),所以这会儿调用到 JDKDynamicProxy 的 invoke() 方法时,会先经过 InvocationHandler 的 invoke() 方法,然后再通过反射 method,invoke() 去调用目标对象的方法,因此每次都会先执行 start() 方法,目标方法执行完后又会执行 end() 方法。

 

3.2 CGLIB 动态代理

从上面可以看出,JDK动态代理的前提条件是要有接口存在,那还有许多场景是没有接口的,这个时候就需要 cglib 动态代理了。

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。

CGLIB通过继承方式实现代理,CGLIB动态代理过程中生成的是实现类的子类。

(1) UML图

 

(2) 代码示例

package com.design.pattern.proxy.cglibproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 所需的代理类
 *
 * @author albert.tan
 * @date 2022/11/28
 */
public class CglibProxy implements MethodInterceptor 

    private Enhancer enhancer = new Enhancer();

    private Object target;

    public CglibProxy(Object target) 
        this.target = target;
    

    public Object getProxy() 
        // 设置所需创建子类的类
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable 
        start();
        Object result = method.invoke(target, objects);
        end();
        return result;
    

    public void start() 
        System.out.println("Hello, Welcome");
    

    public void end() 
        System.out.println("Bye Bye");
    

测试类

package com.design.pattern.proxy.cglibproxy;

import com.design.pattern.proxy.jdkproxy.person.Student;

/**
 * @author albert.tan
 * @date 2022/11/28
 */
public class TestCglibProxy 
    public static void main(String[] args) 
        CglibProxy proxy = new CglibProxy(new Student("张三"));
        Student student = (Student) proxy.getProxy();
        student.wakeup();
        student.sleep();
    

 

(3) CGLIB 动态代理原理

通过设置如下的代

以上是关于设计模式代理模式的主要内容,如果未能解决你的问题,请参考以下文章

代理模式

Java设计模式之代理模式

设计模式-代理模式

代理模式

代理模式

以下不属于代理模式缺点的是啥