代理模式(CGLIB和JDK)

news2024/12/1 0:46:18

文章目录

  • 一. 简介
    • 1. 生活中的代理案例
    • 2. java中代理模式的应用
    • 3. 什么是代理模式
  • 二. 如何实现代理
    • 1. 简介
    • 2. 静态代理
    • 3. 动态代理
  • 三. JDK中的动态代理
    • 1. 介绍
    • 2. 测试
    • 3. 原理分析
    • 4. 原理图
  • 四. CGLIB中的动态代理
    • 1. 简介
    • 2. 案例
    • 3. 底层原理分析

一. 简介

1. 生活中的代理案例

  • 房屋中介代理:客户手里没有房源信息,所以我们需要找一个中介来获得房源
  • 商品代购:我们手里没有低成本的商品,所以我们需要找代购者获得低成本商品

对于消费者而言,通过代购只需要关心自己的商品,而不需要关注如何获得商品,这样就对消费者进行了增强

2. java中代理模式的应用

  • 统一异常处理
  • Mybatis
  • Spring的AOP使用原理
  • 日志框架等

3. 什么是代理模式

代理模式是23种设计模式中的一种,属于结构型的模式。指一个对象本身不做实际的操作,而是通过其它对象得到自己想得到的结果,所以目标对象只需要关心自己实现的细节,而通过代理对象来实现功能的增强,可以扩展目标对象的功能。这体现了一种非常重要的编程思想,不能随便修改源码,如果需要修改源码,只需要通过代理的方式来实现功能的扩展。
在这里插入图片描述

二. 如何实现代理

1. 简介

正常情况下我们定义一个接口,即定义了一个规范,然后用一个类实现该接口,然后client调用实现类就可以使用基本功能。现在的情况下,我们不直接调用实现类,而是通过一个代理类实现接口,并加上前置通知和后置通知来增强该接口方法,然后client通过代理类来使用接口定义的功能
在这里插入图片描述

元素组成:

  • 接口:定义行为和规范
  • 被代理类:是目标对象
  • 代理类:功能增强

2. 静态代理

静态代理在 Java 中是一种非常常见的设计模式。在这种模式中,一个类(代理类)提供与另一个类(目标类)相同的接口,然后将调用转发到目标类。代理类和目标类都实现同一个接口。这种模式的一个常见用途是添加跨切面的行为,如日志记录、性能度量或者安全检查等,而不修改目标类的代码。

public interface IService {
    void doSomething();
}

public class RealService implements IService {
    public void doSomething() {
        // 实际业务逻辑
    }
}

public class ProxyService implements IService {
    private IService realService;

    public ProxyService(IService realService) {
        this.realService = realService;
    }

    public void doSomething() {
        // 在调用实际方法前可以执行一些操作
        System.out.println("Before doSomething...");
        realService.doSomething();
        // 在调用实际方法后也可以执行一些操作
        System.out.println("After doSomething...");
    }
}

在这个例子中,RealService 是我们的目标类,ProxyService 是我们的代理类。ProxyService 在调用 RealService 的 doSomething 方法之前和之后执行了一些额外的操作。静态代理有以下特点:

  • 代理类和目标类在编译时就确定下来,是静态的;
  • 代理类和目标类都实现同一个接口,代理类通过接口中定义的方法来调用目标类的实现;
  • 用户通过代理类来调用目标类的方法,代理类可以在调用目标类的方法之前或之后添加一些额外的处理。

静态代理的主要问题是如果接口定义了很多方法,那么代理类需要为每一个方法都写一次转发代码,这显然是非常繁琐的。此外,如果接口发生变化,那么代理类也需要跟着修改,这增加了维护的复杂性。这也是为什么在Java中,动态代理(如使用Java的 java.lang.reflect.Proxy 或者第三方库如CGLIB)常常被用于替代静态代理。

3. 动态代理

Java 中的动态代理是指在运行时动态地生成代理类并创建代理对象的技术。Java 的核心库提供了动态代理的支持,主要在 java.lang.reflect 包中。动态代理是一种方便和强大的工具,它的主要应用包括拦截器、模拟对象(mock objects)、数据库连接以及事务管理等。使用 Java 的动态代理需要定义一个调用处理器(Invocation Handler)。调用处理器是一个实现了 java.lang.reflect.InvocationHandler 接口的类,该接口只定义了一个方法:

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

这个 invoke 方法在代理实例上调用方法时会被调用。它接收三个参数:一个是代理对象本身,一个是在代理对象上调用的方法对象,一个是调用该方法时传递的参数。这个方法可以在调用目标方法前后添加额外的处理逻辑。下面是一个简单的动态代理的例子:

public interface IService {
    void doSomething();
}

public class RealService implements IService {
    public void doSomething() {
        System.out.println("Do something in RealService");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke method...");
        Object result = method.invoke(target, args);
        System.out.println("After invoke method...");
        return result;
    }
}

然后,你可以使用 Proxy 类的 newProxyInstance 方法来创建代理对象:

//一个是在代理对象上调用的方法对象
IService realService = new RealService();
//调用处理器
InvocationHandler handler = new MyInvocationHandler(realService);
//创建代理对象
IService proxyService = (IService) Proxy.newProxyInstance(
    IService.class.getClassLoader(),
    new Class<?>[]{IService.class},
    handler
);
proxyService.doSomething();  // 调用这个方法时,会触发 handler 的 invoke 方法

在这个例子中,我们为 RealService 创建了一个代理,当我们通过代理调用 doSomething 方法时,会先调用 MyInvocationHandler 的 invoke 方法。需要注意的是,Java 的动态代理只能为接口创建代理,不能为类创建代理。如果需要为类创建代理,可以使用如 CGLIB 这样的第三方库。

静态代理动态代理
编译时期代理类在编译时就已经存在代理类是在运行时动态生成
代码复杂度需要为每一个方法都写一次转发代码,如果接口有很多方法,代码量较大只需要写一个调用处理器(Invocation Handler),代码量相对较小
代码灵活性如果接口发生变化,代理类需要跟着修改,维护复杂调用处理器中通常只依赖接口而不依赖具体实现,如果接口发生变化,一般不需要修改调用处理器的代码
实现方式代理类和目标类都实现同一个接口,代理类直接调用目标类的方法使用 Java 的 Proxy 类和 InvocationHandler 接口,代理对象由 JVM 在运行时动态生成
应用场景当需要为一个或几个特定的类或接口创建代理时,静态代理更为简单直接当需要为多个或者不确定的类或接口创建代理,或者需要更灵活、更强大的代理功能(如拦截器、AOP)时,动态代理是更好的选择

三. JDK中的动态代理

1. 介绍

package java.lang.reflect;

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

InvocationHandler 接口(用来做方法拦截的)定义了一个方法:invoke。该方法在代理实例上调用方法时被调用。invoke 方法接受三个参数:

  1. proxy:代理实例。这是调用 invoke 方法的代理类的实例。(通过newProxyInstance创建代理实例)
  2. method:表示在代理实例上调用的接口方法的 Method 实例。这可以用来获取被调用方法的信息,如方法名、参数类型等。(执行目标方法的)
  3. args:包含在代理实例的方法调用传递的参数的对象数组。如果接口方法不带有参数,则为 null。(目标方法的参数)

invoke 方法的返回值是代理实例方法调用的返回值。也就是说,这个方法实际上决定了代理方法的行为。如果在代理实例的方法调用中抛出了异常,那么这个异常会被 invoke 方法抛出。在实际使用中,你通常会创建一个实现 InvocationHandler 接口的类,并在 invoke 方法中编写你的自定义逻辑,如在方法调用前后添加额外的处理等。
在 Java 中,InvocationHandler 是一个接口,用于处理代理实例上的方法调用。它是 Java 动态代理机制的核心接口

Java 的 java.lang.reflect.Proxy 类是实现动态代理的核心类。在动态代理的工作过程中,Proxy 类为指定的接口生成代理类及其对象,并通过调用 InvocationHandler 接口实现方法的动态调用。

public class Proxy implements java.io.Serializable {
    private static final long serialVersionUID = -2222568056686623797L;
    
    private InvocationHandler h;
    
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
        // ...
    }

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // ...
    }

    public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
        // ...
    }
}

Proxy 类提供了以下主要方法:

  1. getProxyClass(ClassLoader loader, Class<?>… interfaces): 该方法用于生成指定接口的代理类的 Class 对象。参数 loader 是类加载器,用于定义代理类;interfaces 是一组接口,代理类会实现这些接口。
  2. newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h): 该方法用于生成指定接口的代理对象。这个方法不仅生成代理类的 Class 对象,还创建了这个类的实例。参数 h 是 InvocationHandler 接口的实现,它定义了在代理对象上的方法调用的行为。
  3. getInvocationHandler(Object proxy): 该方法返回与指定代理对象关联的调用处理器。参数 proxy 是一个代理对象。

在使用 Proxy 类生成代理对象时,首先需要提供一个实现了 InvocationHandler 接口的类,这个类的 invoke 方法定义了在代理对象上的方法调用的行为。然后,可以调用 Proxy.newProxyInstance 方法生成代理对象。当在代理对象上调用方法时,调用将被转发到调用处理器的 invoke 方法。

2. 测试

创建student类

public class Student {
    private String name;

    public Student(String name) {
        this.name = name;
    }
    public Student() {
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

创建service接口

public interface StudentService {
    public void save();
    public Student query();
}

实现service接口(需要代理的一个类——需要增强的类)

public class IStudentServiceImpl implements StudentService {
    public void save() {
        System.out.println("Save student info");
    }

    public Student query() {
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;
        
    }
}

实现增强类

public class DaoTrascation {
    public void before(){
        System.out.println("开启事务操作");
    }
    public void after(){
        System.out.println("关闭事务操作");
    }
}

实现InvocationHandler接口

public class TransactionHandler implements InvocationHandler {
    //增强类对象
    private DaoTrascation daoTrascation;
    //需要代理的目标对象
    private Object object;
    public TransactionHandler(DaoTrascation daoTrascation,Object object){
        this.daoTrascation=daoTrascation;
        this.object=object;
    }
    
    //代理对象要执行的方法,实现目标方法执行,和功能增强
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret=null;
        //判断当前方法是否是save方法,是才做事务操作
        if("save".equals(method.getName())){
            daoTrascation.before();
            ret=method.invoke(object,args);
            daoTrascation.after();
        }else {
            method.invoke(object,args);
        }
        return ret;
    }
}

测试类代码

public class MybeanTest {
    @Test
    public void testMyBean(){
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),  //类加载器
                IStudentServiceImpl.class.getInterfaces(),   //目标类所实现的所有接口
                transactionHandler //方法拦截处理器
        );
        studentService1.save();
        studentService1.query();
    }
}

在这里插入图片描述

3. 原理分析

结合上面代码,通过反编译查看底层

修改测试类代码

 @Test
    public void testMyBean(){
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),
                IStudentServiceImpl.class.getInterfaces(),
                transactionHandler
        );
        studentService1.save(new Student());
        Student query = studentService1.query();
        saveProxyClass("/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest/src");
    }
    //使用生成字节码学习使用
    private void  saveProxyClass(String path) {
        //生成的代理类的字节码文件
        byte[] $proxy1 = ProxyGenerator.generateProxyClass("$Proxy1", IStudentServiceImpl.class.getInterfaces());
        try(FileOutputStream fileOutputStream = new FileOutputStream(new File(path + "$Proxy1.class"));) {
            fileOutputStream.write($proxy1);
        }  catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

允许代码生成字节吗文件

//代理对象首先实现了StudentService接口
ublic final class $Proxy1 extends Proxy implements StudentService {
//生成5个代理方法对象(因为Object对象中有一些方法)
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    //构造函数
    public $Proxy1(InvocationHandler var1) throws  {
        super(var1);
    }
    //equals方法(Object方法)
    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);
        }
    }
    //StudentService的query方法
    public final Student query() throws  {
        try {
            return (Student)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //object的tostring方法
    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);
        }
    }
    StudentService的save方法
    public final void save(Student var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    //object的hashcode方法
    public final int hashCode() throws  {
        try {
        //invoke方法,代理对象为当前对象,代理方法为m0,参数为null
        //h是Proxy类的InvocationHandeler类的实例
            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"));
            m4 = Class.forName("Service.StudentService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Service.StudentService").getMethod("save", Class.forName("pojo.Student"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4. 原理图

在这里插入图片描述

四. CGLIB中的动态代理

1. 简介

CGLIB是一个强大、高性能和高质量的第三方代码生成库。该库被Spring、Mybatis、Hibernate等第三方框架广泛应用,用以提供方法拦截操作。CGLIB属于开源项目,其CGLIB源码地址。CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。CGLIB向比与基于Java反射的JDK动态代理来说,CGLIB功能更加强大。JDK动态代理有个缺陷就是只能对接口进行代理,无法对单独普通类进行代理,而CGLIB则可以解决这一问题。(其实就是对JDK动态代理的一个补充)

2. 案例

  • 导入相关包
 <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>2.2.2</version>
      </dependency>
  • 创建目标类
public class IStudentServiceImpl implements StudentService {
    public void save(Student student) {
        System.out.println("Save student info");
    }

    public Student query() {
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;

    }
}
  • 创建事务操作
public class DaoTrascation {
    public void before(){
        System.out.println("开启事务操作");
    }
    public void after(){
        System.out.println("关闭事务操作");
    }
}
  • 进行事务拦截
public class CglibInterceptor implements MethodInterceptor {
    DaoTrascation daoTrascation;
    StudentService studentService;

    public CglibInterceptor(DaoTrascation daoTrascation, StudentService studentService) {
        this.daoTrascation = daoTrascation;
        this.studentService = studentService;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        daoTrascation.before();
        Object ret=methodProxy.invokeSuper(o,objects);
        daoTrascation.after();
        return ret;
    }
}
  • 测试
  @Test
    public void testMyBean(){
        DaoTrascation trascation = new DaoTrascation();

        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

在这里插入图片描述

3. 底层原理分析

  • 修改测试类
public void testMyBean(){
        //当Spring使用CGLIB创建代理对象时,CGLIB会生成字节码来创建新的类。这个 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY
        // 属性实际上是一个开关,当你设置了这个属性,CGLIB在生成新的类时,会在指定的目录下生成相应的.class文件,这样就可以查看或者进一步
        // 分析这些自动生成的类了。
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest");
        DaoTrascation trascation = new DaoTrascation();
        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

在这里插入图片描述
下面是代理类class文件:

public class IStudentServiceImpl$$EnhancerByCGLIB$$8b9b66bd extends IStudentServiceImpl implements Factory {
 public final void save(Student var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$save$0$Method, new Object[]{var1}, CGLIB$save$0$Proxy);
        } else {
            super.save(var1);
        }
    }
    public final Student query() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (Student)var10000.intercept(this, CGLIB$query$1$Method, CGLIB$emptyArgs, CGLIB$query$1$Proxy) : super.query();
    }
   }
  • 代理类继承了代理对象IStudentServiceImpl,并实现了其方法,所以说这种模式是用继承来实现的
  • 然后在save和query中都调用了拦截器中的interceptor方法实现了增强

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/619590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是Odoo ERP:部署方式、业务集成、成本投入、发展与未来

ERP部署的类型 如何部署ERP 系统&#xff1f;通过多年的发展&#xff0c;ERP系统的部署方式更加多样化&#xff0c;包括公有云或私有云部署、本地部署或整合不同环境的混合部署场景&#xff0c;企业可根据自身条件与应用场景加以选择。下面介绍了每种部署模式的主要优势&#…

kafka系统的CAP保证

kafka系统的CAP保证 CAP理论作为分布式系统的基础理论,它描述的是一个分布式系统在以下三个特性中&#xff1a; 一致性&#xff08;Consistency&#xff09;可用性&#xff08;Availability)分区容错性&#xff08;Partition tolerance&#xff09; 最多满足其中的两个特性。…

Window winget 包管理工具安装踩坑记录

一、简介 想在 window 上安装一个好用的包管理工具&#xff0c;mac 上用 homebrew, window 则考虑再三&#xff0c;使用官方提供的 winget。 winget 官方使用文档。 二、安装流程与踩坑记录 按 win x&#xff0c;选择 Powershell&#xff08;管理员&#xff09; 方式打开 c…

【严重】Linux kernel ksmbd 模块远程代码执行漏洞

漏洞描述 ksmbd 是 Linux kernel 的一个模块&#xff0c;用于提供 SMB&#xff08;Server Message Block&#xff09;文件共享协议的支持&#xff0c;SMB2_TREE_DISCONNECT 命令用于断开客户端与服务器之间的文件共享连接。 Linux kernel 受影响版本中&#xff0c;由于 ksmbd…

SpringBoot Filter使用

filter就是其他框架中的中间件,在SpringBoot启动时有很多默认加载的Filter 例如: characterEncodingFilterhiddenHttpMethodFilterhttpPutFormContentFilterrequestContextFilter Filter存在一个优先级, 数值越小越靠前 这有两个常量用来表顺顺序 Ordered.HIGHEST_PRECEDENCE…

webpack处理样式资源(css less sass scss)

Webpack 本身是不能识别样式资源的&#xff0c;所以我们需要借助 Loader 来帮助 Webpack 解析样式资源 一、处理css样式资源 去项目根目录新建css文件夹&#xff0c;在css文件夹下新增index.css文件&#xff0c;内容如图&#xff1a; 在src-main.js中引入css->index.css文…

各个AI模型写2023年广东高考作文大比拼

今天是一年一度的高考开始的日子&#xff0c;寒窗苦读十二年&#xff0c;剑指今朝。 作为过来人&#xff0c;当年的高考场景还历历在目。这里先预祝各位莘莘学子&#xff0c;高考正常发挥&#xff0c;旗开得胜&#xff0c;马到功成&#xff0c;考上心中理想的大学。 今天早上是…

《精通特征工程》学习笔记(3):特征缩放的效果-从词袋到tf-idf

1.TF-IDF原理 tf-idf 是在词袋方法基础上的一种简单扩展&#xff0c;它表示词频 - 逆文档频率。tf-idf 计算的不是数据集中每个单词在每个文档中的原本计数&#xff0c;而是一个归一化的计数&#xff0c;其中每个单词的计数要除以这个单词出现在其中的文档数量。 词袋bow(w, …

面向对象的特征三:多态性

1.多态性的理解&#xff1a; 可以理解为一个事物的多种形态。 2.何为多态性&#xff1a; 对象的多态性&#xff1a;父类的引用指向子类的对象&#xff08;或子类的对象赋给父类的引用&#xff09; 可以直接应用在抽象类和接口上 举例&#xff1a;Person p new Man(); O…

场景营销解密:出海品牌在全球市场的差异化策略

品牌出海是企业扩大国际市场份额、实现全球化发展的必经之路。然而&#xff0c;面对激烈的全球竞争和多样化的消费者需求&#xff0c;仅仅依靠传统的广告宣传手段已经无法满足品牌推广的需求。而场景营销则通过创造具有情境感的消费体验&#xff0c;更好地满足了消费者的参与需…

手写分布式事务的一种回滚方案。

1&#xff1a;项目架构 我一个朋友的公司基于实际业务的考虑&#xff0c;选择了多个单体项目来组建成一个分布式系统。&#xff08;对于目前来说分布式的系统最好采用微服务的架构来实现项目搭建。但基于许多客户只能采用内网的使用&#xff0c;微服务反而会影响项目的复杂度&a…

STM32ADC学习(一)

ADC 模拟/数字转换器 常见ADC类型 并联比较型工作示意图 ADC的特性参数 分辨率&#xff1a;ADC能辨别的最小模拟量&#xff0c;用二进制位数来表示。例如3.3V&#xff0c;12位&#xff0c;能辨别的最小模拟量就是&#xff1a;&#xff08;3.3/4096&#xff09;转换时间&#x…

【面试题HTTP中的两种请求方法】GET 和 POST 有什么区别?

GET 和 POST 有什么区别&#xff1f; 1.相同点和最本质的区别1.1 相同点1.2 最本质的区别 2.非本质区别2.1 缓存不同2.2 参数长度限制不同2.3 回退和刷新不同2.4 历史记录不同2.5 书签不同 总结代码示例 GET 和 POST 是 HTTP 请求中最常用的两种请求方法&#xff0c;在日常开发…

练手必备,20个Python实战项目含源代码

“读”代码是不能给你带来任何收益的&#xff0c;正如“读书”一样&#xff0c;如果在读的时候你不琢磨&#xff0c;保管你读完仨月准忘了一大半。真正需要的是去“试”代码&#xff0c;动手去调调代码&#xff0c;改改这改改那&#xff0c;看看把A变成B这个代码的结果会有什么…

c#使用RSA公钥解密

文章目录 前言一、解密函数1、上代码&#xff01;2、传入的字符串不是base64格式 二、在线验证总结 前言 新项目对接第三方&#xff0c;会把用户信息反正url里面rsa加密传过来&#xff0c;拿到后我解密出用户数据&#xff0c;只给了一个公钥&#xff0c;他们用的java、我用的c…

SpringBoot生成RESTful API文档

由于我一开始学习的SpringBoot是3以上版本&#xff0c;所以我这里用到的也是支持和SpringBoot3能够整合的SpringDoc 这里先说一下&#xff0c;其实SpringDoc就是Swagger3版本&#xff0c;我一开始整合的2版本&#xff0c;比较麻烦况且最后SpringBoot程序都启动不了了&#xff0…

怎样书写专业的落地性能测试计划?

目录 引言 什么是性能测试计划 性能测试计划包含的内容 背景 性能目标 压测范围 启停准则 性能指标 系统架构图 压测前准备 工具准备 数据准备 性能设计 监控设计 项目组织架构 成果输出 项目风险分析 引言 测试计划是软件测试流程中的一个重要步骤&#xff0c;它涉及到对软件…

面向教育行业的MDM(移动设备管理)解决方案

什么是面向教育的MDM 学校和教育机构的移动设备管理 &#xff08;MDM&#xff09; 通过将智能设备配置为适合教育用途&#xff0c;支持通过这些设备进行学习。面向教育的 MDM 解决方案允许组织的 IT 管理员或教学人员管理有助于学习的设备&#xff0c;如智能手机、平板电脑、笔…

十三、输出多个立方体并深度测试

第一部分概念 1&#xff09;由来&#xff1a; 深度测试&#xff1a;opengl的深度测试是指在片段着色器执行之后&#xff0c;利用深度缓冲所保存的深度值决定当前片段是否被丢弃的过程。 深度缓冲区和颜色缓冲区是差不多的&#xff0c;有相同的宽高度&#xff0c;并且一般在窗…

实验篇(7.2) 06. 通过安全隧道访问远端内网服务器 (FortiClient-SSL) ❀ 远程访问

【简介】直接映射服务器到公网&#xff0c;没有验证不安全&#xff1b;通过Web浏览器访问远程内网服务器&#xff0c;有验证也安全&#xff0c;但是支持的协议太少。那有没有即安全&#xff0c;又能支持所有协议的访问方法呢&#xff1f;我们来看看SSL VPN的隧道模式。 实验要求…