目录

一、为什么要学注解?

二、注解是什么?

三、为什么要使用注解?

四、注解的作用

五、注解的分类

5.1 元注解

@Retention(/ rɪˈtenʃ(ə)n /) ★★★★★

@Target ★★★★★

@Inherited(/ ɪnˈherɪtɪd /) ★★

@Repeatable(/ rɪˈpiːtəbl /) ★★

@Documented(/ˈdɒkjumentɪd /) ★

5.2 标准注解

5.3 自定义注解

六、使用反射操作注解

七、注解的底层实现-动态代理

八、总结:注解工作流程


一、为什么要学注解?

        在日常开发中,基本都是在使用别人定义或是各种框架的注解,比如Spring框架中常用的一些注解:@Controller、@Service、@RequestMapping,以此来实现某些功能,但是却不知道如何实现的,所以如果想学习这些框架的实现原理,那么注解就是我们必知必会的一个点。其次,可以利用注解来自定义一些实现,比如在某个方法上加一个自定义注解,就可以实现方法日志的自动记录打印,这样也可以展现足够的逼格。所以如果你想走上人生巅峰,更好的利用框架,又或者想要高一点的逼格,从团队中突出,那么学习注解都是前提。


二、注解是什么?

        在Java中注解其实就是写在接口、类、属性、方法上的一个标签,或者说是一个特殊形式的注释,注解在代码运行时是可以被反射读取并进行相应的操作,而如果没有使用反射或者其他检查,那么注解是没有任何真实作用的,也不会影响到程序的正常运行结果。

​         举个例子:@Override就是一个注解,它的作用是告诉阅读者(开发人员、编译器)这个方法重写了父类的方法,对于开发人员只是一个标志,而编译器则会多做一些事情,编译器如果发现方法标注了这个注解,就会检查这个方法到底是不是真的覆写了父类的方法,如果没有那就是在欺骗他的感情,甭废话,编译时直接给你报个错,不留情面的那种。而如果不添加@Override注解,程序也是可以正常运行的,不过缺乏了静态的检查,本来是想覆写父类的hello方法的,却写成了he110方法,这就会有些尴尬了。

在spring框架中的注解会影响到程序的运行,是因为spring内部使用反射操作了对应的注解。

         上面的说法是为了方便理解的,那么下面来个稍微正式一点的:注解是提供一种为程序元素设置元数据的方法,理解起来还是一样的,程序元素就是指接口、类、属性、方法,这些都是属于程序的元素,那啥叫元数据呢?

        元数据就是描述数据的数据(data about data),举个简单的例子,系统上有一个sm.png文件,这个文件才是我们真正需要的数据本身,而这个文件的属性则可以称之为sm.png的元数据,是用来描述png文件的创建时间、修改时间、分辨率等信息的,这些信息无论是有还是没有都不影响它作为图片的性质,都可以使用图片软件打开。

  • 元数据是添加到程序元素如方法、字段、类和包上的额外信息,注解就是一种载体形式
  • 注解不能直接干扰程序代码的运行

三、为什么要使用注解?

​         以Spring为例,早期版本的Spring是通过XML文件的形式对整个框架进行配置的,一个缩减版的配置文件如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"><!-- 配置事物管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!-- 配置注解驱动事物管理 --><tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

        在xml文件中可以定义Spring管理的Bean、事物切面等,话说当年非常流行xml配置的。优点呢就是整个项目的配置信息集中在一个文件中,从而方便管理,是集中式的配置。缺点也显而易见,当配置信息非常多的时候,配置文件会变得越来越大不易查看管理,特别是多人协作开发时会导致一定的相互干扰。

​         现在都提倡解耦、轻量化或者说微小化,那么注解就顺应了这一需求,各个包或模块在内部方法或类上使用注解即可实现指定功能,而且使用起来灰常方便,简单易懂。缺点呢就是不方便统一管理,如果需要修改某一类功能,则需要整体搜索逐个修改,是分散式的存在各个角落。

​         这里扩充一下,Spring注解替代了之前Spring xml文件,是不是说Spring的xml也是一种元数据呢?对的,Spring的配置文件xml也是元数据的一种表现形式。不过xml的方式是集中式的元数据,不需要和代码绑定的,而注解是一种分散式的元数据设置方式。


四、注解的作用

        根本来说注解就是一个注释标签。开发者的视角可以解读出这个类/方法/属性的作用以及该怎么使用,而从框架的视角则可以解析注解本身和其属性实现各种功能,编译器的角度则可以进行一些预检查(@Override)和抑制警告(@SuppressWarnings)等。

  • 作为特定标记,用于告诉编译器一些信息
  • 编译时动态处理,如动态生成代码
  • 运行时动态处理,作为额外信息的载体,如获取注解信息

五、注解的分类

​ 通常来说注解分为以下三类

  • 元注解 – Java内置的注解,标明该注解的使用范围、生命周期等。
  • 标准注解 – Java提供的基础注解,标明过期的元素,标明是复写父类方法的方法,标明抑制警告。
  • 自定义注解 – 第三方定义的注解,含义和功能由第三方来定义和实现。

5.1 元注解

        用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。元XX 都代表最基本最原始的东西,因此,元注解就是最基本不可分解的注解,我们不能去改变它只能使用它来定义自定义的注解元注解包含以下五种: @Retention、@Target、@Documented、@Inherited、@Repeatable,其中最常用的是@Retention和@Target下面分别介绍一下这五种元注解。

@Retention(/ rɪˈtenʃ(ə)n /) ★★★★★

        中文翻译为保留的意思,标明自定义注解的生命周期

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {RetentionPolicy value();
}

​         从编写Java代码到运行主要周期为:源文件→ Class文件 → 运行时数据,@Retention则标注了自定义注解的信息要保留到哪个阶段,分别对应的value取值为:SOURCE →CLASS→RUNTIME

  • SOURCE 源代码java文件,生成的class文件中就没有该信息了
  • CLASS class文件中会保留注解,但是jvm加载运行时就没有了
  • RUNTIME 运行时,如果想使用反射获取注解信息,则需要使用RUNTIME,反射是在运行阶段进行反射的
     

value取值为:SOURCE

value取值为:CLASS

各个生命周期的用途:

  • Source:一个最简单的用法,就是自定义一个注解例如@ThreadSafe,用来标识一个类时线程安全的,就和注释的作用一样,不过更引人注目罢了。
  • Class:这个有啥用呢?个人觉得主要是起到标记作用,还没有做实验,例如标记一个@Proxy,JVM加载时就会生成对应的代理类。
  • Runtime:反射实在运行阶段执行的,那么只有Runtime的生命周期才会保留到运行阶段,才能被反射读取,也是我们最常用的。
@Target ★★★★★

        中文翻译为目标,描述自定义注解的使用范围,允许自定义注解标注在哪些Java元素上(类、方法、属性、局部属性、参数…)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {ElementType[] value();
}

        value是一个数组,可以有多个取值,说明同一个注解可以同时用于标注在不同的元素上。value的取值如下

说明
TYPE类、接口、注解、枚举
FIELD属性
MEHOD方法
PARAMETER方法参数
CONSTRUCTOR构造函数
LOCAL_VARIABLE局部变量(如循环变量、catch参数)
ANNOTATION_TYPE注解
PACKAGE
TYPE_PARAMETER泛型参数 jdk1.8
TYPE_USE任何元素 jdk1.8

        示例:自定义一个注解@RetentionTest,使用@Target注解定义该注解只能在类和方法上使用,使用在属性上时会提示错误。

@Inherited(/ ɪnˈherɪtɪd /) ★★

        标志是否可以被标注类的子类继承被@Inherited修饰的注解是具有继承性的在自定义的注解标注到某个类时,该类的子类会继承这个自定义注解

        注意:只有当子类继承父类的时候,注解才会被继承。类实现接口,或者接口继承接口,都是无法获得父接口上的注解声明的。

        正确的示例如下(通过反射获取注解)

@Repeatable(/ rɪˈpiːtəbl /) ★★

        是否可以重复标注。这个注解其实是一个语法糖,jdk1.8之前也是有办法进行重复标注的,就是使用数组属性(自定义注解会讲到)。下面给一个例子,虽然我们标注的是多个@MyAnnotation,其实会给我们返回一个@MyAnnotations,相当于是Java帮我们把重复的注解放入了一个数组属性中,所以只是一个语法糖而已。

@Documented(/ˈdɒkjumentɪd /) ★

        是否在生成的JavaDoc文档中体现,被标注该注解后,生成的javadoc中,会包含该注解,这里就不做演示了。

5.2 标准注解

标准注解有一下三个

  • @Override 标记一个方法是覆写父类方法
  • @Deprecated 标记一个元素为已过期,避免使用
  • ​ 支持的元素类型为:CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE
  • @SuppressWarnings 不输出对应的编译警告
@SuppressWarnings(value = {"unused", "rawtypes"})
public class StandardTest extends Parent {@Overridepublic void hello() {System.out.println("StandardTest hello");}@Deprecatedpublic void walk() {}
}
5.3 自定义注解

        注解定义格式

public @interface 注解名 {修饰符 返回值 属性名() 默认值;修饰符 返回值 属性名() 默认值;
}

​         首先注解的修饰符一般是public的,定义注解一般都是要给三方使用的,不是public的又有什么意义呢?定义的类型使用@interface,可以猜出来和接口是有一些说不清道不明的关系的,其实注解就是一个接口,在程序运行时,JVM会为其生成对应的代理类

​         然后内部的定义,这个有点四不像,说是方法吧它还有一个默认值,说它是属性吧它的后面还加了一个括号,我个人还是喜欢称之为带默认返回值的接口方法,通过后面的学习我们会进一步认识它的真面目。内部的修饰符只能是public的,即使不写也默认是public的,因为它本质上就是一个接口,而接口方法的默认访问权限就是pubilc的

​         注解是不能继承也不能实现其他类或接口的,本身就是一个元数据了,确实没什么必要。

返回值支持的类型如下

  • 基本类型 int float boolean byte double char logn short
  • String
  • Class
  • Enum
  • Annotation
  • 以上所有类型的数组类型

        定义一个简单的接口示例

// 保留至运行时
@Retention(RetentionPolicy.RUNTIME)
// 可以加在方法或者类上
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {public String method() default "GET";public String path();public boolean required();
}

        接下来我们来看下它到底是不是一个接口,首先编译一下该注解javac RequestMapping.java生成对应的RequestMapping.class文件,然后对其进行反编译,输出如下

// ...
①public interface RequestMapping extends java.lang.annotation.Annotation//...②public abstract java.lang.String method();descriptor: ()Ljava/lang/String;flags: ACC_PUBLIC, ACC_ABSTRACTAnnotationDefault:③default_value: s#7public abstract java.lang.String path();descriptor: ()Ljava/lang/String;flags: ACC_PUBLIC, ACC_ABSTRACTpublic abstract boolean required();descriptor: ()Zflags: ACC_PUBLIC, ACC_ABSTRACT
}
//...

① 从这里可以看到,注解的本质就是一个接口,并且继承了java.lang.annotation.Annotation

② ③这里验证了上面所说的,内部的定义其实就是一个带默认值的方法


六、使用反射操作注解

​         反射可以获取到Class对象,进而获取到Constructor、Field、Method等实例,点开源码结构发现Class、Constructor、Field、Method等均实现了AnnotatedElement接口,AnnotatedElement接口的方法如下

// 判断该元素是否包含指定注解,包含则返回true
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 返回该元素上对应的注解,如果没有返回null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
// 返回该元素上的所有注解,如果没有任何注解则返回一个空数组
Annotation[] getAnnotations();
// 返回指定类型的注解,如果没有返回空数组
T[] getAnnotationsByType(Class<T> annotationClass)
// 返回指定类型的注解,如果没有返回空数组,只包含直接标注的注解,不包含inherited的注解
T getDeclaredAnnotation(Class<T> annotationClass)
// 返回指定类型的注解,如果没有返回空数组,只包含直接标注的注解,不包含inherited的注解
T[] getDeclaredAnnotationsByType
// 返回该元素上的所有注解,如果没有任何注解则返回一个空数组,只包含直接标注的注解,不包含inherited的注解
Annotation[] getDeclaredAnnotations();

这就说明以上元素均可以通过反射获取该元素上标注的注解。

个完整的示例

// package-info.java
@AnyAnnotation(order = 0, desc = "包")
package demo.annotation.reflect;// AnyAnnotation.java
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.PACKAGE, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD,ElementType.LOCAL_VARIABLE, ElementType.PARAMETER})
public @interface AnyAnnotation {int order() default 0;String desc() default "";
}// ReflectAnnotationDemo.java
@AnyAnnotation(order = 1, desc = "我是类上的注释")
public class ReflectAnnotationDemo {@AnyAnnotation(order = 2, desc = "我是成员属性")private String name;@AnyAnnotation(order = 3, desc = "我是构造器")public ReflectAnnotationDemo(@AnyAnnotation(order = 4, desc = "我是构造器参数") String name) {this.name = name;}@AnyAnnotation(order = 45, desc = "我是方法")public void method(@AnyAnnotation(order = 6, desc = "我是方法参数") String msg) {@AnyAnnotation(order = 7, desc = "我是方法内部变量") String prefix = "I am ";System.out.println(prefix + msg);}public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {Class<ReflectAnnotationDemo> clazz = ReflectAnnotationDemo.class;// 获取包上的注解,声明在package-info.java文件中Package packagee = Package.getPackage("demo.annotation.reflect");printAnnotation(packagee.getAnnotations());// 获取类上的注解Annotation[] annotations = clazz.getAnnotations();printAnnotation(annotations);// 获取成员属性注解Field name = clazz.getDeclaredField("name");Annotation[] annotations1 = name.getAnnotations();printAnnotation(annotations1);//获取构造器上的注解Constructor<ReflectAnnotationDemo> constructor = clazz.getConstructor(String.class);AnyAnnotation[] annotationsByType = constructor.getAnnotationsByType(AnyAnnotation.class);printAnnotation(annotationsByType);// 获取构造器参数上的注解Parameter[] parameters = constructor.getParameters();for (Parameter parameter : parameters) {Annotation[] annotations2 = parameter.getAnnotations();printAnnotation(annotations2);}// 获取方法上的注解Method method = clazz.getMethod("method", String.class);AnyAnnotation annotation = method.getAnnotation(AnyAnnotation.class);printAnnotation(annotation);// 获取方法参数上的注解Parameter[] parameters1 = method.getParameters();for (Parameter parameter : parameters1) {printAnnotation(parameter.getAnnotations());}// 获取局部变量上的注解/*** 查了一些资料,是无法获取局部变量的注解的,且局部变量的注解仅保留到Class文件中,运行时是没有的。* 这个更多是给字节码工具使用的,例如lombok可以嵌入编译流程,检测到有对应注解转换成相应的代码,* 而反射是无法进行操作的。当然也可以利用asm等工具在编译器完成你要做的事情*/}public static void printAnnotation(Annotation... annotations) {for (Annotation annotation : annotations) {System.out.println(annotation);}}
}

@AnyAnnotation的Retention中的生命周期改为SOURCE/CLASS试试,这时就获取不到任何注解信息了哦


七、注解的底层实现-动态代理

首先准备一下测试代码,如下

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Learn {// 默认值为"default"public String name() default "default";// 必须填写public int age();
}
@Learn(age = 18)
public class LearnAnnotationReflect {// 获取LearnAnnotationReflect类的Class对象public static void main(String[] args) {Class<LearnAnnotationReflect> reflectClass = LearnAnnotationReflect.class;// 判断LearnAnnotationReflect类是否有Learn注解if (!reflectClass.isAnnotationPresent(Learn.class)) {return;}// 获取LearnAnnotationReflect类的Learn注解Learn learn = reflectClass.getAnnotation(Learn.class);// 输出Learn注解的name属性System.out.println(learn.name());// 输出Learn注解的age属性System.out.println(learn.age());}
}

        在System.out.println(learn.name());打一个断点,以Debug模式运行,查看learn这个对象到底是什么

​         从上面的截图可以看出,jdk为Learn生成了一个叫$Proxy1的代理对象,并且包含了一个内部成员AnnotationIvocationHandler,接下来就是调用$Proxy1.name()进行获取name的值,那么我们来看下$Proxy1到底是一个什么样的对象,在jdk8中可以添加JVM参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles来保存代理类,更高版本可以使用-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true来保存代理类。在Idea中的设置方法如下

       重新运行程序,就会发现在项目根目录多了如下类,其中$Proxy1就是Learn注解对应的代理类

当我们调用Learn.name()时,其实就是调用这个代理类的name方法,如下

    public final String name() throws  {try {return (String)super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}

代理类的name方法中主要是调用h的invoke方法传入当前对象,以及m3这个方法元素,m3如下

m3 = Class.forName("demo.annotation.runtime.Learn").getMethod("name");

​         在5.3讲解的内容时,我们反编译了注解的class文件,知道在编译注解时,实际上编译为了一个接口,接口中定义了想干的属性的方法。

​ 那么基本的流程我们就可以梳理出来了:

  1. 通过反射我们可以获取对应元素上的注解@Learn,前面说过注解本质是一个接口,也就是获取到了Learn接口的代理对象。
  2. Learn代理对象提供了相应的同名方法,内部声明了原注解的相应方法Method,如method3
  3. 之后通过代理对象父类的h成员属性,也就是AnnotationInvocationHandler去执行invoke方法
  4. AnnotationInvocationHandler在初始化时,会包含一个memberValues的map,key就是方法名,value就是对应的属性值,在invoka内部通过Method的name从memberValues中获取到对应的值并返回

​​ 接下来我们来看下AnnotationInvocationHandler中的invoke方法相关信息,如下

// 当前注解类型
private final Class<? extends Annotation> type;
// 当前注解的相关属性集合,key是方法名,value是对应的值
private final Map<String, Object> memberValues;// jdk会将对应的属性信息传过来
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {this.type = var1;this.memberValues = var2;}public Object invoke(Object var1, Method var2, Object[] var3) {// 方法名String var4 = var2.getName();// 参数类型Class[] var5 = var2.getParameterTypes();// 如果是equals方法,则调用对应方法if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {return this.equalsImpl(var3[0]);// 不是equals方法,却有参数,说明是错误的,注解的方法是不允许有参数的} else if (var5.length != 0) {throw new AssertionError("Too many parameters for an annotation method");} else {// 定义一个变量var7 默认值-1byte var7 = -1;// 不同的方法赋予var7不同的值switch(var4.hashCode()) {case -1776922004:if (var4.equals("toString")) {var7 = 0;}break;case 147696667:if (var4.equals("hashCode")) {var7 = 1;}break;case 1444986633:if (var4.equals("annotationType")) {var7 = 2;}}// 返回对应方法的处理switch(var7) {case 0:return this.toStringImpl();case 1:return this.hashCodeImpl();case 2:return this.type;// 默认方法, 也就是我们自定义的属性方法default:// 从map集合中获取对应的值Object var6 = this.memberValues.get(var4);if (var6 == null) {throw new IncompleteAnnotationException(this.type, var4);} else if (var6 instanceof ExceptionProxy) {throw ((ExceptionProxy)var6).generateException();} else {if (var6.getClass().isArray() && Array.getLength(var6) != 0) {var6 = this.cloneArray(var6);}return var6;}}}}

八、总结:注解工作流程

  • 通过键值对的形式为注解属性赋值
  • 编译器检查注解的使用范围 (将注解信息写入元素属性表 attribute)
  • 运行时JVM将单个Class的runtime的所有注解属性取出并最终存入map里
  • 创建AnnotationInvocationHandler实例并传入前面的map
  • JVM使用JDK动态代理为注解生成代理类,并初始化处理器
  • 调用invoke方法,通过传入方法名返回注解对应的属性值

面向大佬编程:Java注解入门到精通,这一篇就够了_java 注解-CSDN博客

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

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

相关文章

43.安卓逆向2-补环境-使用unidbg(使用Smali语法调用方法和使用方法地址调用方法)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwdzy89 提取码&#xff1…

【Kubernetes知识点问答题】Pod 调度

1. 如何将特定 Pod 调度到指定的节点&#xff1f;可以使用下列方法中的任何一种来选择 K8s 对特定 Pod 的调度&#xff1a;① 与节点标签匹配的 nodeSelector&#xff1a;在 Pod 的规范中使用 nodeSelector 字段来指定节点标签&#xff0c;以便将 Pod 调度到具有特定标签的节点…

wordpress显示时间日期的几种常见的方式

在WordPress中&#xff0c;显示时间日期有多种常见方式&#xff0c;包括使用默认设置、模板标签、插件等&#xff0c;以下是详细介绍&#xff1a; 使用默认设置 WordPress的默认设置允许你在文章列表中显示文章的发布时间。登录到WordPress后台&#xff0c;在“设置”中找到“…

基于飞算JavaAI实现布隆过滤器防止缓存穿透:原理、实践与全流程解析

引言&#xff1a;当缓存失效时&#xff0c;系统如何避免“雪崩式崩溃”&#xff1f; 在互联网高并发场景中&#xff08;如电商秒杀、社交平台热点新闻&#xff09;&#xff0c;缓存是提升系统性能的核心手段——将频繁访问的数据&#xff08;如商品详情、用户信息&#xff09;存…

DeepResearch开源与闭源方案对比

在这个AI不再只是聊天工具的时代&#xff0c;"深度研究"已经成为大语言模型&#xff08;LLM&#xff09;的一项新能力。先进的LLM不再只是给出快速的一次性回答&#xff0c;而是可以像研究助手一样工作——搜索网上信息&#xff0c;调用各种工具&#xff08;比如搜索…

UniApp 页面传参方式详解

在 UniApp 开发中&#xff0c;页面间参数传递是核心功能之一。以下是 8 种常用的传参方式&#xff0c;每种方式都有其适用场景和特点&#xff1a;一、URL 拼接传参&#xff08;最常用&#xff09; 适用场景&#xff1a;简单数据传递&#xff0c;如 ID、状态值等基础类型数据 实…

音频分类标注工具

pyqt 分类标注工具&#xff1a;import glob import sys import json import os from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableWidget, QTableWidgetItem,QSplitter, QVBoxLayout, QWidget, QPushButton, QRadioButton,QButtonGroup, QLabel, QHBoxLayout, Q…

云计算-Kubernetes+Istio 实现金丝雀发布:流量管理、熔断、流量镜像、ingreess、污点及pv案例实战

介绍 在微服务架构中,如何安全、高效地实现服务发布与流量管理是保障业务稳定性的核心挑战。金丝雀发布(Canary Release)、灰度发布等策略通过精细化的流量控制,可有效降低新版本上线风险, Istio 作为主流的服务网格(Service Mesh)工具。 此次Istio 在 Kubernetes 集群…

12.web api 3

定时器-间歇函数

ComfyUI进阶:EchoMimic插件全解析,让静态肖像实现音频驱动的精准口型动画

在数字内容创作中&#xff0c;让静态肖像“开口说话”并做出自然表情&#xff0c;是提升交互感与沉浸感的关键。传统动画制作需专业人员逐帧调整口型与表情&#xff0c;成本高且效率低。ComfyUI的EchoMimic插件通过音频驱动技术&#xff0c;实现了“输入音频→自动生成匹配口型…

链式前向星、vector存图

场景设定 想象你是一个社交达人&#xff0c;要记录你和所有朋友的关系&#xff08;这就是“图”&#xff09;。每个朋友是一个节点&#xff0c;关系是一条边。你需要快速回答&#xff1a;“我有哪些朋友&#xff1f;”&#xff08;遍历邻居&#xff09;。方式1&#xff1a;链式…

YAML 中定义 List 的几种方式

在 YAML 配置文件中定义 List 并在 Spring 应用中注入是非常常见的操作&#xff0c;下面详细介绍具体写法和注入方式。一、YAML 中定义 List 的几种方式1. 缩进式写法&#xff08;推荐&#xff09;最常用的方式&#xff0c;通过短横线 - 加空格表示列表项&#xff1a;yaml# app…

C# 反射和特性(自定义特性)

自定义特性 你或许已经注意到了&#xff0c;应用特性的语法和之前见过的其他语法有很大不同。你可能会觉得特 性是一种完全不同的结构类型&#xff0c;其实不是&#xff0c;特性只是一种特殊的类。 有关特性类的一些要点如下。 用户自定义的特性类叫作自定义特性。所有特性类都…

科目二的四个电路

一.K21电动机单连续运转接线(带点动控制)1.电路图2.主线路这可很明了,是一条直线,从上接到下就OK了,然后从热继电器出来,接到SB3按钮的常闭触点上接着往下走一端接到SB2的常闭触点上,接着往下走&#xff0c;走到接触器的线圈上,从L2借一条火线出来,从熔断器的上端接入,另一端接…

【位运算】查询子数组最大异或值|2693

本文涉及知识点 位运算、状态压缩、枚举子集汇总 3277. 查询子数组最大异或值 给你一个由 n 个整数组成的数组 nums&#xff0c;以及一个大小为 q 的二维整数数组 queries&#xff0c;其中 queries[i] [li, ri]。 对于每一个查询&#xff0c;你需要找出 nums[li…ri] 中任…

HTML DOM 方法

HTML DOM 方法 引言 HTML DOM&#xff08;文档对象模型&#xff09;是HTML文档的编程接口&#xff0c;它允许开发者通过JavaScript来操作HTML文档中的元素。DOM 方法是DOM编程的核心&#xff0c;它提供了丰富的操作手段来改变网页的结构、样式和行为。本文将详细介绍HTML DOM中…

w嵌入式分享合集68

自己的原文哦~ https://blog.51cto.com/whaosoft/14133002 一、一键开关机电路的设计方案 方案一&#xff1a;电路图 一键开关机电路分析如下&#xff1a; 电路工作流程如下&#xff1a; Key按下瞬间&#xff0c;Q2、Q1导通&#xff0c;7805输入电压在8.9V左右&…

FFmpeg QoS 处理

FFmpeg 中的 QoS (服务质量) 处理主要关注于实时流媒体传输中的时序控制、丢帧策略和网络适应等方面。以下是 FFmpeg 中 QoS 相关的关键机制和配置方法。1. 基本 QoS 机制丢帧策略 (Frame Dropping)cAVDictionary *options NULL; av_dict_set(&options, "framedrop&q…

TexStudio中的Latex,PDFLatex,XeLatex和LuaLatex的区别

多种LaTeX编译器一、多种LaTeX编译器 1.1 PDFLaTeX&#xff08;1994年&#xff09; 默认、最常用的引擎。 输入文件通常是 ASCII 或 UTF-8 编码&#xff08;但中文需要 CJK 宏包或 ctex 宏包支持&#xff09;。 字体选择受限&#xff1a;只能使用 TeX 自带的字体或者 Type 1…

容器化部署:用Docker封装机器翻译模型与服务详解

文章目录一、机器翻译容器化的技术栈选型1.1 为什么需要容器化MT模型&#xff1f;1.2 基础镜像选择对比1.3 典型依赖分层方案1.4 性能对比&#xff08;容器化 vs 原生部署&#xff09;二、关键部署模式2.1 轻量级API服务封装2.2 模型热更新策略三、Docker镜像构建3.1 编写Docke…