SpringCloud(20)之Skywalking Agent原理剖析
一、Agent原理剖析
使用Skywalking的时候,并没有修改程序中任何一行 Java 代码,这里便使用到了 Java Agent 技术,我 们接下来展开对Java Agent 技术的学习。
1.1 Java Agent
Java Agent 是从 JDK1.5 开始引入的,算是一个比较老的技术了。作为 Java 的开发工程师,我们常用的 命令之一就是 java 命令,而 Java Agent 本身就是 java 命令的一个参数(即-javaagent)。正如上一课 时接入 SkyWalking Agent 那样, -javaagent 参数之后需要指定一个 jar 包,这个 jar 包需要同时满足下 面两个条件:
- 在META-INF目录下的MANIFEST.MF文件中必须指定premain-class配置项;
- premain-class配置项指定的类必须提供premain()方法。
在 Java 虚拟机启动时,执行 main() 函数之前,虚拟机会先找到-javaagent 命令指定 jar 包,然后执行 premain-class 中的 premain() 方法。用一句概括其功能的话就是:main() 函数之前的一个拦截器。
使用Java Agent的步骤大概如下:
- 定义一个MANIFEST.MF文件,在其中添加premain-class配置项;
- 创建premain-class配置项指定的类,并在其中实现premain()方法,方法如下:
- 将MANIFEST.MF文件和premain-class指定的类一起打包成一个jar包;
- 使用-javaagent指定该jar包的路径可执行其中的premain()方法;
/**** 执行方法拦截* @param agentArgs:-javaagent 命令携带的参数。在前面介绍 SkyWalking Agent 接入时提到* agent.service_name 这个配置项的默认值有三种覆盖方式,* 其中,使用探针配置进行覆盖,探针配置的值就是通过该参数传入的。* @param inst:java.lang.instrumen.Instrumentation 是 Instrumention 包中定义的一个接口,它提供了操作类定义的相关方法。*/public static void premain(String agentArgs, Instrumentation inst){System.out.println("参数:" + agentArgs);}
1.2定义自己的Agent
1)探针工程
创建工程 hailtaxi-agent用来编写agent包,该类需要用 maven-assembly-plugin打包,我们先引入 该插件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.itheima</groupId><artifactId>hailtaxi-agent</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.9.2</version></dependency><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId><version>1.9.2</version></dependency></dependencies><build><plugins><plugin><artifactId>maven-assembly-plugin</artifactId><configuration><appendAssemblyId>false</appendAssemblyId><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive> <!--自动添加META-INF/MANIFEST.MF --><manifest><!-- 添加 mplementation-*和Specification-*配置项--><addDefaultImplementationEntries>true</addDefaultImplementationEntries><addDefaultSpecificationEntries>true</addDefaultSpecificationEntries></manifest><!-- 将 premain-class 配置项设置为com.jokermqc.LoginAgent--><manifestEntries><Premain-Class>com.jokermqc.LoginAgent</Premain-Class><!--<Premain-Class>com.jokermqc.AgentByteBuddy</Premain-Class>--></manifestEntries></archive></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins></build>
</project>
在该工程中编写一个类 com.itheima.LoginAgent :
public class LoginAgent {/**** 执行方法拦截* @param agentArgs:-javaagent 命令携带的参数。在前面介绍 SkyWalking Agent 接入时提到* agent.service_name 这个配置项的默认值有三种覆盖方式,* 其中,使用探针配置进行覆盖,探针配置的值就是通过该参数传入的。* @param inst:java.lang.instrumen.Instrumentation 是 Instrumention 包中定义的一个接口,它提供了操作类定义的相关方法。*/public static void premain(String agentArgs, Instrumentation inst){System.out.println("参数:" + agentArgs);}
}
从字面上理解,就是运行在main()函数之前的类。在Java虚拟机启动时,在执行main()函数之前,会先运行指定类的premain()方法,在premain()方法中对class文件进行修改,它有两个入参:
agentArgs:启动参数,在JVM启动时指定;
instrumentation:上文所将的Instrumentation的实例,我们可以在方法中调用上文所讲的方法,注册对应的Class转换器,对Class文件进行修改
如下图,借助Instrumentation,JVM启动时的处理流程是这样的:JVM会执行指定类的premain()方法,在premain()中可以调用Instrumentation对象的addTransformer方法注册ClassFileTransformer。当JVM加载类时会将类文件的字节数组传递给ClassFileTransformer的transform方法,在transform方法中对Class文件进行解析和修改,之后JVM就会加载转换后的Class文件:
然后把工程进行打包,得到hailtaxi-agent-1.0-SNAPASHOT.jar,这个就是对应的探针包。
此时我们把jar包解压, MANIFEST.MF 内容如下:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: admin
Build-Jdk: 1.8.0_91
Specification-Title: hailtaxi-agent
Specification-Version: 1.0-SNAPSHOT
Implementation-Title: hailtaxi-agent
Implementation-Version: 1.0-SNAPSHOT
Implementation-Vendor-Id: com.itheima
Premain-Class: com.itheima.LoginAgent
2)普通工程
我们在创建一个普通工程 hailtaxi-user ,在该工程中创建一个普通类并编写main方法:
public class UserInfo {public static void main(String[] args) throws InterruptedException {System.out.println("张三是个中国人!");//调用say()方法say();TimeUnit.SECONDS.sleep(2);}
}
然后我们再在启动命令中加上刚刚生成的agent包,启动命令如下:
-javaagent:D:/project/skywalking/hailtaxi-agent/target/hailtaxi-agent-1.0- SNAPSHOT.jar=hailtaxi-user
此时运行效果如下:
1.3 自定义方法统计方法耗时
Java Agent 能做的事情非常多,而刚才打印一句日志只是一个能功能展示。要想使用 java agent做 更多事,这里需要关注一下 premain() 方法中的第二个参数: Instrumentation 。Instrumentation 位于 java.lang.instrument 包中,通过这个工具包,我们可以编写一个强大的 Java Agent 程序。
因为Instrumentation操作字节码非常麻烦,所以一般不会通过Instrumentation 来操作字节码,而是通过Byte Buddy,下面来介绍一下byte Buddy。
1.3.1Byte Buddy介绍
Byte Buddy 是一个开源 Java 库,其主要功能是帮助用户屏蔽字节码操作,以及复杂的
Instrumentation API 。 Byte Buddy 提供了一套类型安全的 API 和注解,我们可以直接使用这些 API 和 注解轻松实现复杂的字节码操作。另外,Byte Buddy 提供了针对 Java Agent 的额外 API,帮助开发人 员在 Java Agent 场景轻松增强已有代码。
引入Byte Buddy依赖:
<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.9.2</version></dependency><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId><version>1.9.2</version></dependency>
创建统计拦截器:
/*** @author maoqichaun* @date 2024年03月05日 18:47*/
public class MethodTimeInterceptor {/*** 这里有一点类似于Spring AOP** @param method 拦截的方法* @param callable 调用对象的代理对象* @return java.lang.Object* @author maoqichuan* @date 2024/3/5*/@RuntimeTypepublic static Object interceptor(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {//时间统计开始long start = System.currentTimeMillis();// 执行原函数Object result = callable.call();//执行时间统计System.out.println(method.getName() + ":" + (System.currentTimeMillis() - start) + "ms");return result;}
}
这里整体实现类似动态代理执行过程,也类似SpringAop中的环绕通知,其中几个注解我们一起来学习 一下:
@RuntimeType 注解:告诉 Byte Buddy 不要进行严格的参数类型检测,在参数匹配失败时,尝试使用 类型转换方式(runtime type casting)进行类型转换,匹配相应方法。
@Origin 注解:注入目标方法对应的 Method 对象。如果拦截的是字段的话,该注解应该标注到 Field 类型参数。
@SuperCall:这个注解比较特殊,我们要在 intercept() 方法中调用目标方法的话,需要通过这种方 式注入,与 Spring AOP 中的 ProceedingJoinPoint.proceed() 方法有点类似,需要注意的是,这里不能修改调用参数,从上面的示例的调用也能看出来,参数不用单独传递,都包含在其中了。另外, @SuperCall 注解还可以修饰 Runnable 类型的参数,只不过目标方法的返回值就拿不到了。
配置agent拦截:
public class AgentByteBuddy {/**** 执行方法拦截* @param agentArgs:-javaagent 命令携带的参数。在前面介绍 SkyWalking Agent 接入时提到* agent.service_name 这个配置项的默认值有三种覆盖方式,* 其中,使用探针配置进行覆盖,探针配置的值就是通过该参数传入的。* @param inst:java.lang.instrumen.Instrumentation 是 Instrumention 包中定义的一个接口,它提供了操作类定义的相关方法。*/public static void premain(String agentArgs, Instrumentation inst) throws IllegalAccessException, InstantiationException {//动态构建操作,根据transformer规则执行拦截操作AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {@Overridepublic DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,TypeDescription typeDescription,ClassLoader classLoader,JavaModule javaModule) {//构建拦截规则return builder//method()指定哪些方法需要被拦截,ElementMatchers.any()表示拦截所有方法.method(ElementMatchers.<MethodDescription>any())//intercept()指定拦截上述方法的拦截器.intercept(MethodDelegation.to(MethodTimeInterceptor.class));}};//采用Byte Buddy的AgentBuilder结合Java Agent处理程序new AgentBuilder//采用ByteBuddy作为默认的Agent实例.Default()//拦截匹配方式:类以com.itheima开始(其实即使com.jokermqc包下的所有类).type(ElementMatchers.nameStartsWith("com.jokermqc"))//拦截到的类由transformer处理.transform(transformer)//安装到 Instrumentation.installOn(inst);}
}
同时将pom.xml中的premain-class替换成 AgentByteBuddy,然后javaagent的jar替换一下即可生效;
二、 Byte Buddy
在前面学习 Java Agent 技术时,结合 Byte Buddy 技术实现了统计方法执行时间的功能。 Byte Buddy 在Skywalking中被广泛使用,我们接下来继续学习Byte Buddy,为后续分析 SkyWalking Agent打下基 础。
2.1 Byte Buddy 应用场景
Java 是一种强类型的编程语言,即要求所有变量和对象都有一个确定的类型,如果在赋值操作中出现类 型不兼容的情况,就会抛出异常。强类型检查在大多数情况下是可行的,然而在某些特殊场景下,强类 型检查则成了巨大的障碍。
我们在做一些通用工具封装的时候,类型检查就成了很大障碍。比如我们编写一个通用的Dao实现数据 操作,我们根本不知道用户要调用的方法会传几个参数、每个参数是什么类型、需求变更又会出现什么 类型,几乎没法在方法中引用用户方法中定义的任何类型。我们绝大多数通用工具封装都采用了反射机 制,通过反射可以知道用户调用的方法或字段,但是Java反射有很多缺陷:
1:反射性能很差
2:反射能绕开类型安全检查,不安全,比如权限暴力破解
学完agent后,我们可以基于agent做出一些改变,运行时代码生成在 Java 应用启动之后再动态生成一 些类定义,这样就可以模拟一些只有使用动态编程语言编程才有的特性,同时也不丢失 Java 的强类型 检查。在运行时生成代码需要特别注意的是 Java 类型被 JVM 加载之后,一般不会被垃圾被回收,因此 不应该过度使用代码生成。
java编程语言代码生成库不止 Byte Buddy一个,以下代码生成库在 Java 中也很流行:
- Java Proxy:Java Proxy 是 JDK 自带的一个代理工具,它允许为实现了一系列接口的类生成代理类。Java Proxy 要求 目标类必须实现接口是一个非常大限制,例如,在某些场景中,目标类没有实现任何接口且无法修改目 标类的代码实现,Java Proxy 就无法对其进行扩展和增强了。
- CGLIB:CGLIB 诞生于 Java 初期,但不幸的是没有跟上 Java 平台的发展。虽然 CGLIB 本身是一个相当强大的 库,但也变得越来越复杂。鉴于此,导致许多用户放弃了 CGLIB 。
- Javassist:Javassist 的使用对 Java 开发者来说是非常友好的,它使用Java 源代码字符串和 Javassist 提供的一些简 单 API ,共同拼凑出用户想要的 Java 类,Javassist 自带一个编译器,拼凑好的 Java 类在程序运行时会 被编译成为字节码并加载到 JVM 中。Javassist 库简单易用,而且使用 Java 语法构建类与平时写 Java 代 码类似,但是 Javassist 编译器在性能上比不了 Javac 编译器,而且在动态组合字符串以实现比较复杂
的逻辑时容易出错。
-
Byte Buddy:Byte Buddy 提供了一种非常灵活且强大的领域特定语言,通过编写简单的 Java 代码即可创建自定义的 运行时类。与此同时,Byte Buddy 还具有非常开放的定制性,能够应付不同复杂度的需求。
上面所有代码生成技术中,我们推荐使用Byte Buddy,因为Byte Buddy代码生成可的性能最高,Byte Buddy 的主要侧重点在于生成更快速的代码,如下图:
2.2 Byte buddy学习
我们接下来详细讲解一下Byte Buddy Api ,对重要的方法和类进行深度剖析。
2.2.1 ByteBuddy语法
任何一个由 Byte Buddy 创建/增强的类型都是通过 ByteBuddy 类的实例来完成的,我们先来学习一下 ByteBuddy类,如下代码:
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()// 生成 Object的子类.subclass(Object.class)// 生成类的名称为"com.jokermqc.Type".name("com.jokermqc.Type").make();
Byte Buddy 动态增强代码总共有三种方式:
subclass:对应 ByteBuddy.subclass() 方法。这种方式比较好理解,就是为目标类(即被增强的类) 生成一个子类,在子类方法中插入动态代码。
rebasing:对应 ByteBuddy.rebasing() 方法。当使用 rebasing 方式增强一个类时,Byte Buddy 保存目标类中所有方法的实现,也就是说,当 Byte Buddy 遇到冲突的字段或方法时,会将原来的字段或 方法实现复制到具有兼容签名的重新命名的私有方法中,而不会抛弃这些字段和方法实现。从而达到不丢失 实现的目的。这些重命名的方法可以继续通过重命名后的名称进行调用。
redefinition:对应 ByteBuddy.redefine() 方法。当重定义一个类时,Byte Buddy 可以对一个已 有的类添加属性和方法,或者删除已经存在的方法实现。如果使用其他的方法实现替换已经存在的方法实现,则原来存在的方法实现就会消失。
通过上述三种方式完成类的增强之后,我们得到的是 DynamicType.Unloaded 对象,表示的是一个未 加载的类型,我们可以使用 ClassLoadingStrategy 加载此类型。 Byte Buddy 提供了几种类加载策略, 这些策略定义在 ClassLoadingStrategy.Default 中,其中:
- WRAPPER 策略:创建一个新的 ClassLoader 来加载动态生成的类型。
- CHILD_FIRST 策略:创建一个子类优先加载的 ClassLoader ,即打破了双亲委派模型。
- INJECTION 策略:使用反射将动态生成的类型直接注入到当前 ClassLoader 中。
实现如下:
Class<?> dynamicClazz = new ByteBuddy()// 生成 Object的子类.subclass(Object.class)// 生成类的名称为"com.joker.Type".name("com.joker.Type").make().load(Demo.class.getClassLoader()//使用WRAPPER 策略加载生成的动态类型.ClassLoadingStrategy.Default.WRAPPER).getLoaded();
前面动态生成的dynamicType类只是简单的继承了Object类,在实际的应用中动态生成的新类型一般的目的就是为了增强原始的方法,下面通过一个示例展示Byte Buddy如何增强toString()方法;
// 创建ByteBuddy对象String str = new ByteBuddy()// subclass增强方式.subclass(Object.class)// 新类型的类名.name("com.joker.Type")// 拦截其中的toString()方法.method(ElementMatchers.named("toString"))// 让toString()方法返回固定值.intercept(FixedValue.value("Hello World!")).make()// 加载新类型,默认WRAPPER策略.load(ByteBuddy.class.getClassLoader()).getLoaded()// 通过 Java反射创建 com.xxx.Type实例.newInstance()// 调用 toString()方法.toString();
首先需要关注的是这里的method方法,method()方法可以通过传入的ElementMatchers参数匹配多个需要修改的方法,这的ElementMarchers.named("toString")就是按照方法名来匹配。如果通过存在多个重载方法,也可以使用ElementMarchers其他API来进一步描述方法的签名,如下所示:
// 指定方法名称ElementMatchers.named("toString")// 指定方法的返回值.and(ElementMatchers.returns(String.class))// 指定方法参数.and(ElementMatchers.takesArguments(0));
接下来要关注的是intercept方法,通过method方法拦截到的所有方法会有intercept()方法指定的Implementtation对象决定如何增强,这里的FixValue.value()会将方法的视线修改为固定值,上面的例子就是固定返回字符串:“Hello World!”。
2.2.2 ByteBuddy创建代理
我们先创建一个普通类,再为该类创建代理对象,创建代理对方法进行拦截处理。
1)普通类:
public class UserService {//方法1public String username(){System.out.println("com.jokermqc.service.UserService.username.....");return "张三";}//方法2public String address(String username){System.out.println("com.jokermqc.service.UserService.address(String username).....");return username+"来自 【湖北省武汉市】";}//方法3public String address(String username,String city){System.out.println("com.jokermqc.service.UserService.address(String username,String city).....");return username+"来自 【湖北省"+city+"】";}
}
2)创建代理对象
//创建ByteBuddyUserService userService = new ByteBuddy()//指定创建UserServiceImpl对象的子类.subclass(UserService.class)//匹配方法,所有方法均被拦截.method(ElementMatchers.isDeclaredBy(UserService.class))//任何拦截都返回一个固定值.intercept(MethodDelegation.to(new AspectLog()))//创建动态对象.make().load(ByteBuddy.class.getClassLoader(),ClassLoadingStrategy.Default.INJECTION).getLoaded().newInstance();userService.username();userService.address("王五","武汉");userService.address("张三");
2.2.3 ByteBuddy在程序中的应用
上面我们创建代理的案例中,把返回值设置成了固定值,但在真实程序汇总通常是要做特定业务流程处 理,比如事务、日志、权限校验等,此时我们需要用到ByteBuddy的MethodDelegation对象,它可以 将拦截的目标方法委托给其他对象处理,这里有几个注解我们先进行说明:
-
@RuntimeType:不进行严格的参数类型检测,在参数匹配失败时,尝试使用类型转换方式(runtime typecasting)进行类型转换,匹配相应方法;
-
@This:注入被拦截的目标对象。
-
@AllArguments:注入目标方法的全部参数。
-
Origin:注入目标方法对应的 Method 对象。如果拦截的是字段的话,该注解应该标注到 Field 类型参数。
-
@Super:注入目标对象。通过该对象可以调用目标对象的所有方法。
-
@SuperCall:这个注解比较特殊,我们要在 intercept() 方法中调用目标方法的话,需要通过这种 方式注入,与 Spring AOP 中的 ProceedingJoinPoint.proceed() 方法有点类似,需要注意的是, 这里不能修改调用参数,从上面的示例的调用也能看出来,参数不用单独传递,都包含在其中了。 另外,@SuperCall 注解还可以修饰 Runnable 类型的参数,只不过目标方法的返回值就拿不到了。
我们对上面的源对象userservice进行一个增强,做一个日志切面;
1)创建代理对象
public static void main(String[] args) throws Exception {//创建ByteBuddyUserService userService = new ByteBuddy()//指定创建UserServiceImpl对象的子类.subclass(UserService.class)//匹配方法,所有方法均被拦截.method(ElementMatchers.isDeclaredBy(UserService.class))//任何拦截都返回一个固定值.intercept(MethodDelegation.to(new AspectLog()))//创建动态对象.make().load(ByteBuddy.class.getClassLoader(),ClassLoadingStrategy.Default.INJECTION).getLoaded().newInstance();userService.username();userService.address("王五","武汉");userService.address("张三");}
2)增强处理类
public class AspectLog {@RuntimeTypepublic Object intercept(// 目标对象@This Object obj,// 注入目标方法的全部参数@AllArguments Object[] allArguments,// 调用目标方法,必不可少@SuperCall Callable<?> zuper,// 目标方法@Origin Method method,// 目标对象@Super Object instance) throws Exception {//目标方法执行前执行日志记录System.out.println("准备执行Method="+method.getName());// 调用目标方法Object result = zuper.call();//目标方法执行后执行日志记录System.out.println("方法执行完成Method="+method.getName());return result;}
}
运行结果如下:
以上就是Skywalking Agent原理的解析,主要是介绍了什么是Java Agent,以及Byte Buddy的使用,因为在Skywalking中就是使用了ByteBuddy对字节码进行增强,有了这个技术基础才能更好的理解Skywalking源码;
相关文章:

SpringCloud(20)之Skywalking Agent原理剖析
一、Agent原理剖析 使用Skywalking的时候,并没有修改程序中任何一行 Java 代码,这里便使用到了 Java Agent 技术,我 们接下来展开对Java Agent 技术的学习。 1.1 Java Agent Java Agent 是从 JDK1.5 开始引入的,算是一个比较老的…...

容器(0)-DOCKERFILE-安装-常用命令-部署-迁移备份-仓库
1.安装 启动 systemclt start docker //启动 systemctl status docker //状态 docker info systemclt stop docker systemctl status docker systemctl enable docker //开机启动 2.常用命令 镜像查看 docker images 镜像查看 docker status 镜像拉取 docker pull centos:…...

低功耗DC-DC电压调整器IU5528D
IU5528D是一款超微小型,超低功耗,高效率,升降压一体DC-DC调整器。适用于双节,三节干电池或者单节锂电池的应用场景。可以有效的延长电池的使用时间。IU5528D由电流模PWM控制环路,误差放大器,比较器和功率开关等模块组成。该芯片可在较宽负载范围内高效稳…...
【备战蓝桥杯系列】单源最短路径Dijkstra算法模板
Dijkstra算法模板 蓝桥杯中也是会考到图论最短路的,一旦考到,基本是不会太难的,只要知道板子就基本能拿分了。 两个板子如下 朴素Dijkstra算法 适应情况:稠密图,正权边 时间复杂度 O(n^2 m) int dijkst(){memse…...

嵌入式系统中端口号的理解与分析
每当看到有人的简历上写着熟悉 tcp/ip, http 等协议时, 我就忍不住问问他们: 你给我说说, 端口是啥吧! 可惜, 很少有人能说得让人满意... 所以这次就来谈谈端口(port), 这个熟悉的陌生人. 在此过程中, 还会谈谈间接层, naming service 等概念, IoC, 依赖倒置等原则以及 TCP 协议…...

3.自定义工程目录配置CMakeLists
问题背景 熟悉stm32keil开发的都知道,我们在编写不同的外设时,通常都会单独编写一个app文件夹或者是user文件夹之类的来存放不同外设功能的源文件和头文件。 在前面一节2.构建第一个工程并烧录到ESP32开发板-CSDN博客中,我们是使用了一个乐鑫…...

Vue3.0里为什么要用 Proxy API 替代 defineProperty API
一、Object.defineProperty 定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象 为什么能实现响应式 通过defineProperty 两个属性,get及set get 属性的 getter 函…...

c++初阶------类和对象(下)
作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 🎂 作者介绍: 🎂🎂 🎂 🎉🎉🎉…...

PMP考试:如何高效学习PMBOK?
PMBOK(项目管理知识体系指南)是PMP考试的核心教材,学习PMBOK对于备考PMP考试至关重要。那么我将分享一些高效学习PMBOK的方法和技巧,帮助同学们更好地掌握项目管理知识。 一、制定学习计划 在学习PMBOK之前,制定一个详…...

个人博客网站前端页面的实现
博客网站前端页面的实现 博客登录页 相关代码 login.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…...
Kotlin Retrofit 网络请求
一、添加依赖: //Retrofit 网络请求implementation("com.squareup.retrofit2:retrofit:2.3.0")implementation("com.squareup.retrofit2:converter-gson:2.3.0")//json转换 二、创建单例类: package com.example.buju.httpimport …...
pyside6 pytq PyDracula QVideoWidget视频只有声音没有画面
解决方案: 先不使用框架,纯pyside6代码,如果添加视频有画面有声音,那可以排除是硬件问题,如果没有画面只有声音,可能是视频解码器无法解码,换个格式的视频文件如果只有使用PyDracula 出问题&am…...
Python爬网页,不确定网页的编码,不需要用第三方库
Python爬网页,不确定网页的编码,不需要用第三方库,自己写个判断,乱拳打死老师傅 detect试了,不好用 apparent_encoding试了,不好用 encoding试了,不好用 headers里get试了,不好用…...

Web测试的基础流程(外加测试过程需要的注意5点)
前言 在Web工程过程中,基于Web系统的测试、确认和验收是一项重要而富有挑战性的工作。基于Web的系统测试与传统的软件测试不同,它不但需要检查和验证是否按照设计的要求运行,而且还要测试系统在不同用户的浏览器端的显示是否合适。 重要的是…...

项目解决方案:视频监控接入和录像系统设计方案(下)
目 录 1.概述 2. 建设目标及需求 2.1建设总目标 2.2 需求描述 2.3 需求分析 3.设计依据与设计原则 3.1设计依据 3.2 设计原则 4.建设方案设计 4.1系统方案设计 4.2组网说明 5.产品介绍 5.1视频监控综合资源管理平台介绍 5.2视频录像服务器和存储 5.2.…...
Python爬虫-使用Prefect框架实现一个可视化爬虫项目
前言 本文是该专栏的第19篇,后面会持续分享python爬虫干货知识,记得关注。 相信有的同学,在处理爬虫项目的时候,有时也会需要你将爬虫项目进行一个可视化展示,方便管理者能及时详细的了解当前爬虫任务的执行进度以及执行情况,甚至需要做一个爬虫监控预警的可视化任务。 …...
[hive面试真题]-基础理论篇
hive的工作流程 hive中分区表,分桶表 工作中hive分区表的应用示例 发现hive分区中的数据不对怎么处理 hive出现code 1 2 3 什么原因 ,怎么处理 工作中hive常见的文件格式 .压缩格式 工作时常用的hive函数 谈谈对窗口函数的理解 hive中如果出现数据倾斜 ,怎么发现 ,怎么…...
【其他】sd卡的照片在相机上能看到在电脑上却看不到
sd卡的照片在相机上能看到在电脑上却看不到 前情提要:太长不看版解决办法:思路:一、首先考虑恢复数据二、 解决文件后缀是exe的问题 前情提要: 在相机里可以看到照片和视频,但是SD卡通过读卡器插入电脑看不到&#x…...

Linux 之六:系统性能监控和挂载
系统性能 Linux系统中,有许多命令用于监测和分析性能指标。以下是一些常用的Linux性能分析命令: top:实时查看并监控CPU、内存以及各个进程的资源占用情况。htop(需要安装):一个增强版的 top 命令&#x…...

【Web】浅聊Java反序列化之C3P0——JNDI注入利用
目录 简介 原理分析 EXP 前文:【Web】浅聊Java反序列化之C3P0——URLClassLoader利用 【Web】浅聊Java反序列化之C3P0——不出网Hex字节码加载利用 简介 出网的情况下,这个C3P0的Gadget可以和fastjson,Snake YAML , JYAML,Yamlbeans , …...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...

Java数组Arrays操作全攻略
Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...