精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(上)

攻破Java技术盲点之剖析动态代理的实现原理和开发指南

  • 背景介绍
    • 静态代理和动态代理
      • 动态代理与静态代理的区别
  • 进入正题
    • 重温:静态代理
      • 实现静态代理案例
      • 静态代理的弊端
    • 重温:动态代理
      • Java动态代理
        • InvocationHandler
        • Java动态代理的实现
          • 下面看具体的代码实例
            • 目标接口类和对应实现类
            • 定义和实现调用处理器
            • 目标方法的具体测试效果
            • 三个参数的意义如下
            • 动态代理神奇的地方
            • 代理调用和目标调用
        • Java动态代理的好处
        • Java动态代理的总结归纳
        • Java动态代理的原理剖析
          • 代理调用的实现i原理
          • Proxy.newProxyInstance
          • java.lang.reflect.Proxy#getProxyClass0
    • java.lang.reflect.Proxy.ProxyClassFactory#apply
      • ProxyGenerator.generateProxyClass
          • 输出对应的生产proxy的class代码
        • Proxy代理源码流程总结
        • Java动态代理的弊端
      • Cglib动态代理
        • 区别在于Java代理
  • 未完待续
  • 总结分析
  • 参考资料

背景介绍

在Java编程中,动态代理的应用非常广泛。它被广泛应用于Spring AOP框架、Hibernate数据查询、测试框架的后端mock、RPC以及Java注解对象获取等领域。

静态代理和动态代理

与静态代理不同,动态代理的代理关系是在运行时确定的,这使得它在灵活性上更胜一筹。相比之下,静态代理的代理关系在编译时就确定了,实现起来相对简单,适用于代理类较少且确定的情况。然而,动态代理提供了更大的灵活性,能够更好地应对复杂的编程需求。

动态代理与静态代理的区别

在这里插入图片描述

本篇文章主要来重塑和探讨Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理。

进入正题

为了做一个参考,我们先试用一个静态代理模式的案例为基础,从而衬托出动态代理的优势和灵活,先来看看静态代理模式,先从直观的示例说起,假设我们有一个接口ProxyTest和一个简单实现ProxyTestImp,这是Java中常见的模式,使用接口来定义协议,然后通过不同的实现类来具体实现这些行为。

public interface ProxyTest{String test(String str);
}
// 实现
public class ProxyTestImp implements ProxyTest{@Overridepublic String test(String str) {return "exec: " + str;}
}

通过日志记录来追踪test()方法的调用,你可以通过静态代理来实现这一目标。

重温:静态代理

因为需要对一些函数进行二次处理,或是某些函数不让外界知道时,可以使用代理模式,通过访问第三方,间接访问原函数
的方式,达到以上目的,来看一下代理模式的类图:
在这里插入图片描述

实现静态代理案例

通过创建一个实现了相同接口的代理类,并在代理类中调用目标类的方法并记录日志,来实现对test()调用的日志记录。这样,你就可以在代理类中实现对test()方法的调用和日志记录的统一管理。

在这里插入图片描述
静态代理可以在编译时确定代理关系,实现起来相对简单。

class StaticProxiedTest implements ProxyTest{private ProxyTest proxyTest = new ProxyTestImp ();@Overridepublic String test(String str) {logger.info("You said: " + str);return proxyTest .test(str);}
}

静态代理的弊端

当需要为多个类进行代理时,建立多个代理类会导致维护难度增加。

静态代理之所以存在这些问题,是因为代理关系在编译期就已经确定。然而,如果在运行期才确定代理哪个类,那么解决这些问题会更加简单。因此,动态代理的存在变得非常必要,它提供了更大的灵活性,能够更好地应对这类问题。

重温:动态代理

动态代理模式是Java中常见的一种设计模式,它可以动态地创建代理对象,对方法进行拦截和处理。动态代理模式有两种实现方式,一种是基于Java的内置支持,称为Java动态代理;另一种是使用第三方库,如cglib。
在这里插入图片描述

Java动态代理

Java动态代理是通过接口来实现的,它要求被代理的对象必须实现一个或多个接口。在运行时,Java动态代理会生成一个实现了这些接口的代理类,该代理类继承了java.lang.reflect.Proxy类,并使用InvocationHandler作为参数来设置对方法调用的处理逻辑。

InvocationHandler

Java动态代理模式里面有个调用处理器的概念,在JDK中,实现了InvocationHandler这个接口的类就是一个调用处理器类,其中使用了些反射的相关技术。

调用处理器的概念:请求到后台服务,会先经过调用处理器,之后才会到后台服务。然后继续有之后的操作,就像一个过滤网,一层层的过滤,只要满足一定条件,才能继续向后执行。

调用处理器的作用:控制目标对象的目标方法的执行。

在这里插入图片描述

Java动态代理的实现

开发调用处理器以实现动态代理的具体操作步骤包括以下几个关键环节:

  1. 引入必要的类:在开发过程中,首先需要引入目标类以及与扩展方法相关的类库。这些类库将为后续的代理处理提供必要的支持。
  2. 对象赋值:在创建代理处理器时,通常需要通过调用目标类的构造函数来为其相关对象进行赋值。这些赋值操作对于确保代理处理的正确性和一致性至关重要。
  3. 逻辑合并:在实现动态代理的过程中,需要在invoke方法中巧妙地结合各种逻辑处理。这个方法决定了目标方法是否被调用,以及如何响应和处理这些调用。通过合理地组织这些逻辑,可以确保代理处理器能够根据需求动态地扩展和调整其行为。
下面看具体的代码实例
目标接口类和对应实现类

先定义一个代理接口类

/** * 目标接口: * 包含目标方法的声明 */  
public interface TargetInterface {  void exec();  
} 

先定义一个代理接口类的实现类,用于作为被代理的实际对象。

/** * 被代理的类 * 目标对象类 * 实现目标接口. * 继而实现目标方法。 */  
public class TargetObject implements TargetInterface {  @Override  public void exec() {  System.out.println("exec");  }  
}
定义和实现调用处理器

首先,实现一个InvocationHandler,方法调用会被转发到该类的invoke()方法。然后在需要使用TargetObject 的时候,通过JDK动态代理获取TargetObject的代理对象。

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  /** * 动态代理-拦截器 */  
public class MyInvocationHandler implements InvocationHandler {  private Object target;//目标类    public MyInterceptor(Object target) {  this.target = target;  }  /** * args 目标方法的参数 * method 目标方法 */  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  System.out.println("aaaaa");//切面方法a();  method.invoke(this.target, args);//调用目标类的目标方法  System.out.println("bbbbb");//切面方法f();  return null;  }  
}  
目标方法的具体测试效果

具体通过调用代理对象,来调用目标对象的目标方法的具体测试

import java.lang.reflect.Proxy;    
public class MainTest {  public static void main(String[] args) {  //目标对象  TargetObject target = new TargetObject();  //拦截器  MyInvocationHandler myInterceptor = new MyInvocationHandler (target);  /* *  Proxy.newProxyInstance参数: *  1、目标类的类加载器 *  2、目标类的所有的接口 *  3、拦截器 */  //代理对象,调用系统方法自动生成  TargetInterface proxyObj = (TargetInterface) 	Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), myInterceptor);  proxyObj.exec();  }  
}  

上述代码的关键是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法,该方法会根据指定的参数动态创建代理对象。

三个参数的意义如下

在这里插入图片描述
newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。

动态代理神奇的地方

在这里插入图片描述

代理调用和目标调用
  • 代理调用:在invoke()方法中,你可以自由地加入各种逻辑,比如修改方法参数、添加日志功能或安全检查功能等。通过这种方式,你可以灵活地控制代理对象的操作行为,实现更加复杂的逻辑功能。

  • 目标调用:之后我们通过某种方式执行真正的方法体,示例中通过反射调用了TargetObject对象的相应方法,还可以通过RPC调用远程方法。

注意:对于从Object中继承的方法,JDK Proxy会把hashCode()、equals()、toString()这三个非接口方法转发给InvocationHandler,其余的Object方法则不会转发

Java动态代理的好处
  • 省去了编写代理类的工作量】:通过动态代理可以很明显的看到它的好处,在使用静态代理时,如果不同接口的某些类想使用代理模式来实现相同的功能,将要实现多个代理类,但在动态代理中,只需要一个代理类就好了。

  • 灵活地重用于不同的应用场景】:动态代理实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景中。

Java动态代理的总结归纳

类比静态代理,可以发现代理类不需要实现原接口了,而是实现InvocationHandler。通过Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), this);来动态生成一个代理类,该类的类加载器与被代理类相同,实现的接口与被代理类相同,通过上述方法生成的代理类相当于静态代理中的代理类。

Java动态代理在运行期决定代理对象是怎么样的,解决了静态代理的弊端。当动态生成的代理类调用方法时,会触发invoke方法,在invoke方法中可以对被代理类的方法进行增强。

Java动态代理的原理剖析

JDK的动态代理的类看不见摸不着,虽然可以看到效果,但是底层到底是怎么做的,为什么要求实现接口呢?

代理调用的实现i原理

上文说了,当动态生成的代理类调用方法时,会触发invoke方法。很显然invoke方法并不是显示调用的,它是一个回调机制,那么回调机制是怎么被调用的呢?

上述动态代理的代码中,唯一不清晰的地方只有Proxy创建代理对象,如下所示:

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
Proxy.newProxyInstance

我们先来分析一下对应的JDK的源码:

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{// 判空,判断 h 对象是否为空,为空就抛出 NullPointerExceptionObjects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {// 进行包访问权限、类加载器等权限检查checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.* 查找或生成指定的代理类*/Class<?> cl = getProxyClass0(loader, intfs);// 省略若干代码}

第一步,尝试获取代理类,该代理类可能会被缓存,如果没有缓存,那么进行生成逻辑.

java.lang.reflect.Proxy#getProxyClass0
    private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {// 数量超过 65535 就抛出异常,665535 这个就不用说了吧if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// 如果代理类已经通过类加载器对给定的接口进行实现了,那么从缓存中返回其副本// 否则,它将通过ProxyClassFactory创建代理类return proxyClassCache.get(loader, interfaces);}

最后发现会对生成的代理类进行缓存,有了,就不直接返回,没有的,还得生成代理类,我们继续往下走:

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

java.lang.reflect.Proxy.ProxyClassFactory#apply

关键点在于 ProxyClassFactory 这个类,从名字也可以猜出来这个类的作用。看看代码:

/*** A factory function that generates, defines and returns the proxy class given* the ClassLoader and array of interfaces.*/private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>{// prefix for all proxy class names 定义前缀private static final String proxyClassNamePrefix = "$Proxy";// next number to use for generation of unique proxy class names  原子操作,适用于多线程private static final AtomicLong nextUniqueNumber = new AtomicLong();public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) {/** Verify that the class loader resolves the name of this* interface to the same Class object.*/Class<?> interfaceClass = null;try {// 通过反射获取到接口类interfaceClass = Class.forName(intf.getName(), false, loader);} catch (ClassNotFoundException e) {}// 所得到的接口类与传进来的不相等,说明不是同一个类if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}/** Verify that the Class object actually represents an* interface.*/if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}/** Verify that this interface is not a duplicate. */if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());}}String proxyPkg = null;     // package to define proxy class inint accessFlags = Modifier.PUBLIC | Modifier.FINAL;/** Record the package of a non-public proxy interface so that the* proxy class will be defined in the same package.  Verify that* all non-public proxy interfaces are in the same package.*/for (Class<?> intf : interfaces) {int flags = intf.getModifiers();if (!Modifier.isPublic(flags)) {accessFlags = Modifier.FINAL;String name = intf.getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) {// if no non-public proxy interfaces, use com.sun.proxy packageproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** Choose a name for the proxy class to generate.*/long num = nextUniqueNumber.getAndIncrement();// 生产代理类的名字String proxyName = proxyPkg + proxyClassNamePrefix + num;// 一些验证、缓存、同步的操作,不是我们研究的重点/** Generate the specified proxy class.* 生成特殊的代理类*/byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {/** A ClassFormatError here means that (barring bugs in the* proxy class generation code) there was some other* invalid aspect of the arguments supplied to the proxy* class creation (such as virtual machine limitations* exceeded).*/throw new IllegalArgumentException(e.toString());}}}

ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);``,这段代码即为生成动态代理类的关键,执行完后会返回该描述该代理类的字节码数组.随后程序读取该字节码数组,将其转化为运行时的数据结构-Class对象,作为一个常规类使用.

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);final byte[] var4 = var3.generateClassFile();// 如果声明了需要持久化代理类,则进行磁盘写入.if (saveGeneratedFiles) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {try {int var1 = var0.lastIndexOf(46);Path var2;if (var1 > 0) {Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));Files.createDirectories(var3);var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");} else {var2 = Paths.get(var0 + ".class");}Files.write(var2, var4, new OpenOption[0]);return null;} catch (IOException var4x) {throw new InternalError("I/O exception saving generated file: " + var4x);}}});}return var4;}

这里我们找到了一个关键的判断条件-saveGeneratedFiles,即是否需要将代理类进行持久化.

ProxyGenerator.generateProxyClass

public class ProxyGeneratorUtils {/*** 把代理类的字节码写到硬盘上 * @param path 保存路径 */public static void writeProxyClassToHardDisk(String path) {
// 获取代理类的字节码  byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", Student.class.getInterfaces());FileOutputStream out = null;try {out = new FileOutputStream(path);out.write(classFile);out.flush();} catch (Exception e) {e.printStackTrace();} finally {try {out.close();} catch (IOException e) {e.printStackTrace();}}}
}

跟踪这个方法的源码,可以看到程序进行了验证、优化、缓存、同步、生成字节码、显示类加载等操作,前面的步骤并不是我们关注的重点,而最后它调用了

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

该方法用来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码byte[]数组

输出对应的生产proxy的class代码

在main函数中加入System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");,会在根目录下生成了一个 $Proxy0.class 文件,把Class文件反编译后可以看见如下代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.TargetObject;public final class $Proxy0 extends Proxy implements TargetObject
{private static Method m1;private static Method m2;private static Method m3;private static Method m0;/*** 注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白* 为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个* 被代理对象的实例** super(paramInvocationHandler),是调用父类Proxy的构造方法。* 父类持有:protected InvocationHandler h;* Proxy构造方法:*    protected Proxy(InvocationHandler h) {*         Objects.requireNonNull(h);*         this.h = h;*    }**/public $Proxy0(InvocationHandler paramInvocationHandler)throws {super(paramInvocationHandler);}//这个静态块本来是在最后的,我把它拿到前面来,方便描述static{try{//看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("proxy.TargetObject").getMethod("exec", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException localNoSuchMethodException){throw new NoSuchMethodError(localNoSuchMethodException.getMessage());}catch (ClassNotFoundException localClassNotFoundException){throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}/*** *这里调用代理对象的exec方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。*this.h.invoke(this, m3, null);这里简单,明了。*来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,*再联系到InvacationHandler中的invoke方法。嗯,就是这样。*/public final void exec() throws {try{this.h.invoke(this, m3, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。}

动态代理类不仅代理了显示定义的接口中的方法,而且还代理了java的根类Object中的继承而来的equals()、hashcode()、toString()这三个方法,并且仅此三个方法。可以在上述代码中看到,无论调用哪个方法,都会调用到InvocationHandler的invoke方法,只是参数不同。

Proxy代理源码流程总结

在这里插入图片描述

Java动态代理的弊端

代理类和委托类需要实现同一个接口,这意味着只有实现了某个接口的类才能使用Java动态代理机制。然而,在实际情况中,并非所有类都会实现接口。因此,对于没有实现接口的类,Java动态代理机制无法使用。而CGLIB则可以实现对类的动态代理,弥补了Java动态代理的不足之处。


Cglib动态代理

cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

而cglib是基于字节码的库,可以在运行时动态地创建子类并覆盖方法。相比Java动态代理,cglib的使用更加灵活,因为它不需要被代理的对象实现接口。同时,cglib还支持对私有方法的拦截和处理。

区别在于Java代理

在这里插入图片描述

未完待续

由于篇幅过长,会引起视觉疲劳和大脑疲劳,故此,作者会将cglib的的原理和实现放到了下一章:精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(下),希望大家多多消化本章节内容,等待下一章的到来。

总结分析

本文介绍了Java两种常见动态代理机制的用法和原理,JDK原生动态代理是Java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理;CGLIB通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理final的情况。

动态代理是Spring AOP(Aspect Orient Programming, 面向切面编程)的实现方式,了解动态代理原理,对理解Spring AOP大有帮助。

参考资料

  • 【JDK Proxy官方文档】
  • 【cglib文档】
  • 【asm文档】

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

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

相关文章

Blazor项目如何调用js文件

以下是来自千问的回答并加以整理&#xff1a;&#xff08;说一句&#xff0c;文心3.5所给的回答不完善&#xff0c;根本运行不起来&#xff0c;4.0等有钱了试试&#xff09; 在Blazor项目中引用JavaScript文件&#xff08;.js&#xff09;以实现与JavaScript的互操作&#xff…

基于JAVA的服装店库存管理系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 服装档案模块2.4 服装入库模块2.5 服装出库模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 角色表3.2.2 服装档案表3.2.3 服装入库表3.2.4 服装出库表 四、系统展示五、核心代码5.…

Java 11中的新字符串APIs详解

第1章 引言 大家好&#xff0c;我是小黑&#xff0c;咱们都知道&#xff0c;Java作为一种广泛使用的编程语言&#xff0c;每一次更新都会带来不少新鲜事物。而Java 11&#xff0c;作为长期支持&#xff08;LTS&#xff09;版本之一&#xff0c;更是引起了广大开发者的关注。好…

并发(8)

目录 46.Thread.sleep(),Object.wait(),Condition.await(),LockSupport.part()的区别&#xff1f; 47.如果在wait&#xff08;&#xff09;之前执行了notify&#xff08;&#xff09;会怎样&#xff1f; 48.如果在park()之前执行了unpark()会怎样&#xff1f; 49.什么是AQS&…

九州金榜|孩子厌学,作为父母有想做自己的原因吗?

孩子不会天生就厌学&#xff0c;如果孩子天生厌学&#xff0c;那么孩子就不可能学会说话&#xff0c;走路&#xff0c;日常生活&#xff0c;更不可能去上学&#xff0c;孩子厌学因素非常多&#xff0c;而作为父母&#xff0c;你有没有想过是你的原因造成的呢&#xff1f;九州金…

编程语言的未来,通用代码生成器和超级语言

编程语言的未来&#xff0c;通用代码生成器和超级语言 我们生活在一个编程语言种类繁多&#xff0c;百花齐放的年代。形形色色的编程语言覆盖了软件开发的方方面面。如果说这些变成语言有什么共性的话&#xff0c;大家都知道&#xff0c;大多数编程语言是高级语言。 何为高级…

K8S---通过curl访问api

1、列出所有的接口 curl --cacert /opt/kubernetes/ssl/ca.pem --cert /opt/kubernetes/ssl/default-admin.pem --key /opt/kubernetes/ssl/default-admin-key.pem https://10.240.184.74:6443/ 2、列出核心api的版本 curl --cacert /opt/kubernetes/ssl/ca.pem -…

手敲MyBatis(十四章)-解析含标签的动态SQL语句

1.前言 这一章主要的就是要解析动态标签里的Sql语句&#xff0c;然后进行条件语句的拼接&#xff0c;动态标签实现了trim和if标签&#xff0c;所以Sql节点就要加上TrimSqlNode和ifSqlNode&#xff0c;我们最终要获取Sql源&#xff0c;动态Sql语句需要一些处理&#xff0c;所以…

SpringCloud系列篇:核心组件之声明式HTTP客户端组件【远程消费】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringCloud的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一. 远程消费组件是什么 二. 远程消…

竞赛练一练 第22期:NOC大赛每日一练,python题目刷题第7天,包含答案解析

题目来自:NOC大赛创客智慧编程赛项Python 复赛模拟题(一) 第一题: 编写一个彩票游戏:随机生成一个不重复的五位数作为彩票号,游戏提示用户输入一个五位整数,然后根据下面的规则判断用户是否能赢得奖金,最后要求输出彩票号和奖金。 (1)若用户输入的数字和彩票的数字完…

c语言考试代码

文章目录 文件操作题 “%”&#xff1a;格式化字符串的起始标志。 “0”&#xff1a;表示使用零来填充输出字段的宽度。 “4”&#xff1a;表示输出字段的宽度为4个字符&#xff0c;如果输出的十六进制数不足4位&#xff0c;则在左边用零进行填充。 “x”&#xff1a;表示以十六…

MySql海量数据存储与优化

一、Mysql架构原理和存储机制 1.体系结构 2.查询缓存 3.存储引擎 存储引擎的分类 innodb&#xff1a;支持事务&#xff0c;具有支持回滚&#xff0c;提交&#xff0c;崩溃恢复等功能&#xff0c;事务安全myisam:不支持事务和外键&#xff0c;查询速度高Memory&#xff1a;利…

Spring Boot实现数据加密脱敏:注解 + 反射 + AOP

文章目录 1. 引言2. 数据加密和脱敏的需求3. Spring Boot项目初始化4. 敏感数据加密注解设计5. 实现加密和脱敏的工具类6. 实体类和加密脱敏注解的使用7. 利用AOP实现加密和脱敏8. 完善AOP切面9. 测试10. 拓展功能与未来展望10.1 加密算法的选择10.2 动态注解配置 11. 总结 &am…

CHS_02.1.1.2+操作系统的特征

CHS_02.1.1.2操作系统的特征 操作系统的四个特征并发这个特征为什么并发性对于操作系统来说是一个很重要的基本特性资源共享虚拟异步性 各位同学 大家好 在这个小节当中 我们会学习 操作系统的四个特征 操作系统有并发 共享 虚拟和异部这四个基本的特征 其中 并发和共享是两个…

机器人的末端执行器由什么零件组成,有什么作用。

问题描述&#xff1a;机器人的末端执行器由什么零件组成&#xff0c;有什么作用。 问题解答&#xff1a; 机器人的末端执行器是机器人机械结构的最末端部分&#xff0c;用于执行具体的任务和与环境进行交互。末端执行器通常由多个零部件组成&#xff0c;其主要作用是完成机器…

log4j RCE漏洞原理分析及检测

实现原理 log4j支持使用表达式的形式打印日志&#xff0c;比如 logger.info("system propety: ${sys:user.dir}");问题就在与表达式支持非常多样&#xff0c;其中有一个jndi就是今天的主题 logger.info("system propety: ${jndi:schema://url}");jdk将从…

学习笔记——C++ do while语句

作用&#xff1a;满足循环条件&#xff0c;执行循环语句 语法&#xff1a;do{循环语句}while{循环条件}&#xff1b; 注意&#xff1a;与while的区别在于do while 会先执行一次循环语句&#xff0c;再判断循环条件。 示例&#xff1a;打印0-9的数字 #include<bits/stdc.h…

图神经网络|9.3 邻接矩阵的变换

由于邻接矩阵中一般不会&#xff08;i,i&#xff09;等于1&#xff0c;除非第i个点上有自环。 而如果用邻接矩阵去乘上特征矩阵&#xff0c;那么将丢失自身向自身的贡献。 此时可以再邻接矩阵的基础上&#xff0c;再加上一个单位阵&#xff0c;从而使得最终的结果包含自身对整体…

RedisInsight - Redis官方可视化工具

一、RedisInsight 简介 RedisInsight 是一个直观高效的 Redis GUI 管理工具&#xff0c;它可以对 Redis 的内存、连接数、命中率以及正常运行时间进行监控&#xff0c;并且可以在界面上使用 CLI 和连接的 Redis 进行交互&#xff08;RedisInsight 内置对 Redis 模块支持&#…

JavaWeb——新闻管理系统(Jsp+Servlet)之jsp新闻新增

java-ee项目结构设计 1.dao:对数据库的访问&#xff0c;实现了增删改查 2.entity:定义了新闻、评论、用户三个实体&#xff0c;并设置对应实体的属性 3.filter&#xff1a;过滤器&#xff0c;设置字符编码都为utf8&#xff0c;防止乱码出现 4.service:业务逻辑处理 5.servlet:处…