【Spring6】| GoF之代理模式(静态代理和动态代理)

目录

一:GoF之代理模式

1. 对代理模式的理解

2. 静态代理

3. 动态代理

3.1 JDK动态代理

3.2 CGLIB动态代理


一:GoF之代理模式

1. 对代理模式的理解

生活场景1:牛村的牛二看上了隔壁村小花,牛二不好意思直接找小花,于是牛二找来了媒婆王妈妈;这里面就有一个非常典型的代理模式。牛二不能和小花直接对接,只能找一个中间人。其中王妈妈是代理类牛儿是目标类。王妈妈代替牛二和小花先见个面。(现实生活中的婚介所)在程序中,对象A和对象B无法直接交互时

生活场景2:你刚到北京,要租房子,可以自己找,也可以找链家帮你找;其中链家是代理类你是目标类。你们两个都有共同的行为:找房子。不过链家除了满足你找房子,另外会收取一些费用的。(现实生活中的房产中介)【在程序中,功能需要增强时】

西游记场景:八戒和高小姐的故事,八戒要强抢民女高翠兰。悟空得知此事之后怎么做的?悟空幻化成高小姐的模样,代替高小姐与八戒会面。其中八戒是客户端程序悟空是代理类高小姐是目标类。那天夜里,在八戒眼里,眼前的就是高小姐,对于八戒来说,他是不知道眼前的高小姐是悟空幻化的,在他内心里这就是高小姐,所以悟空代替高小姐和八戒亲了嘴儿;这是非常典型的代理模式实现的保护机制。代理模式中有一个非常重要的特点:对于客户端程序来说,使用代理对象时就像在使用目标对象一样。【在程序中,目标需要被保护时】

业务场景:系统中有A、B、C三个模块,使用这些模块的前提是需要用户登录,也就是说在A模块中要编写判断登录的代码,B模块中也要编写,C模块中还要编写,这些判断登录的代码反复出现,显然代码没有得到复用,可以为A、B、C三个模块提供一个代理,在代理当中写一次登录判断即可!代理的逻辑是:请求来了之后,判断用户是否登录了,如果已经登录了,则执行对应的目标,如果没有登录则跳转到登录页面。【在程序中,目标不但受到保护,并且代码也得到了复用】

(1)代理模式是GoF23种设计模式之一,属于结构型设计模式。

(2)代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

(3)代理模式中的角色:

①代理类(代理主题)

②目标类(真实主题)

③代理类和目标类的公共接口(抽象主题):客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。

(4)代理模式的类图:

(5)代理模式在代码实现上,包括两种形式:

①静态代理。

②动态代理。

2. 静态代理

订单业务接口OrderService类

package com.bjpowernode.proxy.service;

// 订单业务接口
public interface OrderService {
    // 生成订单
    void generate();
    // 修改订单信息
    void modify();
    // 查看订单信息
    void detail();
}

接口的实现类OrderServiceImpl类

其中Thread.sleep()方法的调用是为了模拟操作耗时。

package com.bjpowernode.proxy.service.impl;

import com.bjpowernode.proxy.service.OrderService;

public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        try {
            // 模拟订单生成耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成...");
    }

    @Override
    public void modify() {
        try {
            // 模拟修改订单耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改...");
    }

    @Override
    public void detail() {
        try {
            // 模拟查看订单耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("正在查看详情...");
    }
}

客户端进行访问测试

package com.bjpowernode.proxy.client;

import com.bjpowernode.proxy.service.OrderService;
import com.bjpowernode.proxy.service.impl.OrderServiceImpl;

public class Test {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl();
        orderService.generate();
        orderService.modify();;
        orderService.detail();
    }
}

假如项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化;于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长?如果是你,你该怎么做呢?

第一种方案:直接修改Java源代码,在每个业务方法中添加统计逻辑,如下:

直接在原来的代码片段中进行修改!

package com.bjpowernode.proxy.service.impl;

import com.bjpowernode.proxy.service.OrderService;

public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        // 执行前计时
        long begin = System.currentTimeMillis();

        try {
            // 模拟订单生成耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成...");
        // 执行后计时
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        // 执行前计时
        long begin = System.currentTimeMillis();

        try {
            // 模拟修改订单耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改...");
        // 执行后计时
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        // 执行前计时
        long begin = System.currentTimeMillis();

        try {
            // 模拟查看订单耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("正在查看详情...");
        // 执行后计时
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }
}

缺点:需求可以满足,但显然是违背了OCP开闭原则,并且相同的代码写了很多遍;所以这种方案不可取!

第二种方案:再编写一个子类OrderServiceImplSub继承OrderServiceImpl,在子类中重写每个方法,代码如下:

使用继承(泛化)关系,以子类的方式存在,在子类方法中使用super关键子调用父类的方法,耦合度比较高!

package com.bjpowernode.proxy.service.impl;

public class OrderServiceImplSub extends OrderServiceImpl{
    @Override
    public void generate() {
        // 执行前计时
        long begin = System.currentTimeMillis();
        // 调用父类的方法
        super.generate();
        // 执行后计时
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        // 执行前计时
        long begin = System.currentTimeMillis();
        // 调用父类的方法
        super.modify();
        // 执行后计时
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        // 执行前计时
        long begin = System.currentTimeMillis();
        // 调用父类的方法
        super.detail();
        // 执行后计时
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }
}

这种方式也可以解决,但是存在两个问题:

①缺点:虽然解决了OCP开闭原则,但是采用了继承的方式,导致代码之间的耦合度较高。

②缺点:代码也没有得到复用。

第三种方案:使用代理模式(静态代理)

使用关联关系,以属性的方式存在,代理对象和目标对象实现同一个接口,并把目标对象通过有参构造传过来,耦合度比继承低!

代理对象

前面最初编写的OrderService接口就是公共接口、OrderServiceImpl就是目标对象,现在就需要编写一个代理对象;代理对象和目标对象要具有相同的行为,就要实现同一个或同一些接口!

package com.bjpowernode.proxy.service.impl;

import com.bjpowernode.proxy.service.OrderService;

// 代理对象(代理对象和目标对象要具有相同的行为,就要实现同一个或同一些接口)
// 客户端在使用代理对象的时候就像在使用目标对象一样。
public class OrderServiceProxy implements OrderService {
    // 将目标对象作为代理对象的一个属性,这种关系叫做关联关系,比前面的继承关系的耦合度低。
    // 注意:这里要写一个公共接口类型,因为公共接口耦合度低。
    private OrderService target;
    // 提供构造方法进行赋值
    public OrderServiceProxy(OrderService target) {
        this.target = target;
    }

    @Override
    public void generate() { // 代理方法
        // 增强
        long begin = System.currentTimeMillis();
        // 调用目标对象的目标方法
        target.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {// 代理方法
        // 增强
        long begin = System.currentTimeMillis();
        // 调用目标对象的目标方法
        target.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {// 代理方法
        // 增强
        long begin = System.currentTimeMillis();
        // 调用目标对象的目标方法
        target.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }
}

编写测试

先创建一个目标对象,然后把目标对象作为属性传到代理对象当中,因为我们在代理对象当中的构造方法中使用的是它们共有的接口OrderService作为属性参数;所以就可以通过代理对象的代理方法完成操作!

package com.bjpowernode.proxy.client;

import com.bjpowernode.proxy.service.OrderService;
import com.bjpowernode.proxy.service.impl.OrderServiceImpl;
import com.bjpowernode.proxy.service.impl.OrderServiceProxy;

public class Test {
    public static void main(String[] args) {      
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService proxy = new OrderServiceProxy(target);
        // 调用代理对象的代理方法
        proxy.generate();
        proxy.modify();
        proxy.detail();


    }
}

优点:解决了OCP问题,采用代理模式的关联关系,可以降低耦合度!

缺点:类爆炸,假设系统中有1000个接口,那么每个接口都需要对应代理类,这样类会急剧膨胀;仍然未解决代码复用问题,不好维护。
怎么解决类爆炸问题?
可以使用动态代理来解决这个问题;动态代理还是代理模式,只不过添加了字节码生成技术,可以在内存中为我们动态的生成一个class字节码,这个字节码就是代理类。

3. 动态代理

(1)在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量和解决代码复用的问题!

(2)在内存当中动态生成类的技术常见的包括:

JDK动态代理技术:只能代理接口

CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类底层是通过继承的方式实现的。性能比JDK动态代理要好(底层有一个小而快的字节码处理框架ASM)

Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。(前面MyBatis已经讲过)

OrderService接口

package com.bjpowernode.dynamic.proxy;

// 订单业务接口
public interface OrderService {
    // 生成订单
    void generate();
    // 修改订单信息
    void modify();
    // 查看订单信息
    void detail();
}

OrderServiceImpl实现类

package com.bjpowernode.dynamic.proxy;

public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        try {
            // 模拟订单生成耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成...");
    }

    @Override
    public void modify() {
        try {
            // 模拟修改订单耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改...");
    }

    @Override
    public void detail() {
        try {
            // 模拟查看订单耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("正在查看详情...");
    }
}

3.1 JDK动态代理

需求:业务增强,获取每个方法的执行时间,我们采用动态代理的方式,在内存中生成代理类!

客户端程序

第一步:还是创建目标对象。

第二步:创建代理对象,这个代理对象是动态生成的,此时需要Proxy类newProxyInstance()方法,这个方法有三个参数:
①第一个参数:ClassLoader loader-类加载器,这个类加载器有什么用呢?
在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中;加载类就需要类加载器,所以这里需要指定类加载器;并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个

第二个参数:Class<?>[] interfaces-代理类要实现的接口,代理类和目标类要实现同一个接口或一些接口;在内存中生成代理类的时候,这个代理类是需要告诉它实现哪些接口的

第三个参数:InvocationHandler h-调用处理器;InvocationHandler 被翻译为:调用处理器,是一个接口;在调用处理器接口中编写的增强的业务代码。既然是接口,就要编写接口的实现类。

package com.bjpowernode.dynamic.client;

import com.bjpowernode.dynamic.proxy.OrderService;
import com.bjpowernode.dynamic.proxy.OrderServiceImpl;

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        Object obj = Proxy.newProxyInstance(
                     target.getClass().getClassLoader(),                     
                     target.getClass().getInterfaces(),
                     "调用处理器");
        // 调用代理对象的代理方法
        // 返回的是一个Object,实现的是同一个OrderService接口,所以可以向下转型
        OrderService proxyObj = (OrderService)  obj;
        proxyObj.generate();
        proxyObj.modify();
        proxyObj.detail();

    }
}

 第三步: 着重解决上面的第三个参数:调用处理器的参数,肯定要写一个实现类去实现InvocationHandler接口

(1)为什么强行要求你必须实现InvocationHandler接口?
因为一个类实现接口就必须实现接口中的方法;并且必须实现invoke()方法,因为JDK在底层调用invoke()方法的程序已经提前写好了!
注意:invoke()方法不是我们程序员负责调用的,是JDK负责调用的。
(2) invoke方法什么时候被调用呢?

当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。

(3)invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数,我们可以在invoke方法的大括号中直接使用。invoke方法的三个参数:
第一个参数:Object proxy 代理对象的引用,这个参数使用较少
第二个参数:Method method 目标对象上的目标方法。(要执行的目标方法就是它)
第三个参数:Object[] args 目标方法上的实参

注意:invoke方法执行过程中,先使用TimeInvocationHandler(target)参数把目标对象传过来,然后在处理器中使用method参数通过反射机制来调用目标对象的目标方法;所以我们就需要目标对象,所以我们就提供了目标对象的属性,提供了构造方法用来赋值,根据前面TimeInvocationHandler(target)参数把传过来的目标对象进行赋值!

完整客户端程序

package com.bjpowernode.dynamic.client;

import com.bjpowernode.dynamic.proxy.OrderService;
import com.bjpowernode.dynamic.proxy.OrderServiceImpl;

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        Object obj = Proxy.newProxyInstance(
            target.getClass().getClassLoader(),         
            target.getClass().getInterfaces(),
            new TimeInvocationHandler(target));
        // 调用代理对象的代理方法
        // 返回的是一个Object,实现的是同一个OrderService接口,所以可以向下转型
        OrderService proxyObj = (OrderService)  obj;
        // 调用方法是invoke方法里面的增强代码会执行,但是目标对象的目标方法并不会执行,
        // 所以需要在new TimeInvocationHandler()中把target目标对象传过去
        proxyObj.generate();
        proxyObj.modify();
        proxyObj.detail();

    }
}

用来增强代码的处理器程序

package com.bjpowernode.dynamic.client;

import com.bjpowernode.dynamic.proxy.OrderService;

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

public class TimeInvocationHandler implements InvocationHandler {

    // 目标对象
    private Object target;
    // 构造方法,给成员变量赋值
    public TimeInvocationHandler(Object target) {
        this.target = target;
    }

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

        // 增强代码
        long begin = System.currentTimeMillis();

        // 调用目标对象的目标方法(通过反射机制)
        // 方法四要素:哪个对象target,哪个方法method,传什么参数args,返回什么值retValue
        Object retValue = method.invoke(target, args);

        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");

        return retValue;
    }
}

 细节:对于上面的代码一定要注意返回值的问题

现在我们的方法都是void没有返回值,看起来通过反射机制调用方法返回retValue没什么用;但是我们一旦提供一个有返回值的参数,如果不去return retValue,在客户端就获取不到这个值,获取到的就会是一个null!

封装一个工具类

package com.bjpowernode.dynamic.util;

import com.bjpowernode.dynamic.client.TimeInvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyUtil {

    // 封装一个工具方法,可以通过这个方法获取代理对象。
    public static Object newProxyInstance(Object target){
        // 底层是调用的还是JDK的动态代理。
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimeInvocationHandler(target));
    }

}

使用封装后的工具类

3.2 CGLIB动态代理

CGLIB既可以代理接口,又可以代理类,底层采用继承的方式实现!

所以被代理的目标类不能使用final关键字修饰!

第一步:引入CGLIB的依赖

 <!--引入CJLIB的依赖-->
<dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>3.3.0</version>
</dependency>

第二步:准备一个没有实现接口的类(可以代理类)

package com.bjpowernode.proxy.service;


public class UserService {

    // 目标方法
    public boolean login(String username, String password){
        System.out.println("系统正在验证身份...");
        if ("admin".equals(username) && "123".equals(password)) {
            return true;
        }
        return false;
    }

    // 目标方法
    public void logout(){
        System.out.println("系统正在退出...");
    }
}

第三步:客户端程序,使用CGLIB在内存中为UserService类生成代理类,并创建对象

①首先创建一个字节码增强器,创建Enhancer对象。

②因为cglib底层使用的是继承的方式实现,所以需要告诉cglib要继承哪个类,调用增强器的setSuperclass方法,参数是我们的要代理类的.class。

③调用增强器的setCallback方法,设置回调接口,参数是一个方法拦截器,这个拦截器需要我们自己编写一个类去实现MethodInterceptor接口。

④调用增强器的create方法,生成源码,编译class并加载到JVM,并创建代理对象。

⑤最后一步,调用代理对象的代理方法。

package com.bjpowernode.proxy.client;

import com.bjpowernode.proxy.service.TimerMethodInterceptor;
import com.bjpowernode.proxy.service.UserService;
import net.sf.cglib.proxy.Enhancer;

public class Client {
    public static void main(String[] args) {
        // 第一步:创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 第二步:告诉cjlib要继承那个类
        enhancer.setSuperclass(UserService.class);
        // 第三步:设置回调接口,参数是方法拦截器
        enhancer.setCallback(new TimerMethodInterceptor());
        // 第四步:生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();
        // 第五步:调用方法
        // 调用代理对象的代理方法。
        boolean success = userServiceProxy.login("admin", "123");
        System.out.println(success ? "登录成功" : "登录失败");

        userServiceProxy.logout();
    }
}

可以输出以下userServiceProxy(代理对象),记住格式,根据这个名字可以推测框架底层是否使用了CGLIB动态代理;其实底层是:

com.bjpowernode.proxy.service.UserService$$EnhancerByCGLIB$$4a68228b@2471cca7 

这个类继承了userServiceProxy这个代理对象

第四步:着重解决第三步的回调函数,方法拦截器问题

和JDK动态代理原理差不多,在JDK动态dialing中需要提供的是InvocationHandler,而CGLIB动态代理提供的是:MethodInterceptor,所以也要编写一个方法实现这个接口,并重写MethodInterceptor接口中的intercept()方法,该方法有4个参数:

第一个参数目标对象;

第二个参数目标方法;

第三个参数目标方法调用时的实参;

第四个参数代理方法;

package com.bjpowernode.proxy.service;

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

import java.lang.reflect.Method;

public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前增强
        long begin = System.currentTimeMillis();
        // 调用目标,通过代理对象的invokeSuper方法
        Object retValue = methodProxy.invokeSuper(target, objects);
        // 后增强
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
        // 一定要返回
        return retValue;
    }
}

 测试结果

这是因为cjlib底层的问题,JDK高于8的就会出现这个问题!怎么解决呢?设置两个参数!

 ①先点击Modify options

② 选择 Add VM options

 ③设置以下两个参数

--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED

 ④正常执行

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

内网渗透之某后渗透利用【网络安全】

0x01 工具介绍 工具原理 Mimikatz 的主要原理是在 Windows 系统中用户登录后系统会将身份凭证存储于lsass.exe进程的内存当中&#xff0c;Mimikatz 通过注入lsass.exe进程读取进程内存&#xff0c;从中获取对应的明文密码。 常见问题 在 Windows Vista 系统之后不再存储 LM…

0203优先级下的调度问题_环_拓扑排序-有向图-数据结构和算法(Java)

1 概述 在和有向图相关的实际应用中&#xff0c;有向环特别的重要。在实际应用中&#xff0c;一般只会重点关注其中的一小部分&#xff0c;或者只想知道它们是否存在。 2 调度问题 一种应用广泛的模型是给定一组任务并安排它们的执行顺序&#xff0c;限制条件是这些任务的执…

机器学习:逻辑回归模型算法原理(附案例实战)

机器学习&#xff1a;逻辑回归模型算法原理 作者&#xff1a;i阿极 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x1f44d;收藏&#…

Github学生包申请秒过经验并使用Copilot

写在前面 前提是在校学生&#xff0c;且有学校邮箱&#xff0c;当然你也得有Github账户前往学信网下载 教育部学籍在线验证报告将报告转换成英文版本&#xff0c;我用的是手机夸克自带的拍照翻译功能 具体流程 设置Github个人信息 来到 https://github.com/settings/profil…

如何用Postman做接口自动化测试?没有比这个更详细的了

目录 前言 什么是自动化测试 自动化测试有哪些分类 为什么需要自动化测试 Postman自动化测试演示 1.新建集合 2.新建接口 3.填写自动化测试脚本 4.录入所有接口 5.执行自动化测试 前言 什么是自动化测试 把人对软件的测试行为转化为由机器执行测试行为的一种实践。 …

腾讯Coding平台学习笔记二:自定义团队插件的使用方法

目录一、前言二、系统环境三、工作目标四、流水线设置五、开发工具5.1 教程地址5.2 开发工具程序结构5.3 qciplugin.yml文件5.4 main.py文件六、插件的安装6.1 打包成zip6.2 上传zip包6.3 构建新插件6.4 质量门禁7、流水线设置7.1 添加质量管理阶段节点7.2 添加其它动作八、流水…

cookie和session的原理以及在Servlet中的应用

文章目录简介cookiecookie的实质及实现原理cookie在Servlet的应用sessionsession的实质及实现原理session在Servlet中的应用HttpServletRequest&#xff0c;Session&#xff0c;ServletContext简介 cookie保存在客户端&#xff0c;session保存在服务器端。二者均用于描述会话的…

【第十一届“泰迪杯”数据挖掘挑战赛】B题产品订单的数据分析与需求预测“解题思路“”以及“代码分享”

【第十一届泰迪杯B题产品订单的数据分析与需求预测产品订单的数据分析与需求预测 】第一大问代码分享&#xff08;后续更新LSTMinformer多元预测多变量模型&#xff09; PS: 代码全写有注释&#xff0c;通俗易懂&#xff0c;包看懂&#xff01;&#xff01;&#xff01;&…

RK3568平台开发系列讲解(驱动基础篇)IO 模型的分类

🚀返回专栏总目录 文章目录 一、阻塞 IO二、非阻塞 IO三、IO 多路复用四、信号驱动五、异步 IO沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将针对IO模型进行分类。 假设有这样一个场景,从磁盘中循环读取 100M 的数据并处理,磁盘读取 100M 需要花费 20 秒的…

Transformer在计算机视觉中的应用-VIT、TNT模型

上期介绍了Transformer的结构、特点和作用等方面的知识&#xff0c;回头看下来这一模型并不难&#xff0c;依旧是传统机器翻译模型中常见的seq2seq网络&#xff0c;里面加入了注意力机制&#xff0c;QKV矩阵的运算使得计算并行。 当然&#xff0c;最大的重点不是矩阵运算&…

【数据结构】树的概念

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

网络基础认识

目录 一、计算机网络背景 1.1 网络发展 1.2 "协议"由来 二、网络协议初识 2.1 协议分层 2.2 OSI七层模型 2.3 TCP/IP五层模型 三、网络协议栈 四、数据包封装与分用 五、网络传输基本流程 5.1 同局域网的两台主机通信 5.2 跨网络的两台主机通信 六、网络…

MySQL高级第六篇:数据库性能分析与优化

MySQL高级第六篇&#xff1a;数据库性能分析与优化一、数据库服务器优化步骤概述二、慢查询日志&#xff1a;记录执行慢的SQL1. 开启慢查询日志2. 设置long_query_time3. 查看慢查询数与慢查询SQL三、分析查询语句&#xff1a;EXPLAIN1. 概述2.EXPLAIN各列的含义一、数据库服务…

【leetCode189】轮转数组

作者&#xff1a;日出等日落 专栏&#xff1a;leetCode刷题训练 要成功不需要什么特别的才能&#xff0c;只要把你能做的小事做得好就行了。 ——维龙 目录 题目&#xff1a; 第一种方法&#xff1a; 第二种方法&#xff1a; 第三种方法&#xff1a; 今…

UDP、TCP三次握手和四次挥手

-----UDP与TCP----- 相同点 tcp、udp都是工作在传输层进行数据传输&#xff08;二进制标识文本或者视频或者图片&#xff09; 不同点 tcp基于连接&#xff0c;保障传输的安全udp基于非连接&#xff0c;保障传输的速度 -----TCP的三次握手----- 过程 为什么不是两次握手&a…

PMP考试备考:你不知道的8个常考概念

PMP考试即将到来&#xff0c;为便于广大考生在考试前查漏补缺&#xff0c;给大家准备了PMP考试中常考的八个重要概念&#xff0c;包括敏感性分析、德尔菲技术等&#xff0c;快来看看吧。 01敏感性分析 敏感性分析有助于确定哪些风险对项目具有最大的潜在影响。它有助于理解项…

UWB芯片DW3000之双边双向测距法

目录 双边双向测距 使用四个信息 使用三个信息 双边双向测距 使用四个信息 双边双向测距(DS-TWR)是基本的单边双向测距的扩展&#xff0c;其中使用两次往返时间测量并结合给出飞行时间结果&#xff0c;即使在相当长的响应延迟情况下也能减少误差。 带有四个信息的双面双向…

安全多方计算之八:Mix-Match

Mix-Match1. 混合网络基于ElGamal加密方案的混合网络2. PET协议3. Mix-Match协议4. 百万富翁问题的Mix-Match解决方案M.Jakobsson和A.Juels提出了基于Mix-Match的安全多方计算协议构造方法&#xff0c;该类协议包括Mix与Match两个阶段&#xff1a; Mix阶段&#xff1a;通过构造…

详解LinkedHashSet和LinkedHashMap

目录 一.LinkedHashSet和LinkedHashMap 1.基本介绍 2.与HashSet和HashMap的区别 3.LinkedHashSet和LinkedHashMap具体的方法 1.LinkedHashSet 2.LinkedHashMap 二.模拟代码实现LinkedHashMap 三.具体应用 一.LinkedHashSet和LinkedHashMap 1.基本介绍 顾名思义,根据名…

gpt4国内可以使用吗-chatgpt国内使用的软件排行榜

gpt4国内怎么用&#xff1f; 目前 OpenAI 尚未正式发布 GPT-4 模型&#xff0c;因此目前尚无法直接使用它。预计当GPT-4发布时&#xff0c;将通过OpenAI平台提供API以供使用者调用&#xff0c;同时新的API接口可能需要在不同国家/地区进行不同程度的注册或许可等手续。 当Ope…