不懂Nacos没关系,可以看看它是怎么运用代理模式的

news2024/11/25 23:02:12

背景

看Nacos的源代码时,发现其中有对代理模式的运用,而且用得还不错,可以作为一个典型案例来聊聊,方便大家以更真实的案例来体验一下代理模式的运用。如果你对Nacos不了解,也并不影响对本篇文章的阅读和学习。

本文涉及知识点:代理模式的定义、代理模式的运用场景、Nacos的服务注册、静态代理模式、动态代理模式、Cglib动态代理、Spring中AOP所使用的代理等。

何谓代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,通常使用代理对象来执行目标对象的方法并在代理对象中增强目标对象的方法。

定义有一些绕口,举个生活中的简单例子:你去租房,可以直接找房东,也可以找中介。而代理模式就是你租房不用找房东,通过中介来租,而中介呢,不仅仅能够提供房屋出租服务(目标对象的方法),还可以提供房屋清洁的服务(对目标对象方法的增强)。

在上述例子中,中介是代理对象,房东是目标对象(或委托对象),中介为房东提供了出租的功能,在出租的功能上代理又可以提供增强的房屋清洁功能。

为什么要使用代理模式呢?

原因有二:

  • 中介隔离作用:在上述例子中,无论是因为客户嫌直接找房东麻烦,还是房东嫌出租客户麻烦,中间都需要一个专门的角色来处理这事,它就是代理。也就是说,客户类不想或者不能直接引用一个委托对象,代理对象就可以在二者之间起到中介的作用。
  • 开闭原则:在上面的例子中,房东只想出租房屋,而租户租房时还想享受清洁服务,而这个清洁服务就需要通过代理类来处理。这样不用直接在房东出租功能上修改(新增)清洁服务,仅通过代理类就可以完成,符合开闭原则。上面的例子是提供一些特定的服务,在实践中,像鉴权、计时、缓存、日志、事务处理等一些公共服务都可以在代理类中完成。

代理模式的分类

代理模式通常可分为两类:静态代理动态代理。动态代理的实现又有JDK动态代理CGLIB动态代理两种实现方式。

静态代理是由开发人员直接编写代理类,代理类和委托类之间的关系在运行前已经确定好的。当需要修改或屏蔽一个或若干类的部分功能,复用另一部分功能时,可使用静态代理。

动态代理的代理类是在运行时期间由编译器动态生成(比如,JVM的反射机制生成代理类),在运行时确定代理类和委托类之间的关系。当需要拦截一批类中的某些方法,在方法前后加入一些公共操作时,可使用动态代理。

静态代理

在Nacos中服务注册接口使用的代理模式为静态代理。静态代理模式需要先定义接口,委托类和代理类一起实现该接口,然后通过调用代理类对应的方法间接调用委托类的对应方法。

常见的静态代理类数据模型如下:

静态代理(图片来源网络)

上图中通过代理类对委托类的方法进行拓展,在方法执行前后新增一些逻辑处理,比如日志、计时等,这是最简单的一种代理模式实现。

在Nacos中静态代理模式运用的场景是客户端实例向Nacos的注册、注销等操作。由于实例的注册方式支持临时实例和持久实例两种方式,代理类就起到了判断到底是采用临时实例注册服务,还是使用持久实例注册服务。

下面直接以Nacos相关源码来进行解析说明。

第一步,定义接口,静态代理是需要先定义一个共同的实现接口的。

public interface ClientOperationService {
    
    /**
     * Register instance to service.
     *
     */
    void registerInstance(Service service, Instance instance, String clientId) throws NacosException;
    
    // ...
}

在Nacos中定义了一个ClientOperationService的接口,其中提供了实例的注册、注销等功能,这里为了方便阅读,仅展示注册实例代码(后续代码相同)。

第二步,定义两个委托类,一个委托类实现临时实例注册,一个委托类实现持久实例注册。

@Component("ephemeralClientOperationService")
public class EphemeralClientOperationServiceImpl implements ClientOperationService {
    
    @Override
    public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
       // ... 临时实例注册逻辑实现
    }
    // ...
}

@Component("persistentClientOperationServiceImpl")
public class PersistentClientOperationServiceImpl extends RequestProcessor4CP implements ClientOperationService {
    
    @Override
    public void registerInstance(Service service, Instance instance, String clientId) {
       // ... 永久实例注册逻辑实现
    }
    // ...
}    

EphemeralClientOperationServiceImpl类为临时实例操作服务实现,实现了ClientOperationService接口。PersistentClientOperationServiceImpl类为永久实例操作服务实现,同样实现了ClientOperationService接口。

第三步,定义代理类。通常情况下,一个代理类代理一个委托类,但在Nacos中,代理类实现了区分到底是临时实例还是永久实例的逻辑,因此代理类同时代理了上述两个委托类。

@Component
public class ClientOperationServiceProxy implements ClientOperationService {
    
    private final ClientOperationService ephemeralClientOperationService;
    
    private final ClientOperationService persistentClientOperationService;
    
    public ClientOperationServiceProxy(EphemeralClientOperationServiceImpl ephemeralClientOperationService,
            PersistentClientOperationServiceImpl persistentClientOperationService) {
        this.ephemeralClientOperationService = ephemeralClientOperationService;
        this.persistentClientOperationService = persistentClientOperationService;
    }
    
    @Override
    public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
        final ClientOperationService operationService = chooseClientOperationService(instance);
        operationService.registerInstance(service, instance, clientId);
    }
    
    private ClientOperationService chooseClientOperationService(final Instance instance) {
        return instance.isEphemeral() ? ephemeralClientOperationService : persistentClientOperationService;
    }
    // ...
}

代理类ClientOperationServiceProxy通过构造方法传入了两个委托类,通过chooseClientOperationService方法根据参数来判断具体使用哪个委托类,从而实现了在registerInstance方法中,根据参数动态的判断注册实例的方式。

Nacos的代理模式实现,符合我们前面提到的“客户类不想或者不能直接引用一个委托对象”的场景,这里是(每个)客户类“不想”每次调用时都判断采用何种方式注册,从而把这个判断逻辑交给了代理类才进行处理。

像Nacos中的这种实现就属于静态代理模式,在程序运行之前,已经通过代码实现了具体的代理类实现。静态代理的优点非常明显,可以在不改变目标对象的前提下,扩展目标对象的功能。

但缺点也同样明显:

  • 重复性:如果需要代理的业务或方法越多,则重复的模板代码就越多;
  • 脆弱性:一旦目标对象(接口)的方法有所变动,比如新增接口,代理对象和目标对象需要同时修改。如果目标对象有多个代理对象,影响范围可想而知。

JDK动态代理

静态代理是在编码阶段已经把代理类实现好了,那么是否可以在运行时动态构建代理类,来实现代理的功能呢?JDK动态代理便提供了这样的功能。

需要注意的是,JDK动态代理并不等价于动态代理,它只是动态代理的实现方式之一,即我们后面要讲到的Cglib动态代理也是动态代理的实现之一。

使用JDK动态代理时,代理对象不需要再实现接口,而目标对象依旧需要实现接口。使用JDK动态代理时需要用到两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

下面以用户登录时,在登录操作前后打印日志为例,体验一下JDK动态代理的功能。

第一步,创建业务接口。

public interface UserService {
	void login(String username, String password);
}

第二步,创建业务实现类。

public class UserServiceImpl implements UserService{

	@Override
	public void login(String username, String password) {
		System.out.println("User Login Service!");
	}
}

第三步,创建业务逻辑处理器,实现InvocationHandler接口。

public class LogHandler implements InvocationHandler {

	/**
	 * 被代理的对象,实际的方法执行者
	 */
	Object target;

	public LogHandler(Object object) {
		this.target = object;
	}


	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("Before Login---");
		// 调用target的method方法
		Object result = method.invoke(target, args);
		System.out.println("After Login---");
		return result;
	}
}

这里我们编写了一个LogHandler类,实现InvocationHandler接口,重写invoke方法。

invoke方法中定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用。

这里,在执行目标类方法前后可添加对应的日志信息打印或其他操作,在上述代码中分别打印了“Before Login”和“After Login”的信息。

第四步,模拟客户端使用。

public class JdkProxyTest {

   public static void main(String[] args) {

      // 创建被代理的对象,UserService接口的实现类
      UserServiceImpl userService = new UserServiceImpl();

      // 创建代理对象,包含三个参数:ClassLoader、目标类实现接口数组、事件处理器
      UserService userProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
            userService.getClass().getInterfaces(),
            new LogHandler(userService));

      userProxy.login("admin", "123456");
   }
}

在上述测试类中,先创建了被代理类的对象,然后通过Proxy的newProxyInstance方法构建了代理对象,生成的代理对象实现了目标类的所有接口,并对接口的方法进行了代理。

当我们通过代理对象调用具体方法时,底层将通过反射,调用我们实现的invoke方法,最后通过调用目标对象的登录方法。

执行上述方法,控制台打印日志如下:

Before Login---
User Login Service!
After Login---

可以看到,在登录操作前后,打印了对应的日志。

在构建代理对象时,用到了Proxy的newProxyInstance方法,该方法接收三个参数:

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

通过上述方式,我们实现了基于JDK的动态代理。JDK动态代理有以下特点:

  • 通过实现InvocationHandler接口完成代理逻辑,所有函数调用都经过invoke函数转发,可在此进行自定义操作,比如日志系统、事务、拦截器、权限控制等。
  • 通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
  • 代理类必须实现接口

可以看出,JDK动态代理的一个致命缺点就是目标类必须实现某个接口。而要解决这个问题,可以通过Cglib代理来实现,我们后面会具体讲到。

JDK动态代理类

在上述实践的过程中,我们是否考虑过,通过JDK动态代理生成的代理类到底是什么样子呢?我们通过下面的工具类,可以一探究竟。

public class ProxyUtils {

   /**
    * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
    * params: clazz 需要生成动态代理类的类
    * proxyName: 为动态生成的代理类的名称
    */
   public static void generateClassFile(Class clazz, String proxyName) {
      // 根据类信息和提供的代理类名称,生成字节码
      byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
      String paths = clazz.getResource(".").getPath();
      System.out.println(paths);

      try (FileOutputStream out = new FileOutputStream(paths + proxyName + ".class")) {
         //保留到硬盘中
         out.write(classFile);
         out.flush();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

上面代码定义了一个将代理类保持到磁盘中的工具类。然后,在JdkProxyTest类的最后,调用该方法,将JDK动态生成的代理类打印出来。

public class JdkProxyTest {

	public static void main(String[] args) {

		// 创建被代理的对象,UserService接口的实现类
		UserServiceImpl userService = new UserServiceImpl();

		// 创建代理对象,包含三个参数:ClassLoader、目标类实现接口数组、事件处理器
		UserService userProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
				userService.getClass().getInterfaces(),
				new LogHandler(userService));

		userProxy.login("admin", "123456");

		// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
		ProxyUtils.generateClassFile(userService.getClass(), "UserServiceProxy");

	}
}

其他代码未变,最后一行添加了工具类ProxyUtils的调用。

执行上述代码,会在项目目录的target下生成名为“UserServiceProxy”的class文件。本人执行时,打印的路径为“…/target/classes/com/secbro2/proxy/”。

在该目录下找到UserServiceProxy.class类文件,通过IDE的反编译功能,可看到如下代码:

public final class UserServiceProxy extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public UserServiceProxy(InvocationHandler var1) throws  {
        super(var1);
    }

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

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

    public final void login(String var1, String var2) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.secbro2.proxy.UserService").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从反编译的代理类中,我们可以得到以下信息:

  • UserServiceProxy继承了Proxy类,实现了UserService接口,当然接口中定义的login方法也同样实现了。同时,还实现了equals、hashCode、toString等方法。
  • 由于UserServiceProxy继承了Proxy类,所以每个代理类都会关联一个InvocationHandler方法调用处理器。
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承。
  • 每个方法都有一个 Method对象来描述,Method对象在static静态代码块中创建,以 m + 数字 的格式命名。
  • 调用方法时通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h.invoke 实际上是在创建代理时传递给 Proxy.newProxyInstance 的LogHandler对象,它继承InvocationHandler类,负责实际的调用处理逻辑。

而LogHandler的 invoke 方法接收到method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法。

至此,我们已经了解了基于JDK动态代理的使用以及所生成代理类的结构,下面就来看看无需目标类实现接口的Cglib动态代理实现。

Cglib动态代理

在上面的实例中可以看到无论使用静态代理或是JDK动态代理,目标类都需要实现一个接口。在某些情况下,目标类可能并没有实现接口,这时就可以使用Cglib动态代理。

Cglib(Code Generation Library)是一个功能强大、高性能、开源的代码生成包,它可以为没有实现接口的类提供代理。

Cglib代理可以称为子类代理,具体而言,Cglib会在内存中构建一个目标类的子类,重写其业务方法,从而实现对目标对象功能的扩展。因为采用继承机制,所以不能对final修饰的类进行代理。

Cglib通过Enhancer类来生成代理类,通过实现MethodInterceptor接口,在其intercept方法中对目标对象的方法进行增强,并可通过Method或MethodProxy继承类来调用原有方法。

这次以下订单(OrderService)为例来展示一下通过Cglib在下订单操作前后添加日志信息。

在使用Cglib之前,首先需要引入对应的依赖jar包,大多数项目中往往Cglib已经被间接引入了,可核实其版本是否是预期版本。这里采用Maven形式,引入Cglib依赖。

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

第一步,定义业务类OrderService,不需要实现任何接口。

public class OrderService {
	public void order(String orderNo){
		System.out.println("order something... ");
	}
}

第二步,定义动态代理类的创建及业务实现。

/**
 * 动态代理类,实现方法拦截器接口
 **/
public class LogInterceptor implements MethodInterceptor {

	/**
	 * 给目标对象创建一个代理对象
	 */
	public Object getProxyInstance(Class targetClass){
		// 1.工具类
		Enhancer enhancer = new Enhancer();
		// 2.设置父类
		enhancer.setSuperclass(targetClass);
		// 3.设置回调函数
		enhancer.setCallback(this);
		// 4.创建子类(代理对象)
		return enhancer.create();
		// 上述方法也可以直接使用如下代码替代
		// return Enhancer.create(targetClass,this);
	}

	/**
	 *
	 * @param o 要进行增强的对象
	 * @param method 拦截的方法
	 * @param objects 方法参数列表(数组)
	 * @param methodProxy 方法的代理,invokeSuper方法表示对被代理对象方法的调用
	 */
	@Override
	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		// 扩展日志记录
		System.out.println("LogInterceptor:Before Login---");
		// 注意:调用的invokeSuper而不是invoke,否则死循环。
		// methodProxy.invokeSuper执行的是原始类的方法,method.invoke执行的是子类的方法
		Object object = methodProxy.invokeSuper(o, objects);
		// 扩展日志记录
		System.out.println("LogInterceptor:After Login---");
		return object;
	}
}

LogInterceptor类实现了MethodInterceptor接口,在重写的intercept方法中添加了要扩展的业务内逻辑。其中需要注意的是,intercept方法内调用的是MethodProxy#invokeSuper方法,而不是invoke方法。

同时,在LogInterceptor类中定义了创建目标对象的代理对象的工具方法getProxyInstance,值得留意的是Enhancer#setCallback方法的参数this,指的便是LogInterceptor的当前对象。

第三步,编写测试客户端。

public class CglibTest {

	public static void main(String[] args) {
		OrderService orderService = (OrderService) new LogInterceptor().getProxyInstance(OrderService.class);
		orderService.order("123");
	}
}

执行上述方法,打印日志如下:

LogInterceptor:Before Login---
order something... 
LogInterceptor:After Login---

成功的在目标对象的方法前后植入日志信息。

关于Cglib动态代理有以下特点:

  • 需要引入Cglib的依赖jar包,通常Spring的核心包已包含Cglib功能。
  • Cglib动态代理不需要接口信息,但是它拦截并包装被代理类的所有方法。
  • 委托类不能为final,否则报错java.lang.IllegalArgumentException: Cannot subclass final class xxx。
  • 不会拦截委托类中无法重载的final/static方法,而是跳过此类方法只代理其他方法。
  • 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求。

三种代理对比

静态代理:代理类和目标类都需要实现接口,从而达到代理增强其功能。

JDK动态代理:基于Java反射机制实现,目标类必须实现接口才能生成代理对象。使用Proxy.newProxyInstance方法生成代理类,并实现InvocationHandler中的invoke方法,实现增强功能。

Cglib动态代理:基于ASM机制实现,通过生成目标类的子类作为代理类。无需实现接口,使用Cblib中的Enhancer来生成代理对象子类,并实现MethodInterceptorintercept方法来实现增强功能。

JDK动态代理的优势:JDK自身支持,减少依赖,可随着JDK平滑升级,代码实现简单。

Cglib动态代理的优势:无需实现接口,达到无侵入;只操作我们关心的类,而不必为其他相关类增加工作量;

Spring中动态代理支持

Spring的AOP实现中主要应用了JDK动态代理以及Cglib动态代理,对应的实现类位于spring-aop的jar包中。

// 基于JDK的动态代理实现类
org.springframework.aop.framework.JdkDynamicAopProxy
// 基于Cglib的动态代理实现类
org.springframework.aop.framework.CglibAopProxy

Spring默认使用JDK动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式的动态代理。如果目标对象没有实现接口,则需要使用Cglib动态代理来实现。

在了解了JDK动态代理及Cglib动态代理的使用及特性之后,大家可以对照思考一下Spring事务失效的一些场景,Spring的事务实现便是基于AOP来实现的,比如:

  • 方法使用private定义,导致事务失效:被代理方法必须是public。
  • 方法使用final修饰:如果方法被定义为final,JDK动态代理或Cglib无法重写该方法。
  • 同一类内部方法调用:直接使用this对象调用方法,无法生成代理方法,会导致事务失效。

关于Spring中动态代理的其他内容,本文就不再展开了,感兴趣的读者可直接阅读对应的源码。

小结

本文从Nacos中的静态代理模式实现,延伸拓展讲解了代理模式的定义、代理模式的运用场景、静态代理模式、动态代理模式、Cglib动态代理、Spring中AOP所使用的代理等。

通过文章中关联的知识点,以及在不同跨度的项目中的实践案例,大家应该能够感知到到代理模式,特别是基于JDK动态代理和Cglib动态代理在实践中的重要性。抓紧学一波吧。

参考文章:

https://segmentfault.com/a/1190000040407024

https://juejin.cn/post/6844903744954433544

https://www.cnblogs.com/clover-toeic/p/11715583.html

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

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

相关文章

前端三小时用html和js写一个贪吃蛇游戏,非常简单带讲解,代码可直接用,功能完整

目录 游戏主体部分--地狱模式 游戏主页入口 预览图 游戏入口代码 1.html 2.css 3.js 注册页面代码 游戏实现很简单&#xff0c;只写游戏主体的话只要三小时就够了。 话不多说&#xff0c;我们直接来看效果预览。 转成gif图之后有点卡&#xff0c;但是游戏效果并不卡&…

php宝塔搭建部署实战PESCMSTEAM团队任务管理系统源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套php开发的PESCMSTEAM团队任务管理系统源码&#xff0c;感兴趣的朋友可以自行下载学习。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTMLcnetos7以上 宝塔面板 文字搭建教程 下载源码&am…

k8s lifecycle——poststart和prestop

1、lifecycle的声明 lifecycle:postStart:exec:command: ["/bin/sh", "-c", "sleep 100"]preStop:exec:command: ["/bin/sh", "-c", "sleep 100"]2、poststart 容器创建后立即执行&#xff0c;主要用于资源部署、…

JPEG编码原理及简易编码器实现

简介 以学习为目的编写的简易jpeg编码器&#xff0c;以看得懂为目标&#xff0c;代码尽可能清晰简洁&#xff0c;不对内存、性能做看不懂的优化&#xff0c;也不实现jpeg更多高级特性。 这篇文章是我从自己的开源工程中整理来的 本文对应的工程为https://gitee.com/dma/learn…

【OpenFOAM】-olaFlow-算例4- irreg45degTank

算例路径&#xff1a; olaFlow\tutorials\irreg45degTank 算例描述&#xff1a; 不规则波浪模拟 学习目标&#xff1a; 不规则波浪模拟&#xff1a;olaFlow中单向不规则波采用线性波浪叠加法生成&#xff0c;基本原理如图2所受&#xff0c;需要提供对应波谱的周期、波高和相位的…

生产制造业管理系统对企业究竟有哪些作用?

对于生产制造企业来说&#xff0c;除了涉及到产品的生产制造和原料采购&#xff0c;还需要管理销售、库存、财务等方方面面&#xff0c;生产制造业管理系统的使用&#xff0c;尤为重要。正因如此&#xff0c;借助生产制造业管理系统来完善生产管理流程、提升生产管理水平&#…

LVGL学习笔记4 - 主题Themes

目录 1. 获取主题句柄 2. 设置基础主题 3. 设置主题的回调函数 4. 使能主题 5. 实例 5.1 定义一个全局Style变量 5.2 显示默认主题风格的矩形 5.3 初始化新主题的样式 5.4 初始化新主题 5.5 回调函数的实现 5.6 设置新主题 5.7 显示 主题是风格的集合。对应的变量结构…

设计模式--reactor 模式

说明 本文基于 tomcat 8.5.x 编写。author blog.jellyfishmix.com / JellyfishMIX - githubLICENSE GPL-2.0 介绍 reactor 模式通常应用于网络 IO 场景&#xff0c;高性能的中间件 redis, netty 都在使用。 背景 原始的网络 IO 模型 最原始的网络 IO 模型&#xff0c;服务…

Java学习笔记【8】异常

⛵ ⛵ ⛵ ⛵ ⛵ &#x1f680; &#x1f680; &#x1f680; &#x1f680; &#x1f680;   大家好&#x1f91d;&#xff0c;我是 &#x1f449;老孙&#x1f448;&#xff0c;未来学习路上多多关照 &#x1f91d; 一个喜欢用 ✍️ 博客记录人生的程序猿 &#x1f649;&…

Python遥感图像处理应用篇(二十七):Python绘制遥感图像各波段热力图(相关系数矩阵)(续)

续-https://soderayer.blog.csdn.net/article/details/125757807 上一篇中使用csv文件计算的相关系数热力图,本篇我们直接使用遥感图像来计算图像波段之间的相关系数。 方法一:已有软件ENVI计算 实际上,目前已有的软件,如ENVI就可以直接计算图像波段之间的相关系数,该工…

【高精度定位】关于GPS、RTK、PPK三种定位技术的探讨

高精度定位通常是指亚米级、厘米级以及毫米级的定位&#xff0c;从市场需求来看&#xff0c;定位的精度越高往往越好。“高精度、低成本”的定位方案无疑将是未来市场的趋势。 在物联网时代&#xff0c;大多数的应用或多或少都与位置服务相关联&#xff0c;尤其是对于移动物体而…

深入理解MySQL——分库分表种类与原则

分库分表的种类 首先说明&#xff0c;这里所说的分库分表是指把数据库中数据物理地拆分到多个实例或多台机器上去&#xff0c;而不是MySQL原生的Partitioning。 这里稍微提一下Partitioning&#xff0c;这是MySQL官方版本支持的&#xff0c;在本地针对表的分区进行操作&#…

[Flask]各种子功能的实现

一、标准Flask架构搭建 ①config.py 新建一个文件config.py&#xff0c;在其中进行参数初始化&#xff0c;再使用下面代码加载到app.py&#xff08;主程序&#xff09;中 import config app.config.from_object(config) #由config.py初始化 ②exts.py 用于放置扩展模块&a…

(二十四)Vue之props配置项

文章目录props基本使用props的数组形式props的对象形式检测类型检测类型 其他验证Vue学习目录 上一篇&#xff1a;&#xff08;二十三&#xff09;Vue之ref属性 props props 可以是数组或对象&#xff0c;用于让组件接收外部传过来的数据 约定props是只读的&#xff0c;Vue…

开源 高性能 云原生!时序数据库 TDengine 上线亚马逊Marketplace

近日&#xff0c;涛思数据旗下开源、高性能、云原生的时序数据库&#xff08;Time Series Database&#xff0c;TSDB&#xff09;TDengine 成功上线亚马逊云科技 Marketplace&#xff0c;为用户提供了更加丰富的订阅渠道。 TDengine 是针对时序数据特点研发和优化的数据库解决方…

CentOS8 Elasticsearch8.x 安装遇到的问题解决汇总

报错清单 启动报错&#xff1a;ERROR: Elasticsearch exited unexpectedly curl测试报错&#xff1a;curl: (52) Empty reply from server 报错解决 启动报错 起因 使用archive方式安装elasticsearch后&#xff0c;在目录中运行./bin/elasticsearch报错如下&#xff1a; 原…

第二十七章 数论——快速幂与逆元

第二十七章 快速幂与扩展欧几里德算法一、快速幂1、使用场景2、算法思路&#xff08;1&#xff09;二进制优化思想&#xff08;2&#xff09;模运算法则3、代码实现&#xff08;1&#xff09;问题&#xff08;2&#xff09;代码二、快速幂求逆元1、什么是逆元&#xff1f;&…

结构体位段问题

每一位勇敢努力的少年&#xff0c;必将不负众望&#xff01; 什么是位段 位段的详细解释 位段其实也是一种结构体的类型 1.位段的成员是 int ,short int unsigned int , signed int , short , char 类型 2.位段的成员名后有一个冒号和一个数字 看一个例子&#xff1a; st…

通过静态LSP、LDP LSP、MPLS TE三种方式实现总部与分支的互通

一、静态LSP 特点&#xff1a;类似静态路由&#xff0c;简单易用&#xff0c;手动建立lsp&#xff0c;定制转发路径&#xff0c;无需控制报文&#xff0c;资源消耗少。 缺点&#xff1a;不适合大型复杂拓扑&#xff0c;不能根据网络变化而动态调整&#xff0c;需要管理员手动调…

【jprofiler应用-oom原因定位】

1.安装jprofiler jprofiler_windows-x64_11_0_2.exe 2.使用KeyGen.exe生成注册码然后输入 3.idea中安装jprofiler插件 File-->Setting-->Plugins 搜索jprofiler插件然后安装 4.以一个内存溢出的程序为例子进行分析(一直分配内存&#xff0c;List容器引用着Student导致…