结构型设计模式-1.代理设计模式

结构型设计模式-1.代理设计模式

结构型设计模式:利用类与类之间的关系(继承、组合),形成一种类与类之间的结构,通过这种结构提高代码的可拓展性、可维护性和可重用性。

一、简介

代理设计模式(Proxy Design Pattern)是一种结构型设计模式,它为其他对象提供一个代理,以控制对这个对象的访问。根据不同时期生成的代理对象,分为:

  • 静态代理:指代理类在编译时就已经确定。
  • 动态代理:指代理类在运行时动态生成。

代理模式可以用于实现懒加载、安全访问控制、日志记录等功能,其核心就是:屏蔽掉对原始对象的直接访问,为原始对象的能力提高增强。

其大致流程如下:

  1. 创建一个接口,定义代理类和被代理类共同实现的方法。
  2. 创建被代理类,实现这个接口,并且在其中定义实现方法。
  3. 创建代理类,也要实现这个接口,同时在其中定义一个被代理类的对象作为成员变量。
  4. 在代理类中实现接口中的方法,方法中调用被代理类中的对应方法。
  5. 通过创建代理对象,并调用其方法,方法增强。 这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改。

二、静态代理

1、简介

在静态代理中,需要手动创建代理类和被代理类,并且它们实现相同的接口或继承相同的父类。

2、基本流程

  1. 创建一个接口 / 抽象父类 / 父类:定义代理类和被代理类共同实现的方法。
  2. 创建被代理类:实现上述接口,并在其中定义实现方法。
  3. 创建代理类:同样实现上述接口,并在其中定义一个被代理类的对象作为成员变量。
  4. 在代理类中实现接口中的方法:在这些方法中,调用被代理类对象的对应方法。
  5. 通过创建代理对象并调用其方法,实现对被代理类方法的增强或修改。

3、简单示例

当涉及到继承关系时,我们可以使用静态代理来实现对继承类的功能增强。以下是一个示例代码,演示了如何使用静态代理来实现继承类的功能增强:

首先,我们有一个基础类 BaseClass,它定义了一些基本的操作:

// 基础类
class BaseClass {
    public void performOperation() {
        System.out.println("Performing base operation...");
    }
}

接下来,我们创建一个代理类 ProxyClass,它继承自基础类,并在其方法中添加额外的逻辑:

// 代理类,继承自基础类
class ProxyClass extends BaseClass {
    @Override
    public void performOperation() {
        // 在调用父类方法之前添加额外的逻辑
        System.out.println("Before performing operation...");

        // 调用父类方法
        super.performOperation();

        // 在调用父类方法之后添加额外的逻辑
        System.out.println("After performing operation...");
    }
}

在代理类中,我们重写了基础类的 performOperation() 方法,并在方法中通过调用 super.performOperation() 来执行基础类的功能。同时,在调用父类方法之前和之后,我们添加了额外的逻辑。

最后,我们可以使用代理类来执行操作,并观察功能增强的效果:

public class Main {
    public static void main(String[] args) {
        ProxyClass proxy = new ProxyClass();
        proxy.performOperation();
    }
}

在上述示例中,我们创建了 ProxyClass 的实例,并调用其 performOperation() 方法。在执行该方法时,代理类将在调用父类方法之前和之后添加额外的逻辑。这样,我们就实现了对继承类功能的增强,而不需要修改基础类的代码。

通过静态代理,我们可以在继承关系中对基础类的功能进行增强,而不影响基础类的原有实现。这样,我们可以通过代理类在调用父类方法之前或之后添加额外的逻辑,实现功能的灵活扩展。


下面是一个使用接口实现静态代理的示例代码:

首先,我们定义一个共同的接口 Image,它包含一个方法 display()

// 共同的接口
interface Image {
    void display();
}

接下来,我们创建一个具体的接口实现类 RealImage,实现了 Image 接口:

// 接口实现类
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}

然后,我们创建一个代理类 ImageProxy,它同时实现了 Image 接口,并拥有一个 RealImage 对象作为成员变量:

// 代理类
class ImageProxy implements Image {
    private RealImage realImage;

    public ImageProxy(String filename) {
        this.realImage = new RealImage(filename);
    }

    @Override
    public void display() {
        System.out.println("Loading image: " + realImage.getFilename());
        realImage.display();
    }
}

在代理类中,我们在 display() 方法中先输出加载图片的信息,然后调用 RealImage 对象的 display() 方法来显示图片。

最后,我们可以使用代理类来显示图片,并观察输出结果:

public class Main {
    public static void main(String[] args) {
        Image image = new ImageProxy("example.jpg");
        image.display();
    }
}

在上述示例中,我们创建了 ImageProxy 的实例,并调用其 display() 方法来显示图片。在执行该方法时,代理类会输出加载图片的信息,并通过调用 RealImage 对象的 display() 方法来实际显示图片。

通过使用接口实现静态代理,我们可以在代理类中控制对实现接口的对象的访问,并在调用其方法前后添加额外的逻辑。这样,我们可以对接口实现对象的方法进行增强、修改或限制,以满足特定的需求。

4、优点和缺点

优点:

  • 可以在不修改原始代码的情况下,通过代理对象对被代理对象进行功能增强、安全访问控制、日志记录等操作;也可以在代理对象中进行一些额外的操作,如记录日志、缓存等,以增强被代理对象的功能。
  • 代理对象可以隐藏被代理对象的具体实现,实现了客户端和被代理对象的解耦。

缺点:

  • 静态代理在编译时就已经确定代理类,后续维护可能修改源代码
  • 每个被代理类都需要手动创建一个代理类,当代理类较多或变动频繁时,会增加代码量和维护成本。

5、使用场景

下列是使用chatgpt学习中回答的使用场景和代码示例:

  1. 访问控制和安全性:
    静态代理可以用于控制对被代理对象的访问权限,确保只有具有合适权限的客户端可以访问被代理对象。
  2. 日志记录:
    静态代理可以用于记录对被代理对象的操作日志,方便后续的分析和监控。
  3. 性能监控:
    静态代理可以用于监控被代理对象的性能,统计方法的执行时间、调用次数等指标。
  4. 缓存:
    静态代理可以用于实现对被代理对象的结果进行缓存,提高系统响应速度。
  5. 事务管理:
    静态代理可以用于实现对被代理对象的事务管理,保证操作的原子性和一致性。
  6. 远程代理:
    静态代理可以用于实现远程对象的访问,隐藏底层的网络通信细节。

**【示例-缓存代理】**当涉及到数据库查询时,可以使用静态代理来实现查询缓存的功能。下面是一个简单的示例代码,演示了如何使用静态代理来实现数据库查询缓存的功能:

首先,我们需要定义一个共同的接口,代表数据库操作:

// 定义数据库操作的接口
interface Database {
    String queryData(String query);
}

然后,我们创建一个具体的数据库操作类,实现上述接口,用于执行实际的数据库查询:

// 实现数据库操作的具体类
class DatabaseImpl implements Database {
    @Override
    public String queryData(String query) {
        // 模拟执行数据库查询
        System.out.println("Executing database query: " + query);

        // 返回查询结果
        return "Result for query: " + query;
    }
}

接下来,我们创建一个代理类,用于添加查询缓存的逻辑:

// 创建代理类,添加查询缓存的逻辑
class DatabaseProxy implements Database {
    private Database database;
    private Map<String, String> cache; // 查询缓存

    public DatabaseProxy() {
        this.database = new DatabaseImpl();
        this.cache = new HashMap<>();
    }

    @Override
    public String queryData(String query) {
        // 先检查缓存中是否存在查询结果
        if (cache.containsKey(query)) {
            System.out.println("Retrieving cached result for query: " + query);
            return cache.get(query);
        }

        // 如果缓存中不存在查询结果,则执行实际的数据库查询
        String result = database.queryData(query);

        // 将查询结果存入缓存
        cache.put(query, result);

        return result;
    }
}

在代理类中,我们在queryData()方法中先检查缓存中是否存在查询结果。如果存在,直接从缓存中返回结果;如果不存在,代理类会调用实际的数据库操作类执行查询,并将查询结果存入缓存中。

最后,我们可以使用代理类来执行数据库查询,并观察缓存的效果:

public class Main {
    public static void main(String[] args) {
        Database database = new DatabaseProxy();

        // 第一次执行查询,将结果存入缓存
        String result1 = database.queryData("SELECT * FROM table1");
        System.out.println("Result 1: " + result1);

        // 第二次执行相同的查询,从缓存中获取结果
        String result2 = database.queryData("SELECT * FROM table1");
        System.out.println("Result 2: " + result2);
    }
}

在上述示例中,第一次执行查询时,会调用实际的数据库操作类执行查询,并将结果存入缓存。第二次执行相同的查询时,直接从缓存中获取结果,而不会再次执行数据库查询。

在这里插入图片描述

通过静态代理,我们实现了数据库查询缓存的功能,可以提高查询性能,减少对数据库的访问。这样,在相同的查询被频繁执行时,可以直接从缓存中获取结果,避免了重复的数据库查询操作。


【示例-安全代理】当涉及到安全性验证时,可以使用静态代理来实现安全代理的功能。下面是一个简单的示例代码,演示了如何使用静态代理来实现安全代理:

首先,我们需要定义一个共同的接口,代表敏感操作:

// 定义敏感操作的接口
interface SensitiveOperation {
    void performOperation();
}

然后,我们创建一个具体的敏感操作类,实现上述接口,用于执行实际的敏感操作:

// 实现敏感操作的具体类
class SensitiveOperationImpl implements SensitiveOperation {
    @Override
    public void performOperation() {
        System.out.println("Performing sensitive operation...");
    }
}

接下来,我们创建一个代理类,用于添加安全验证的逻辑:

// 创建代理类,添加安全验证的逻辑
class SecurityProxy implements SensitiveOperation {
    private SensitiveOperation sensitiveOperation;
    private String password; // 安全验证密码

    public SecurityProxy(String password) {
        this.sensitiveOperation = new SensitiveOperationImpl();
        this.password = password;
    }

    @Override
    public void performOperation() {
        // 进行安全验证
        if (authenticate()) {
            sensitiveOperation.performOperation();
        } else {
            System.out.println("Access denied! Invalid password.");
        }
    }

    private boolean authenticate() {
        // 进行安全验证的逻辑,比较输入密码和预设密码是否匹配
        String inputPassword = getPasswordFromUser();
        return inputPassword.equals(password);
    }

    private String getPasswordFromUser() {
        // 模拟从用户输入获取密码的逻辑
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter password: ");
        return scanner.nextLine();
    }
}

在代理类中,我们在performOperation()方法中进行安全验证。首先,用户需要输入密码进行验证;如果验证通过,则调用实际的敏感操作类执行敏感操作;如果验证失败,则拒绝访问。

最后,我们可以使用代理类来执行敏感操作,并观察安全验证的效果:

public class Main {
    public static void main(String[] args) {
        String password = "password123"; // 设置安全验证密码
        SensitiveOperation operation = new SecurityProxy(password);

        // 执行敏感操作,需要通过密码验证
        operation.performOperation();
    }
}

在上述示例中,执行敏感操作时,用户需要输入密码进行安全验证。只有当输入的密码与预设密码匹配时,才能执行实际的敏感操作。否则,将拒绝访问。

通过静态代理,我们实现了安全代理的功能,可以在执行敏感操作前进行安全验证,保护敏感操作的安全性。这样,在需要对敏感操作进行访问控制和验证的场景下,可以使用安全代理来确保只有经过验证的用户才能执行敏感操作。


【示例-远程代理】当涉及到远程对象的访问时,可以使用静态代理来实现远程代理的功能。下面是一个简单的示例代码,演示了如何使用静态代理来实现远程代理:

首先,我们需要定义一个共同的接口,代表远程服务:

// 定义远程服务的接口
interface RemoteService {
    void performTask();
}

然后,我们创建一个具体的远程服务类,实现上述接口,用于执行实际的远程任务:

// 实现远程服务的具体类
class RemoteServiceImpl implements RemoteService {
    @Override
    public void performTask() {
        System.out.println("Performing remote task...");
    }
}

接下来,我们创建一个代理类,用于封装远程通信的逻辑:

// 创建代理类,封装远程通信的逻辑
class RemoteProxy implements RemoteService {
    private RemoteService remoteService;

    public RemoteProxy() {
        // 在代理类中创建远程服务对象
        this.remoteService = new RemoteServiceImpl();
    }

    @Override
    public void performTask() {
        // 在代理类中添加远程通信的逻辑,模拟网络请求
        System.out.println("Sending request to remote server...");

        // 调用远程服务对象的方法
        remoteService.performTask();

        // 在代理类中添加远程通信的逻辑,模拟网络响应
        System.out.println("Received response from remote server...");
    }
}

在代理类中,我们在performTask()方法中添加远程通信的逻辑,模拟网络请求和响应的过程。首先,发送请求到远程服务器;然后,调用远程服务对象的方法执行远程任务;最后,接收远程服务器的响应。

最后,我们可以使用代理类来执行远程任务,并观察远程通信的效果:

public class Main {
    public static void main(String[] args) {
        RemoteService remoteService = new RemoteProxy();

        // 执行远程任务
        remoteService.performTask();
    }
}

在上述示例中,执行远程任务时,代理类将负责封装远程通信的逻辑。在调用远程服务对象的方法之前和之后,代理类会进行网络请求和响应的模拟操作。

通过静态代理,我们实现了远程代理的功能,可以封装远程通信的逻辑,隐藏底层的网络细节。这样,在需要访问远程对象时,可以使用远程代理来进行网络请求和响应的处理,简化了远程通信的操作。

三、动态代理

1、简介

动态代理(Dynamic Proxy)是一种在运行时动态生成代理类的设计模式。与静态代理不同,动态代理不需要手动编写代理类,而是通过Java的反射机制在运行时动态生成代理类,从而实现对被代理对象的代理

  • 基于JDK实现的动态代理,基于接口实现。
  • 基于CGLIB使用的动态代理,基于继承实现。

2、基本流程

  1. 定义一个接口,该接口是被代理类和代理类共同实现的接口。
  2. 创建一个实现了InvocationHandler接口的代理处理器类,该类中包含对方法的增强逻辑。
  3. 使用Proxy类的静态方法newProxyInstance()来创建代理对象,该方法接收三个参数:类加载器、被代理类实现的接口数组、代理处理器对象。
  4. 通过代理对象调用方法,代理处理器中的invoke()方法会被触发,并执行相应的增强逻辑。

3、优点和缺点

优点:

  • 动态代理可以在运行时动态地创建代理对象,适用于不同的接口和被代理类。
  • 它允许在不修改现有代码的情况下,对方法进行统一的增强或拦截,比如性能监控、事务管理、缓存等。
  • 动态代理可以减少代码量,避免手动编写大量的代理类。

缺点:

  • 动态代理的性能相对较低,因为在运行时需要使用反射机制来生成代理类和调用方法。
  • 动态代理只能代理接口,无法代理具体类,因为Java的单继承限制。

4、使用场景

  • 动态代理常用于AOP(面向切面编程)领域,可以通过动态代理在运行时动态地为目标对象添加横切逻辑,如日志记录、事务管理等。
  • 动态代理还可以用于远程方法调用(RMI)、缓存代理、延迟加载等场景,以实现更灵活、可扩展的系统架构。

5、基于 JDK 实现的动态代理

这个例子是基于 JDK 实现的动态代理示例。它演示了如何使用动态代理在运行时为目标对象添加额外的逻辑处理,而无需修改目标对象的代码:

/**
 * Author: shawn
 * Description: 定义一个共同的接口,代表数据库操作
 */
public interface DataBase {
    /**
     * 执行数据库查询操作
     *
     * @param query 查询语句
     * @return 查询结果
     */
    String query(String query);

    /**
     * 执行数据库删除操作
     *
     * @param delete 删除语句
     * @return 删除结果
     */
    String delete(String delete);
}

/**
 * Author: shawn
 * Description: 数据库操作的具体实现类
 */
public class DataBaseImpl implements DataBase {
    @Override
    public String query(String query) {
        // 模拟执行数据库查询
        System.out.println("Executing database query: " + query);

        // 返回查询结果
        return "Result for query: " + query;
    }

    @Override
    public String delete(String delete) {
        // 模拟执行数据库删除
        System.out.println("Executing database delete: " + delete);

        // 返回删除结果
        return "Result for delete: " + delete;
    }
}

/**
 * Author: shawn
 * Description: 该代理处理器类负责处理代理对象的方法调用,并在方法调用前后执行额外的逻辑操作。
 */
public class DatabaseInvocationHandler implements InvocationHandler {
    private Object target;

    public DatabaseInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * invoke()方法用于处理代理对象的方法调用。
     *
     * @param proxy  代理对象本身
     * @param method 被调用的方法对象
     * @param args   方法的参数数组
     * @return 方法的返回结果
     * @throws Throwable 异常信息
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法调用前的逻辑处理
        // 如:判断方法名,查看是否需要做记录日志
        String methodName = method.getName();
        if ("delete".equals(methodName)) {
            //是删除操作才做增强,不然还是调用原方法
            System.out.println("Recording deletion operation log");
        }
        System.out.println("Before method: " + methodName);
        System.out.println("Arguments: " + args[0]);

        // 调用被代理对象的方法
        Object result = method.invoke(target, args);

        // 在方法调用后的逻辑处理
        System.out.println("Result: " + result);
        System.out.println("After method: " + method);

        // 返回方法的返回结果
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建目标对象
        DataBase target = new DataBaseImpl();

        // 创建代理处理器
        DatabaseInvocationHandler handler = new DatabaseInvocationHandler();

        // 创建代理对象
        DataBase proxy = (DataBase) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler
        );

        // 调用代理对象的方法
        String queryResult = proxy.query("SELECT * FROM table");
        System.out.println("------------------------");
        String deleteResult = proxy.delete("DELETE FROM table WHERE id = 1");
        System.out.println("------------------------");


        // 输出方法的返回结果
        System.out.println("Query Result: " + queryResult);
        System.out.println("------------------------");
        System.out.println("Delete Result: " + deleteResult);
    }
}

在上面的代码中,我们使用了动态代理来为 DataBase 接口生成了一个代理类,并在代理类中增加了日志记录的功能。

具体地说,在 DatabaseInvocationHandler 类中的 invoke 方法中,我们根据方法名进行判断,只有当方法名是 "delete" 时才会进行日志记录。这样,在调用代理对象的 delete 方法时,会先输出 “Recording deletion operation log” 的提示信息,表示进行了删除操作的日志记录。

其他方法(比如 query 方法)没有满足条件,所以不会进行日志记录,只会输出方法调用前的提示信息和参数信息。

通过这种方式,我们实现了对原有接口的增强,根据不同的方法名来决定是否进行日志记录,从而实现了按需添加日志记录功能的动态代理类。这样的设计使得我们可以在不修改原有接口和实现类的情况下,为特定方法或特定场景添加额外的功能。

【下列是运行输出】:

Before method: query
Arguments: SELECT * FROM table
Executing database query: SELECT * FROM table
Result: Result for query: SELECT * FROM table
After method: public abstract java.lang.String structuralDesignPattern.proxy.dynamicProxy.DataBase.query(java.lang.String)
------------------------
Recording deletion operation log
Before method: delete
Arguments: DELETE FROM table WHERE id = 1
Executing database delete: DELETE FROM table WHERE id = 1
Result: Result for delete: DELETE FROM table WHERE id = 1
After method: public abstract java.lang.String structuralDesignPattern.proxy.dynamicProxy.DataBase.delete(java.lang.String)
------------------------
Query Result: Result for query: SELECT * FROM table
------------------------
Delete Result: Result for delete: DELETE FROM table WHERE id = 1

6、基于 CGLIB 实现的动态代理

这是一个基于CGLIB实现的动态代理示例。下面是代码的解析:

public class DataBaseImpl {
    public String query(String query) {
        // 模拟执行数据库查询
        System.out.println("Executing database query: " + query);

        // 返回查询结果
        return "Result for query: " + query;
    }

    public String delete(String delete) {
        // 模拟执行数据库删除
        System.out.println("Executing database delete: " + delete);

        // 返回删除结果
        return "Result for delete: " + delete;
    }
}

上述代码定义了一个数据库操作的具体实现类 DataBaseImpl,包含了 querydelete 两个方法。

public class DatabaseMethodInterceptor implements MethodInterceptor {
    private DataBaseImpl dataBase;

    public DatabaseMethodInterceptor() {
        this.dataBase = new DataBaseImpl();
    }

    public DatabaseMethodInterceptor(DataBaseImpl dataBase) {
        this.dataBase = dataBase;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 在方法调用前的逻辑处理
        // 如:判断方法名,查看是否需要做记录日志
        String methodName = method.getName();
        if ("delete".equals(methodName)) {
            //是删除操作才做增强,不然还是调用原方法
            System.out.println("Recording deletion operation log");
        }
        System.out.println("Before method: " + methodName);
        System.out.println("Arguments: " + objects[0]);

        // 调用被代理对象的方法
        Object result = method.invoke(dataBase, objects);

        // 在方法调用后的逻辑处理
        System.out.println("Result: " + result);
        System.out.println("After method: " + method);

        // 返回方法的返回结果
        return result;
    }
}

上述代码是基于CGLIB的方法拦截器 DatabaseMethodInterceptor,实现了 MethodInterceptor 接口。拦截器中的 intercept 方法用于在方法调用前后进行逻辑处理,包括记录日志和调用被代理对象的方法。

public class Main {
    public static void main(String[] args) {
        //cglib通过enhancer
        Enhancer enhancer = new Enhancer();
        //设置他的父类-要继承谁,给谁做代理
        enhancer.setSuperclass(DataBaseImpl.class);
        //设置方法拦截器,用于拦截方法,对方法做增强
        enhancer.setCallback(new DatabaseMethodInterceptor());
        // 创建代理对象
        DataBaseImpl proxy = (DataBaseImpl) enhancer.create();
        // 调用代理对象的方法
        String queryResult = proxy.query("SELECT * FROM table");
        System.out.println("--------cglib----------------");
        String deleteResult = proxy.delete("DELETE FROM table WHERE id = 1");
        System.out.println("--------cglib----------------");

        // 输出方法的返回结果
        System.out.println("Query Result: " + queryResult);
        System.out.println("--------cglib----------------");
        System.out.println("Delete Result: " + deleteResult);
    }
}

上述代码是 Main 类,包含了代理对象的创建和方法调用的示例。使用 Enhancer 创建代理对象,并设置父类和方法拦截器,最终通过 create 方法创建代理对象。然后,通过代理对象调用方法。

在运行该示例代码时,会输出方法调用的前后日志和结果。

请注意,为了使该示例代码正常运行,你需要在项目的依赖中添加 CGLIB 的相关依赖,或者将项目修改为spring boot项目。你可以将以下依赖添加到你的 pom.xml 文件中:

在运行代码时,你将看到方法调用的输出结果和日志记录,以及代理对象的增强效果。

Before method: query
Arguments: SELECT * FROM table
Executing database query: SELECT * FROM table
Result: Result for query: SELECT * FROM table
After method: public java.lang.String structuralDesignPattern.proxy.dynamicProxy.cglib.DataBaseImpl.query(java.lang.String)
--------cglib----------------
Recording deletion operation log
Before method: delete
Arguments: DELETE FROM table WHERE id = 1
Executing database delete: DELETE FROM table WHERE id = 1
Result: Result for delete: DELETE FROM table WHERE id = 1
After method: public java.lang.String structuralDesignPattern.proxy.dynamicProxy.cglib.DataBaseImpl.delete(java.lang.String)
--------cglib----------------
Query Result: Result for query: SELECT * FROM table
--------cglib----------------
Delete Result: Result for delete: DELETE FROM table WHERE id = 1
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

redvelet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值