01-详解静态代理,动态代理(JDK动态代理原理和CGLIB动态代理原理)

news2024/11/26 2:31:53

GoF之代理模式

概述

代理模式是GoF23种设计模式之一,属于结构型设计模式,本质就是通过引入代理对象间接实现对真实对象的操作

业务场景: 系统中有A、B、C三个模块,使用这些模块的前提是需要用户登录

  • 此时就可以为A、B、C三个模块提供一个代理,代理的逻辑请求来了之后先判断用户是否登录了,如果登录了则执行对应的目标,如果没有则跳转到登录页面

Java中的两种代理模式

  • 静态代理: 在编译期就生成代理对象
  • 动态代理: 在Java运行时动态生成代理对象,动态代理又有JDK代理和CGLib代理两种

代理模式的作用

  • 当一个对象需要受到保护的时候即不想让客户看到一些内容和服务,可以考虑使用代理对象去完成某个行为
  • 需要给某个对象的功能进行增强的时候,可以考虑找一个代理进行增强如添加客户需要的额外服务
  • 在程序中对象A和对象B无法直接交互时也可以使用代理模式来解决

代理模式分为三种角色

  • 目标对象(Real Subject): 实现最终核心的业务,是代理对象所代表的真实对象即最终要引用的对象
  • 代理对象(Proxy Subject): 其内部含有对目标对象的引用,它可以访问、控制或扩展目标对象的功能
  • 目标对象和代理对象的公共接口(Abstract Subject): 目标对象和代理对象需要具有相同的行为和动作,让客户端在使用代理对象时就像在使用目标对象一样

在这里插入图片描述

静态代理

准备对象和接口

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

public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 查看订单详情
     */
    void detail();

    /**
     * 修改订单
     */
    void modify();
}

OrderServiceImpl是目标类

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

    @Override
    public void detail() {
        try {
            // 模拟查看订单的耗时
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

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

统计每个业务方法耗费时长

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

  • 缺点: 违背了OCP开闭原则另外代码没有得到复用
public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(1234);
        } 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(2541);
        } 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(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }
}

第二种方案: 编写一个业务类的子类OrderServiceImpl,在子类中重写父类的每个业务方法同时调用父类的业务方法(CGLIB动态代理实现的原理)

  • 缺点: 采用了继承(满足is a的关系)的方式会导致父类和子类的代码之间耦合度非常高,另外代码也没有得到复用
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 detail() {
        long begin = System.currentTimeMillis();
        super.detail();
        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)+"毫秒");
    }
}

第三种方案: 使用静态代理模式,编写一个代理类OrderServiceProxy实现OrderService接口,然后在代理对象中调用目标对象的目标方法

  • 优点: 符合OCP开闭原则,同时采用的是关联关系(满足has a的关系)所以程序的耦合度较低
  • 缺点: 每个目标对象都需要一个代理对象,随着目标对象越来越多就会导致类爆炸不好维护
// 代理类和目标类需要实现相同的接口,让客户端感觉使用代理对象就像在使用目标对象一样
public class OrderServiceProxy implements OrderService{ // 代理对象
    // 关联目标对象即将目标对象作为代理对象的一个属性,使用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 detail() {// 代理方法
        // 对目标对象的方法进行增强
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.detail();
        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)+"毫秒");
    }
}

测试OrderServiceImpl业务类中每个业务方法的耗时

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

JDK动态代理

动态代理还是代理模式,只不过添加了字节码生成技术,可以在在程序运行时在内存中动态的生成字节码即代理类,有效减少代理类的数量同时解决代码复用的问题

在内存当中动态生成字节码的技术常见的包括三种

代理技术功能
JDK动态代理技术只能代理接口
CGLIB(Code Generation Library)动态代理技术既可以代理接口又可以代理类,通过继承的方式实现代理(生成目标类的子类作为),性能比JDK动态代理要好
底层通过使用一个小而快的字节码处理框架ASM
Javassist动态代理技术Javassist是一个开源的分析、编辑和创建Java字节码的类库
通过使用Javassist对字节码操作为JBoss应用服务器实现动态"AOP"框架

JDK动态代理原理

java.lang.reflect.Proxy是JDK提供的一个动态代理类,通过这个类可以在内存中生成代理类的字节码

方法功能
newProxyInstance(类加载器,公共接口类型,调用处理器接口的实现类)Proxy类提供的一个创建代理对象的静态方法
newProxyInstance方法执行结束后,首先在内存中动态生成一个代理类的字节码文件(Xxx.class)
然后通过类加载器获取代理类的字节码对象,使用Class对象实例化一个代理对象并返回
该方法的返回值是Object类型,但由于我们方法参数已经指定了代理对象实现的接口类型,所以可以放心的向下转型为对应的接口类型
invoke(代理对象,目标方法,目标方法调用时要传的参数)每次调用代理对象的任何方法最终都会先执行一次invoke方法(调用一次执行一次),调用代理对象的方法不同,invoke方法参数中的Method也对象不同,最终调用对应的目标类的方法
该方法的返回值由调用的目标对象的目标方法的执行结果决定,如果调用代理对象方法时需要接收返回值,那么就需要将目标对象的目标方法的执行结果返回

关于newProxyInstance()方法的三个重要的参数的含义

  • 类加载器: 要想执行JDK在内存中生成的字节码文件需要先把它通过类加载器加载到内存当中,并且JDK要求目标类的类加载器必须和代理类的类加载器是同一个
  • 公共接口类型: 告诉JDK动态代理生成的代理类要实现哪些接口,由于目标对象和代理对象实现的是同一个接口,所以可以直接通过目标对象获取接口类型
  • 调用处理器: JDK动态代理规定的java.lang.reflect.InvocationHandler是一个回调接口,调用这个接口中方法的程序已经写好了(自动调用 ),我们只要编写增强代码

InvocationHandler接口中invoke方法上的三个参数及调用

  • Object proxy: 设计这个参数只是为了后期需要在invoke方法中使用代理对象
  • Method method: 目标对象上的目标方法,即我们最终要执行的方法
  • Object[] args: 目标方法调用时需要传递的参数
  • 当你每调用一次代理对象的代理方法的时候,InvocationHandler接口的实现类中重写的invoke()方法就会被JDK调用一次,同时会携带方法需要的三个参数
  • 因为InvocationHandler接口的实现类存储的是我们需要增强的代码,所以需要手写,这样并不会造成类爆炸,因为这种实现类只需要写一次就好,代码可以复用

JDK动态生成的代理对象和目标对象的唯一联系就是它们都实现了同一个接口,可以在创建代理对象的时候向下转型

  • 代理类($Proxy0)将我们在Proxy.newProxyInstance方法参数中的匿名内部类对象传递给了父类Proxy

JDK动态代理实现

第一步: 提供OrderService接口及其实现类(目标类)OrderServiceImpl,UserServiceProxy代理类通过从在程序中动态生成

public interface OrderService {
    /**
     * 获取姓名
     */
    Srting getName();
    /**
     * 生成订单
     */
    void generate();

    /**
     * 查看订单详情
     */
    void detail();

    /**
     * 修改订单
     */
    void modify();
}
public class OrderServiceImpl implements OrderService {
    @Override
    public String getName() {
        System.out.println("执行getName方法");
        return "张三";
    }
    
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

第二步: 编写调用处理器接口的实现类TimerInvocationHandler同时实现接口中的invoke方法用来调用目标对象的目标方法并对其功能增强

  • 重写的方法名必须是invoke,因为invoke方法是JDK负责调用的,它已经把调用方法的程序写好了并且方法名就是invoke
  • 给TimerInvocationHandler提供一个构造方法,通过这个构造方法接收目标对象,然后在invoke方法执行过程中使用传递的method参数来调用目标对象的目标方法
/*	
专门负责计时的一个调用处理器对象,在这个调用处理器当中编写计时相关的增强代码,这个调用处理器只需要写一个就行了
*/
public class TimerInvocationHandler implements InvocationHandler {
    // 目标对象
    private Object target;
    public TimerInvocationHandler(Object target) {
        // 将接收的目标对象赋值给成员变量
        this.target = target;
    }
    
    // 负责调用目标对象的目标方法并对其功能增强
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long begin = System.currentTimeMillis();
        // 调用目标对象上的目标方法,方法四要素:哪个对象,哪个方法,传什么参数,返回什么值
        Object retValue = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
        // 如果我们调用的目标对象的目标方法有返回值的话,invoke方法就必须将目标对象的目标方法执行结果继续返回
        return retValue;
    }
}

第三步: 测试调用代理对象的方法,底层JDK会调用invoke方法然后通过method参数去执行目标对象的目标方法

public class Client { 
    public static void main(String[] args) {
        // 第一步:创建目标对象
        OrderService target = new OrderServiceImpl();
        // 第二步:创建代理对象,由于我们方法参数已经指定了代理对象实现的接口类型,所以可以放心的向下转型为对应的接口类型
        OrderService orderServiceProxy = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));
        // 第三步:调用代理对象的代理方法,调用代理对象的代理方法的时候,如果你要做增强的话目标对象的目标方法得保证执行
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();    
        // 调用有返回值的代理对象的代理方法
        String name = orderServiceProxy.getName();
        System.out.println(name);
    }
}

封装创建代理对象的工具类

在工具类ProxyUtil封装方法,实现只要传递一个目标对象就可以通过这个方法获取代理对象

public class ProxyUtil {
    public static Object newProxyInstance(Object target){
        // 底层调用的还是JDK的动态代理
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target));
    }

}

// 进一步封装,使用匿名内部类的方式简化方法参数
public class ProxyUtil {
    public static Object newProxyInstance(Object target){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target){
                	 // 目标对象
    				private Object target;
    				public TimerInvocationHandler(Object target) {
        				// 赋值给成员变量。
        				this.target = target;
    				}
                    
                    @Override
    				public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        				// 这个接口的目的就是为了让你有地方写增强代码。
        				long begin = System.currentTimeMillis();

        				// 调用目标对象上的目标方法
        				// 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
       					Object retValue = method.invoke(target, args);
       		 			long end = System.currentTimeMillis();
        				System.out.println("耗时"+(end - begin)+"毫秒");

        				//如果我们调用的代理对象的代理方法有返回值的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
        				return retValue;
    				}    
                });
    }

}

使用工具类封装的newProxyInstance方法简化客户端代码

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 使用工具类创建代理对象
        OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);
        // 调用代理对象的代理方法
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

CGLIB动态代理原理

CGLIB动态代理既可以代理接口又可以代理类,底层通过继承的方式实现对目标类的代理,所以被代理的目标类不能使用fianl修饰

// CGLIB创建的代理对象的格式,可以根据这个名字推测框架底层是否使用了CGLIB动态代理
class UserService$$EnhancerByCGLIB$$82cb55e3 extends UserService{}

net.sf.cglib.proxy.MethodInterceptor是CGLIB中提供的方法拦截器接口,和JDK动态代理的调用处理器接口一样,我们也需要提供该接口实现类并重写方法

CGLIB中的Enhancer和JDK的Proxy类都是用来创建代理对象的类

  • 使用Proxy类是直接调用静态方法newProxyInstance()创建代理对象,在方法中设置目标对象相关参数和调用处理器接口实现类,直接返回代理对象
  • 使用Enhancer类需要先创建字节码增强对象,然后通过Enhancer对象的不同方法设置目标类相关参数和方法拦截器接口的实现类,最后再调用方法创建代理对象
方法名功能
intercept(目标对象,目标方法,目标方法调用时的实参,调用目标方法时需要用到的MethodProxy类)负责调用目标类的目标方法以及添加增强的代码的方法
invokeSuper(目标对象,目标方法调用时的实参)最中调用目标对象中目标方法的方法
setSuperclass(Class clazz)设置代理类的父类即目标类
setCallback()设置回调为拦截器接口的实现类,等同于JDK动态代理当中的调用处理器接口的实现类
create()创建代理对象
先在内存中生成目标类的子类即代理类的字节码,然后创建代理对象
返回值默认是Object类型,但由于我们已经设置了代理类的父类,所以可以放心强转

CGLIB动态代理实现

第一步: 引入CGLIB的依赖

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

第二步: 准备一个目标类UserService,这个类不需要实现任何接口

public class UserService {
    public void login(String username, String password){
        System.out.printn("系统正在验证身份...");
        if ("admin".equals(username) && "123".equals(password)) {
           	return true;
        }  
         return false;
    }

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

第三步: 编写MethodInterceptor接口的实现类TimerMethodInterceptor并重写intercept()方法负责调用目标类的目标方法以及添加增强代码

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;
    }
}

第四步: 测试使用CGLIB在内存中为UserService类生成的代理类并创建代理对象,然后通过调用代理对象的方法去执行目标对象的目标方法

public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器对象,这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类
        Enhancer enhancer = new Enhancer();

        // 告诉CGLIB生成的代理类的父类是谁即目标类是谁
        enhancer.setSuperclass(UserService.class);

        // 设置回调为方法拦截器接口MethodInterceptor的实现类,等同于JDK动态代理当中的调用处理器接口的实现类
        enhancer.setCallback(new TimerMethodInterceptor());

        // 创建代理对象,代理类的父类是UserService
        UserService userServiceProxy = (UserService) enhancer.create();

        // 查看CGLIB动态代理生成的代理对象
        System.out.println(userServiceProxy);

        // 调用代理对象的代理方法
        boolean success = userServiceProxy.login("admin", "123");
        System.out.println(success ? "登录成功" : "登录失败");
        userServiceProxy.logout();
    }
}

JDK高版本问题

如果高版本的JDK想要使用CGLIB就需要在启动项中添加两个启动参数

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

在这里插入图片描述

动态代理与静态代理比较

优缺点

动态代理与静态代理相比较的优点是在接口方法数量比较多的时候可以进行灵活处理

  • 接口中声明的所有方法都被转移到调用处理器InvocationHandler的一个集中方法invoke中处理,而不需要像静态代理那样在每一个方法进行中转

  • 不管你有多少个Service接口,由于我们的代理对象是在程序运行中动态生成的 , 所以可以实现任何接口代码不会写死

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

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

相关文章

vue + axios + mock

参考来源&#xff1a;Vue mock.js模拟数据实现首页导航与左侧菜单功能_vue.js_AB教程网 记录步骤&#xff1a;在参考资料来源添加axios步骤 1、安装mock依赖 npm install mock -D //只在开发环境使用 下载完成后&#xff0c;项目文件package.json中的devDependencies就会加…

【已解决】linux下轻松解决大多数软件依赖问题

【已解决】linux下轻松解决大多数软件依赖问题 通过aptitute安装 sudo apt install aptitudesudo aptitude install 软件包的名字以安装opencv过程中sudo apt-get install libgtk2.0-dev失败为例 先装aptitute sudo apt install aptitude再装libgtk2.0-dev sudo aptitude …

HR如何应用人才测评系统来开展招聘?

企业招聘&#xff1a;名额少&#xff0c;应聘者多&#xff0c;这是必然现象&#xff01;如果提高招聘效率&#xff0c;成为企业最为关心的问题。 问题可能有 1、简历多筛选难 每次收到一堆的简历&#xff0c;如何从中筛选出有效的人才&#xff0c;是一件头疼的事&#xff0c…

【文末送书】Python界面开发与PyQt

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

【微服务】一体化智慧工地管理平台源码

智慧工地系统是一种利用人工智能和物联网技术来监测和管理建筑工地的系统。它可以通过感知设备、数据处理和分析、智能控制等技术手段&#xff0c;实现对工地施工、设备状态、人员安全等方面的实时监控和管理。 一、智慧工地让工程施工智能化 1、内容全面&#xff0c;多维度数…

[开源]免费开源MES系统/可视化数字大屏/自动排班系统

开源系统概述&#xff1a; 万界星空科技免费MES、开源MES、商业开源MES、市面上最好的开源MES、MES源代码、免费MES、免费智能制造系统、免费排产系统、免费排班系统、免费质检系统、免费生产计划系统。 万界星空开源MES制造执行系统的Java开源版本。开源mes系统包括系统管理…

回馈电子负载的特点

随着科技的不断发展&#xff0c;制造工厂正逐渐采用先进的设备和技术来提高生产效率。回馈电子负载作为一种新型的电力设备&#xff0c;其独特的特点为制造工厂带来了诸多优势。回馈电子负载是一种能够将多余的电能回馈到电网的电力设备&#xff0c;广泛应用于制造工厂、数据中…

Android 接入ttf字体文件

一、业务实现 一些炫酷的App总会加一些App自己的字体。这时候需要找UI提供ttf字体文件。 然后实现 TTF&#xff08;TrueType Font&#xff09;字体文件并将其应用到 TextView。 二、大致流程 将 TTF 字体文件添加到你的 Android 项目中&#xff1a; 将 TTF 文件复制到 res/f…

【Pytorch】计算机视觉项目——卷积神经网络CNN模型识别图像分类

目录 一、前言二、CNN可视化解释器1. 卷积层工作原理 三、详细步骤说明1. 数据集准备2.DataLoader3. 搭建模型CNN3.1 设置设备3.2 搭建CNN模型3.3 设置loss 和 optimizer3.4 训练和测试循环 4. 模型评估和结果输出 一、前言 在上一篇笔记《【Pytorch】整体工作流程代码详解&am…

mac电脑大旧型文件清理软件CleanMyMac2024

CleanMyMac的大旧文件模块会帮您定位、检查和移除您几个月没有打开过并且不再需要的大型文件和文件夹&#xff0c;这样可以节省更多的磁盘空间。 CleanMyMac X全新版下载如下: https://wm.makeding.com/iclk/?zoneid49983 大型和旧文件模块可以查找和移除大型文件和文件夹&…

香港账户的美金如何打到国内账户

香港账户的美金可以有多种方式打到国内账户&#xff0c;以下是几种常见的方式&#xff1a; 1.银行电汇&#xff1a;将美元转账到中国大陆的银行账户上并进行换汇操作&#xff0c;这是一种稳妥可靠的方式&#xff0c;但手续费相对较高。 2. 支付宝国际汇款&#xff1a;通过支付…

任正非说:我们要在整体上形成海军陆战队和主力作战团队相配合的作战方案。

你好&#xff01;这是华研荟【任正非说】系列的第30篇文章&#xff0c;让我们聆听任正非先生的真知灼见&#xff0c;学习华为的管理思想和管理理念。 一、我们的业务量在增长&#xff0c;因此带来表面上人的效益是增长的。但是我们要看到&#xff0c;我们现在利润不是来自于管理…

C++ Qt 学习(三):无边框窗口设计

1. 无边框窗口 1.1 主窗口实现 MainWidget.h #pragma once#include <QtWidgets/QWidget> #include "CTitleBar.h" #include "CFrameLessWidgetBase.h"// 主窗口 MainWidget 继承自无边框窗口公用类 CFrameLessWidgetBase class MainWidget : publi…

全志R128应用开发案例——SPI驱动ST7789V1.3寸LCD

SPI驱动ST7789V1.3寸LCD R128 平台提供了 SPI DBI 的 SPI TFT 接口&#xff0c;具有如下特点&#xff1a; Supports DBI Type C 3 Line/4 Line Interface ModeSupports 2 Data Lane Interface ModeSupports data source from CPU or DMASupports RGB111/444/565/666/888 vide…

【LeetCode:318. 最大单词长度乘积 | 模拟 位运算】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

CRM软件如何高效培育销售线索?

​ 通过线索培育可以挖掘出更多CRM软件销售管道中的有价值客户提高销售业绩。但机遇与挑战总是共存的&#xff0c;培育线索要从不同的渠道执行大量重复性的操作&#xff0c;人为操控不仅速度慢而且容易出错&#xff0c;那么企业如何高效培育销售线索? 发送个性化邮件 我们知…

YOLO目标检测——汽车头部尾部检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;用于训练自动驾驶系统中的车辆感知模块&#xff0c;以实现对周围车辆头部和尾部的准确检测和识别数据集说明&#xff1a;汽车头部尾部检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富标签说明&#xff1a;使用lableimg标注软…

随机森林在生物信息中的应用

今天与大家分享一项强大的机器学习算法随机森林。这个算法不仅在数据科学领域广泛应用&#xff0c;还在生物信息学中发挥了巨大的作用。 让我们一起探索随机森林的原理、优缺点以及它在生物信息领域的实际应用场景&#xff0c;本文将给出R语言进行应用的实际方法&#xff0c;利…

数据采集卡如何选型?

数据采集卡如何选型? 一、 确认采集任务二、 选择合适的传感器三、采样频率、分辨率、总线类型、量程等关键参数选择 一、 确认采集任务 二、 选择合适的传感器 三、采样频率、分辨率、总线类型、量程等关键参数选择 第1步&#xff1a;确认采集任务&#xff0c;电压&#x…

产业园区中工业厂房的能源综合配置——工业园区综合能源数字化系统建设方案

以下内容转自微信公众号&#xff1a;PPP产业大讲堂&#xff0c;《产业园区中工业厂房的能源综合配置》。 园区工业地产中能源综合配置存在的问题 我国园区工业地产建设已历经近40年的发展, 园区在区域经济发展、产业集聚方面发挥了重要的载体和平台作用, 有力推动了我国社会经…