【Spring】GoF 之代理模式

一、代理模式

在 Java 程序中的代理模式的作用:

  • 当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为

  • 需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强

  • A 对象无法和 B 对象直接交互时,也可以使用代理模式来解决

代理模式中的三大角色:

  • 目标对象(演员)

  • 代理对象(替身演员)

  • 目标对象和代理对象的公共接口(演员与替身演员相同的行为,可以让观众不知道是替身演员)

如果使用代理模式,对于客户端程序来说,客户端是无法察觉的,客户端在使用代理对象的时候就像在使用目标对象

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

  • 静态代理

  • 动态代理

 

二、静态代理

package org.qiu.proxy.service;

/**
 * 订单业务接口
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:09
 * @since 1.0
 */
public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 修改订单信息
     */
    void modify();

    /**
     * 查看订单详情
     */
    void detail();
}
package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:10
 * @since 1.0
 */
public class OrderServiceImpl implements OrderService{
    @Override
    public void generate() {
        // 模拟网络延迟
        try {
            Thread.sleep(1024);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成!");
    }

    @Override
    public void modify() {
        // 模拟网络延迟
        try {
            Thread.sleep(512);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改!");
    }

    @Override
    public void detail() {
        // 模拟网络延迟
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单详情......");
    }
}
public class Test {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl();
        orderService.generate();
        orderService.detail();
        orderService.modify();
    }
}

运行结果:  

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

 

第一种方案

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

package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:10
 * @since 1.0
 */
public class OrderServiceImpl implements OrderService{
    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        // 模拟网络延迟
        try {
            Thread.sleep(1024);
        } 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(512);
        } 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(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单详情......");
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }
}

运行效果:  

需求可以满足,但显然是违背了OCP开闭原则,这种方案不可取

第二种方案

编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:

package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:24
 * @since 1.0
 */
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));
    }
}
public class Test {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImplSub();
        orderService.generate();
        orderService.detail();
        orderService.modify();
    }
}

运行效果:  

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

  • 第一个问题:假设系统中有100个这样的业务类,需要提供100个子类,并且之前写好的创建Service对象的代码,都要修改为创建子类对象

  • 第二个问题:由于采用了继承的方式,导致代码之间的耦合度较高。

这种方案也不可取

第三种方案

使用代理模式(这里采用静态代理)

可以为 OrderService 接口提供一个代理类

package org.qiu.proxy.service;

/**
 * 代理对象
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:32
 * @since 1.0
 */
public class OrderServiceProxy implements OrderService {

    // 目标对象
    // 这里要使用“公共接口”类型,因为公共接口耦合度低
    private OrderService orderService;

    // 通过构造方法将目标对象传递给代理对象
    public OrderServiceProxy(OrderService orderService){
        this.orderService = orderService;
    }

    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        orderService.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        orderService.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }
}

这种方式的优点:

符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的 

package org.qiu.proxy.client;

import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.OrderServiceImplSub;
import org.qiu.proxy.service.OrderServiceProxy;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-16-13:13
 * @since 1.0
 */
public class Test {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService proxy = new OrderServiceProxy(target);
        // 调用代理对象的代理方法
        proxy.generate();
        proxy.modify();
        proxy.detail();
    }
}

运行效果:  

以上就是代理模式中的静态代理,其中:

OrderService 接口是代理类和目标类的共同接口

OrderServiceImpl 是目标类

OrderServiceProxy 是代理类

思考一下:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用

 

三、动态代理 

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

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

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

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

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

JDK 动态代理 

package org.qiu.proxy.service;

/**
 * 订单业务接口
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-08:23
 * @since 1.0
 */
public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 修改订单信息
     */
    void modify();

    /**
     * 查看订单详情
     */
    void detail();
}
package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-08:23
 * @since 1.0
 */
public class OrderServiceImpl implements OrderService{
    @Override
    public void generate() {
        // 模拟网络延迟
        try {
            Thread.sleep(1024);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成!");
    }

    @Override
    public void modify() {
        // 模拟网络延迟
        try {
            Thread.sleep(512);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改!");
    }

    @Override
    public void detail() {
        // 模拟网络延迟
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单详情......");
    }
}

使用静态代理的时候,除了以上一个接口和一个实现类之外,还需要写一个代理类 UserServiceProxy!在动态代理中UserServiceProxy 代理类是可以动态生成的,这个类不需要写。我们直接写客户端程序即可:  

package org.qiu.proxy.client;

import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;

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

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-20-08:22
 * @since 1.0
 */
public class Test {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();
        /**
         * 参数解析:
         * 参数一:Classloader loader 类加载器
         *      JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
         * 参数二:Class<?>[] interfaces 代理类要实现的接口
         *      代理类和目标类要实现同一个接口或同一些接口
         * 参数三:InvocationHandler h 调用处理器对象(是一个接口)
         *      在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)
         */
        OrderService orderServiceProxy = (OrderService) 
            Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                                   target.getClass().getInterfaces(), 
                                   new TimeInvocatioinHandler(target)
            );
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}
package org.qiu.proxy.service;

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

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-09:01
 * @since 1.0
 */
public class TimeInvocatioinHandler implements InvocationHandler {

    private Object target;

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

    /**
     * invoke 什么时候被调用?
     *  当代理对象调用代理方法的时候,注册在 InvocationHandler 调用处理器当中的 invoke 方法被调用
     *
     * @param proxy     代理对象的引用
     * @param method    目标对象上的目标方法
     * @param args      目标方法上的实际参数
     * @return          若代理对象代用代理方法之后需要返回结果的话,invoke方法必须将目标对象目标方法执行结果继续返回
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long begin = System.currentTimeMillis();

        // 调用目标对象上的目标方法
        Object resultValue = method.invoke(target, args);

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

        return null;
    }
}

上面调用 JDK 自带的方法比较繁琐,这里可以封装一个工具类,方便使用:  

package org.qiu.proxy.util;

import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.TimeInvocatioinHandler;

import java.lang.reflect.Proxy;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.util
 * @date 2022-11-20-09:12
 * @since 1.0
 */
public class ProxyUtil {

    /**
     * 获取代理对象(JDK动态代理)
     * @param target
     * @return
     */
    public static Object newProxyInstance(Object target){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));
    }

}
package org.qiu.proxy.client;

import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;

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

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-20-08:22
 * @since 1.0
 */
public class Test {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();
        /**
         * 参数解析:
         * 参数一:Classloader loader 类加载器
         *      JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
         * 参数二:Class<?>[] interfaces 代理类要实现的接口
         *      代理类和目标类要实现同一个接口或同一些接口
         * 参数三:InvocationHandler h 调用处理器对象(是一个接口)
         *      在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)
         */
        /*
        OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));*/
        // 工具类封装
        OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

 

CGLIB 动态代理

CGLIB 既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用 final 修饰。

使用CGLIB,需要引入它的依赖:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

准备一个没有实现接口的类,如下:  

package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-09:22
 * @since 1.0
 */
public class UserService {
    public void login(){
        System.out.println("用户正在登录系统....");
    }

    public void logout(){
        System.out.println("用户正在退出系统....");
    }
}

使用 CGLIB 在内存中为 UserService 类生成代理类,并创建对象:  

package org.qiu.proxy.client;

import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-20-09:23
 * @since 1.0
 */
public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(方法拦截器对象);
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();
    }
}

和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor

编写MethodInterceptor接口实现类:

package org.qiu.proxy.service;

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

import java.lang.reflect.Method;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-09:24
 * @since 1.0
 */
public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return null;
    }
}

 

MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:

第一个参数:目标对象

第二个参数:目标方法

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

第四个参数:代理方法

在MethodInterceptor的intercept()方法中调用目标以及添加增强:

package org.qiu.proxy.service;

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

import java.lang.reflect.Method;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-09:24
 * @since 1.0
 */
public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前增强
        long begin = System.currentTimeMillis();
        // 调用目标
        Object retValue = methodProxy.invokeSuper(target, objects);
        // 后增强
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
        // 一定要返回
        return retValue;
    }
}

回调已经写完了,可以修改客户端程序了:  

package org.qiu.proxy.client;

import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-20-09:23
 * @since 1.0
 */
public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(new TimerMethodInterceptor());
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();
    }
}

对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:  

  • --add-opens java.base/java.lang=ALL-UNNAMED

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

 

一  叶  知  秋,奥  妙  玄  心

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

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

相关文章

C# 使用Queue高效检索树行数据符合条件的数据,并返回完整树形数据示例

最近有项目需要加载大型树数据&#xff0c;数据大概3W条 后端使用C# NET6 前端使用Vue3 elementuiplus 虚拟tree 》解决大型树数据加载 遇到的问题是后端在检索数据时&#xff0c;要返回匹配数据的完整树目录 1.因为单条数据没有存放完整路径&#xff0c;需要通过父级ID逐…

【ARM Cortex-M 系列 2.1 -- Cortex-M7 Debug system registers】

请阅读【嵌入式开发学习必备专栏】 文章目录 Debug system registers中断控制状态寄存器&#xff08;ICSR&#xff09;Debug Halting Control and Status Register, DHCSR Debug 寄存器DCRSR与DCRDRCPU 寄存器读操作CPU 寄存器写操作CPU 寄存器选择CPU 寄存器读写示例 调试故障…

Ubuntu安装VScode

Ubuntu安装VScode 前言&#xff1a; 1、Ubuntu安装VScode比较方便 2、我更喜欢source insight 1、获取到linux版本的VScode安装包 VSCode 下载地址是&#xff1a;https://code.visualstudio.com/ 2、得到安装包 3、复制到ubuntu中&#xff0c;使用命令安装 sudo dpkg -i cod…

安卓短视频一键搬运软件_V1.5.2 高级版

短视频一键搬运app是一款非常实用的视频处理软件&#xff0c;拥有各种各样的视频处理功能&#xff0c;可以帮助用户进行视频的多项处理&#xff0c;首先用户可以在这里为视频去除水印&#xff0c;打开视频文件过后&#xff0c;再把视频里面的水印内容框选出来&#xff0c;这样就…

第三课,python基础语法(二),基本算术运算符、3种数据类型、变量命名规则

一&#xff0c;基本算术运算 数学中&#xff1a;&#xff0c;-&#xff0c;&#xff0c; *小练习 请在程序中&#xff0c;定义如下变量&#xff1a; 钱包余额(变量名&#xff1a;money)&#xff0c;初始余额50 请通过程序计算&#xff0c;再购买了&#xff1a; 冰淇淋10元可…

【C语言/数据结构】栈:从概念到两种存储结构的实现

目录 一、栈的概念 二、栈的两种实现方式 1.顺序表实现栈 2.链表实现栈 三、栈的顺序存储结构及其实现 1.栈的声明 2.栈的初始化 3.栈的销毁 4.栈的压栈 5.栈的弹栈 6.栈的判空 7.返回栈顶元素 8.返回栈的长度 四、栈的链式存储结构及其实现 1.栈的声明 2.栈的…

[C++核心编程-03]----C++函数提高学习

目录 引言 正文 01-函数提升简介 02-函数默认参数 03-函数占位参数 04-函数重载 05-函数重载的注意事项 总结 引言 函数在C编程中扮演着至关重要的角色&#xff0c;通过合理使用函数&#xff0c;可以提高程序的结构性、灵活性、可读性和维护性。因此&…

汇昌联信:拼多多入驻条件是哪些?

在电商领域&#xff0c;拼多多以其独特的团购模式迅速崛起&#xff0c;吸引了众多商家的目光。想要在拼多多上开店&#xff0c;了解其入驻条件是必不可少的第一步。下面将详细解读拼多多的入驻条件&#xff0c;帮助有意加入的商家们做好准备。 一、企业资质要求 想要成功入驻拼…

STM32(GPIO)

GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口 引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V 输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等 输入模式下可读取端口的高低电…

【C++】继承与多态的一些练习题

学习完了继承与多态&#xff0c;当然要来点练习题啦~ 第一题&#xff1a; class Base1 { public: int _b1; }; class Base2 { public: int _b2; }; class Derive : public Base1, public Base2 { public: int _d; }; int main(){ Derive d; Base1* p1 &d; Base2* p2…

Day_4

1. 地址簿功能 查询地址列表 属于常规方案 新增地址 属于常规方案 修改地址 删除地址 设置默认地址 2. 用户下单业务 数据库分析 订单表和订单明细表的关系&#xff1a;一对多 代码开发 controller 层 service 层 异常处理&#xff08;收货地址为空、超出配送范围、购物…

文件流-二进制文件(中北大学-程序设计基础(2))

目录 题目 源码 结果示例 题目 建立两个二进制磁盘文件f1.dat,f2.dat&#xff0c;编程实现以下工作&#xff1a; &#xff08;1&#xff09;将20个整数&#xff08;可在程序中初始化&#xff09;&#xff0c;分别存放到两个磁盘文件中&#xff0c;前10个放到f1.dat中&…

【数据可视化01】matplotlib实例介绍1

目录 一、引言二、实例介绍1.柱状图1)简单柱状图2)堆叠柱状图 2.线条形式3.折线图&#xff08;多子图&#xff09;4.散点图5.水平和垂直线条6.饼状图1&#xff09;饼状图2&#xff09;“条形饼”图 一、引言 matplotlib是一个用于绘制数据可视化的Python库。它可以创建各种静态…

Java并发编程:用户态、内核态、cache line和CPU缓存一致性协议

文章目录 一、介绍二、java中那些操作使用了内核态三、cache line的概念四、CPU缓存一致性协议 一、介绍 用户态和内核态是操作系统的两种运行状态&#xff0c;它们分别对应于不同的权限级别和访问能力。 用户态&#xff08;User Mode&#xff09;&#xff1a;这是应用程序运…

volatile详解、原理

文章目录 一、Volatile的定义和作用1.1 Volatile简介1.2 Volatile作用 二、并发编程中的三个问题&#xff1a;可见性、原子性、有序性二、Java内存模型&#xff08;JMM&#xff09;三、volatile变量的特性3.1 线程可见性3.2 禁止重排序禁止重排序原理禁止重排序举例 3.3 volati…

知乎知+广告推广该如何做?怎么收费?

知乎作为一个汇聚高质量用户群体的知识分享平台&#xff0c;成为了众多品牌和产品推广的优选之地。特别是知乎的“知”广告推广服务&#xff0c;以其精准定向、内容原生的特点&#xff0c;深受广告主青睐。 一、知乎知广告推广基础 1. 什么是知乎知&#xff1f; 知是知乎官方…

Java入门基础学习笔记11——关键字和标识符

1、关键字 关键字是java中已经被赋予特定意义的&#xff0c;有特殊作用的一些单词&#xff0c;不可以把这些单词作为标识符来使用。 注意&#xff1a;关键字是java用了的&#xff0c;我们就不能用来作为&#xff1a;类名、变量名、否则会报错。 标识符&#xff1a; 标识符就是…

RAG应用中的路由模式

依据的用户查询意图在 RAG 应用程序使用“路由控制模式”可以帮助我们创建更强大的 RAG 应用程序。我们通常希望用户能够访问的数据可以来自各种来源,如报告、文档、图片、数据库和第三方系统。 对于基于业务的 RAG 应用程序,我们可能还希望用户能够与其它业务系统进行交互,…

Linux 中 alarm 函数详解

目录 简介函数原型函数参数返回值使用示例设置 3 秒闹钟修改闹钟与取消闹钟设置 1 秒周期定时器 更多内容 简介 alarm 函数的功能是设置一个闹钟&#xff08;定时器&#xff09;&#xff0c;当闹钟时间到时&#xff0c;内核会向当前进程发送一个 SIGALRM 信号。 打开 Linux 终…

汇昌联信电商:拼多多新手怎么做店铺的免费流量会慢慢起来?

在拼多多上开店&#xff0c;新手们往往面临着如何吸引免费流量的挑战。毕竟&#xff0c;流量是店铺生存和发展的血脉&#xff0c;没有流量&#xff0c;就没有销量&#xff0c;店铺也就失去了生命力。那么&#xff0c;作为拼多多新手&#xff0c;如何做才能让店铺的免费流量慢慢…