如何获取注解中的值_如何在运行时利用注解信息

注解( annontation )是 Java 1.5 之后引入的一个为程序添加元数据的功能。注解本身并不是魔法,只是在代码里添加了描述代码自身的信息,至于如何理解和使用这些信息,则需要专门的解析代码来负责。

本文首先介绍注解的基本知识,包括注解的分类和运用时的领域知识。随后,给出一个通过的在运行时解析注解的框架代码,介绍处理注解的一般思路。最后,通过现实世界里使用注解的例子,来加深对注解的实用性方面的认识。

注解的基本知识

注解作为程序中的元数据,其本身的性质也被其上的注解所描述。

刚刚我们提到,理解和使用注解信息,需要专门的解析代码。其中,Java 的编译器和虚拟机也包含解析注解信息的逻辑,而它们判断一个注解的性质,就是依赖注解之上的元注解。

能够注解一个注解的注解就是元注解,Java 本身能够识别的元注解有以下几个。

@Retention

Retention 注解的相关定义如下

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

首先我们看到它自己也被几个元注解包括自身所注解,因此在注解的源头有一个类似于自举的概念,最终触发自举的是编译器和源代码中的先验知识。

再看到 Retention 注解的值,是一个注解保留性质的枚举,包括三种情况。

  1. SOURCE 表示注解信息仅在编译时保留,在编译之后就被丢弃,这样的注解为代码的编译提供原信息。例如常用的 @Override 注解就提示 Java 编译器进行重写方法的检查。
  2. CLASS 表示注解信息保留在字节码中,但在运行时不可见。这是注解的默认行为,如果定义注解时没有使用 Retention 注解显式表明保留性质,默认的保留性质就是这个。
  3. RUNTIME 表示注解信息在运行时可见,当然,也就必须保留在字节码中。

SOURCE 标注的注解通常称为编译期注解,Lombok 项目提供大量的编译期注解,以帮助开发者简写自己的代码。例如 @Setter 注解注解在类上时,在编译期由 Lombok 的注解处理器处理,为被注解的类的每一个字段生成 Setter 方法。

编译期的注解需要专门的注解处理器来处理,并且在编译时指定处理器的名字提示编译期使用该处理器进行处理。技术上说,编译期处理注解和运行时处理注解完全是两个概念的事情。本文主要介绍运行时处理注解的技术,关于编译期处理注解的资料,可以参考这篇 ANNOTATION PROCESSING 101 的文章以及 Lombok 的源码。

CLASS 性质虽然是默认的保留性质,但实际使用中几乎没有采用这一保留性质的。准确需要这一性质的情形应该是某些专门的字节码处理框架,大多数时候使用这一性质的注解仅仅是在编译期使用,使用 SOURCE 足以,且使用 SOURCE 还可以减少字节码文件的大小。

本文介绍运行时处理注解的技术,所有在运行时可见的注解都需要显式地标注 @Retention(RetentionPolicy.RUNTIME) 注解。CLASS 和 RUNTIME 性质的注解都会出现在字节码中。编译器将注解信息写成字节码时,通过为 CLASS 性质的注解赋予 RuntimeInvisibleAnnotations 属性,为 RUNTIME 性质的注解赋予 RuntimeVisibleParameterAnnotations 来提示虚拟机在运行时加载的时候区别对待。

运行时,我们可以调用被注解对象的相应方法取得其上的注解,具体手段在【注解解析的框架代码】一节中介绍。

@Target

上一节最后我们提到,注解有不同的注解对象,这正是 Target 注解加入的元数据,其定义如下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {ElementType[] value();
}public enum ElementType {TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USE,MODULE
}

Target 元注解的信息解释了一个注解能够被注解在什么位置上,或者说能够接受该注解的对象集合。一个注解可以有多种类型的注解对象,所有这些对象类型存在 ElementType 枚举中。

大多数枚举值的含义就是字面含义,值得一提的取值包括

  • TYPE 在 Java 中指类、接口、注解或者枚举类
  • TYPE_PARAMETER 在 Java 1.8 中被引入,指的是泛型中的类型参数
  • TYPE_USE 在 Java 1.8 中被引入,指的是所有可以出现类型的位置,具体参考 Java 语言标准的对应章节

常见的 Override 注解只能注解在方法上,Spring 框架中的 Component 注解只能注解在类型上。SuppressWarnings 注解能注解在除了本地变量和类型参数以外的几乎所有地方,Spring 框架中的 Autowired 注解也能注解在字段、构造器、方法参数和注解等多种位置上。

@Inherited

Inherited 主要用来标注注解在类继承关系之间的传递关系。它本身不携带自定义信息,仅作为一个布尔信息存在,即是或者不是 Inherited 的注解。

标注 Inherited 元注解的注解,标注在某个类型上时,其子类也默认视为标注此注解。或者换个方向说,获取某个类的注解时,会递归的搜索其父类的注解,并获取其中标注 Inherited 元注解的注解。注意,标注 Inherited 元注解的注解在子类上也标注时,子类上的注解优先级最高。

技术上说,可以通过 getAnnotationsgetDeclaredAnnotations 的区别来获取确切标注在当前类型上的注解和按照上面描述的方法查找的注解。另一个值得强调的是这种继承仅发生在类的继承上,实现接口并不会导致标注 Inherited 元注解的注解的传递。

值得注意的是,注解本身是不能继承的。为了实现类似继承的效果,开发者们从基于原型的继承找到灵感,采用本节后续将讲到的组合注解技术来达到注解继承的目的。

@Repeatable

Repeatable 注解在 Java 1.8 中被引入,主要是为了解决相同的注解只能出现一次的情况下,为了表达实际中需要的相同注解被标注多次的逻辑,开发者不得不首先创建出一个容器注解,然后使用者在单个和多个注解的情况下分别使用基础注解和容器注解的繁琐逻辑。具体例子如下

@ComponentScan(basePackages = "my.package")
class MySimpleConfig { }@ComponentScans({ @ComponentScan(basePackages = "my.package") @ComponentScan(basePackages = "my.another.package")
})
class MyCompositeConfig { }

有了 Repeatable 注解,从注解处理方,代码不会精简,仍然需要分开处理两种注解类型,但是使用方就可以精简代码。例如上面 MyCompositeConfig 的标注可以变为

@ComponentScan(basePackages = "my.package") 
@ComponentScan(basePackages = "my.another.package")
class MyCompositeConfig { }

对应的注解定义为

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {// ...
}@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {ComponentScan[] value();
}

对于注解的处理方,重复注解会在背后由 Java 编译器转化为容器注解的形式传递。就上面的例子而言,无论有没有 Repeatable 注解,MyCompositeConfig 在获取注解时,都会获取到 ComponentScans 注解及其 ComponentScan[] 形式的元数据信息。

值得注意的是,重复注解和容器注解不能同时存在,即在标记了 @Repeatable(ComponentScans.class) 之后,ComponentScansComponentScan 不能同时标注同一个对象。

@Documented

这个注解没有太多好说的,注解信息在生成文档时默认是不会留存的。如果使用此注解标注某个注解,那么被标注的注解注解的对象的文档会显示它被对应的注解所标注。

组合注解

严格来说,组合注解是一种设计模式而不是语言特性。

由于注解无法继承,例如 Spring 框架中具有 "is-a" 关系的 Service 注解和 Component 注解,无法通过继承将 Service 定义为 Component 的特例。但是在实际使用的时候,又确实有表达这样 "is-a" 关系的需求。

在框架代码中,无法穷尽对下游项目扩展注解实质上的继承关系的情况,但是又需要支持下游项目自定义框架注解的扩展。如何将下游项目自定义的注解和框架注解之间的继承关系表达出来,就是一个技术上实际的需求。

为了解决这个问题,开发者们注意到在注解设计之初,留下了注解能够标注注解的路径。这一路径使得我们可以采用一种类似基于原型的继承的方式,通过递归获取注解上的注解来追溯注解的链条,从而类似原型链上找父类的方式找到当前注解逻辑上继承的注解。

这一技术在 Spring 框架中被广泛使用,例如 Service/Repository/Controller 等注解组合了 Component 注解,从而在下一节的注解解析的框架代码中能够作为 Component 的某种意义上的子注解被识别,同时在需要时取出继承的注解的元数据信息。

注解解析的框架代码

Java 语言提供的方法

注解解析最基础的手段是通过 Java 语言本身提供的方法。哪怕是其他框架增强注解解析的功能,最终也需要依赖基本方法的支持。

运行时获取注解信息,可想而知是通过反射的手段来获取的。Java 为被注解的元素定义了一个 AnnotatedElement 的接口,通过这一接口的方法可以在运行时取得被注解元素之上的注解。该接口的实现类是运行时通过反射拿到的元素里面能够被注解的类。

我们先看到这一接口提供的方法。

public interface AnnotatedElement {<T extends Annotation> T getAnnotation(Class<T> annotationClass);Annotation[] getAnnotations();<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass);Annotation[] getDeclaredAnnotations();<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass);<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass);boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
}

这些方法没必要一个一个讲,其实可以简单地分成两类

  • 获取被注解对象上声明的注解,即 getDeclaredAnnotations 系列的方法
  • 获取被注解对象所拥有的注解,即 getAnnotations 系列的方法,比起上一类,额外包括 @Inherited 的注解

最后 isAnnotationPresent 方法仅仅是一个判断标签式注解的简易方法,内容只有一行。

default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {return getAnnotation(annotationClass) != null;
}

我们可以通过 Java 语言自身的 AnnotationSupport#getIndirectlyPresent 方法来看看怎么用这套基础支持解析注解。

private static <A extends Annotation> A[] getIndirectlyPresent(Map<Class<? extends Annotation>, Annotation> annotations,Class<A> annoClass
) {Repeatable repeatable = annoClass.getDeclaredAnnotation(Repeatable.class);if (repeatable == null)return null;  // Not repeatable -> no indirectly present annotationsClass<? extends Annotation> containerClass = repeatable.value();Annotation container = annotations.get(containerClass);if (container == null)return null;// Unpack containerA[] valueArray = getValueArray(container);checkTypes(valueArray, container, annoClass);return valueArray;
}

以上这段代码是在 Java 1.8 引入 Repeatable 注解后,由于默认的会将重复的 Repeatable 的注解在获取时直接合并成容器注解,为了提供一个方便的按照基础注解来获取注解信息的手段提供的方法。

我们看到,传入的内容包括一个根据 Class 对象查找实现类对象的映射,这个是被注解类所取得的所拥有的注解的类到实例的字典,不用过多关注。另一方面 annoClass 则是我们想要获取的基础注解的类。

例如,annoClass 为上面提过的 Spring 的 ComponentScan 类,对于仅注解了 ComponentScans 的类来说,以 ComponentScan.class 作为参数调用 getDeclaredAnnotationsByType 方法一路走到上面这个方法里,代码逻辑将会看到 ComponentScan 标注了 @Repeatable(ComponentScans.class) 注解,从而在 annotations 映射里查找 ComponentScans 注解的信息,并将它转换为 ComponentScan 的数组返回。

Spring 解析注解的方案

Spring 解析注解的核心是 MergedAnnotation 接口及相关的工具类。

Spring 框架重度使用了注解来简化开发的复杂度。对于具体的某一个或某几个注解,围绕它展开的代码散布在其逻辑链条的各处。但是,Spring 的注解处理的特别之处就在于它定义了 MergedAnnotation 接口,并支持了基于组合注解和 AliasFor 的注解增强机制。

AliasFor 注解的解析非常简单,就是查看当前注解或者 targetAnnotation 注解里面相应名称的注解。在 5.2.7.RELEASE 版本中,其解析逻辑基本在 AnnotationTypeMapping#resolveAliasTarget 方法里,最终组装出来的 AnnotationTypeMapping 对象能够在获取属性值的时候显示处理了 AliasFor 之后的属性值。

下面我们展开说一下如何递归解析组合注解。

为了支持前面提到的组合注解,即注解上的注解的递归查找,Spring 中提供了 AnnotationUtils#findAnnotation 系列方法来做查询,区别于 AnnotationUtils#getAnnotation 的单层查找。

Spring 对这个查找逻辑的演化花了很多心思。

在最新的 Spring 5.2.7.RELEASE 版本中,这两个方法都对 AnnotatedElement 构造了 MergedAnnotation 实例,在最终查找的时候通过不同的谓词策略来做筛选。构造 MergedAnnotation 实例的过程经由几个工厂函数之后构造出一个 TypeMappedAnnotations 的实例,调用其上的 get 方法构造出实际的 MergedAnnotation 对象,这个对象就是对要查找的注解递归查找的结果。

相关逻辑为了定制各种策略变得非常复杂,我们从 4.3.8.RELEASE 版本入手,查看在复杂的定制引入之前,这一查找过程核心逻辑的实现框架。

Annotation[] anns = clazz.getDeclaredAnnotations();
for (Annotation ann : anns) {if (ann.annotationType() == annotationType) {return (A) ann;}
}
for (Annotation ann : anns) {if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {A annotation = findAnnotation(ann.annotationType(), annotationType, visited);if (annotation != null) {return annotation;}}
}

无论后期代码演化得再复杂,其核心还是一个递归查找的过程,也就是以上的代码。

  1. 首先,获取当前的类上的注解,注意这里的类可以是一个注解类,如果此次获取的注解就包含了我们要查找的注解,那么直接返回。
  2. 如果没有包含,对刚才取得的注解递归的查找。注意这里有一个类似于深度优先搜索的 visited 集合。这是因为有些注解可以以自己为目标,导致出现递归查找的自环。典型的例如 Java 自带的元注解 Retention 也被自己所注解。
  3. 如果深度优先搜索穷尽之后没有得到结果,则返回空。

可以看到,上面的逻辑中对 Repeatable 和 Inherited 等元注解的复杂组合情况没有定制的逻辑,而是采用了一些默认的硬编码策略。

最新版本的 Spring 之所以变得相当复杂,有一部分代码量是为了解决搜索的不同策略以及跟进新版 Java 的注解特性。另一部分,注意到上述逻辑在获取注解时没有关心 AliasFor 注解的逻辑,在早期版本中这是由 AnnotationUtils 中的一个全局静态映射来管理的。在最新版本中,产生 MergedAnnotation 时将构造并维护一个本地的 alias 映射。

现实世界的注解解析

上一节介绍了处理注解的两个通用套路,背后的思想是基础的注解信息获取和递归的注解信息获取。本节我们将从现实世界的注解解析入手,介绍实际项目里面特定的注解是如何被解析的。

Flink

@RpcTimeout

Flink 采用类似 RMI 的方式来进行远程调用,为了避免无限阻塞,方法调用时可以传递一个超时参数。本地拦截远端调用的动作时,从方法的签名中反射取得标注 RpcTimeout 的参数,将它作为超时参数传递到实际的方法调用过程中,以在超过限定时间时返回超时异常而非阻塞等待远端调用的返回。

取得标注 RpcTimeout 的参数的逻辑代码展开如下

final Annotation[][] parameterAnnotations = method.getParameterAnnotations();for (int i = 0; i < parameterAnnotations.length; i++) {for (Annotation annotation : parameterAnnotations[i]) {if (annotation.annotationType().equals(RpcTimeout.class)) {if (args[i] instanceof Time) {return (Time) args[i];} else {throw new RuntimeException(/* ... */)}}}
}return defaultTimeout;

可以看到,是针对先验知识能得知的可能出现该注解的位置进行遍历获取。其实,所有的注解解析代码都遵循这样的模式,这也是最基础的模式。

JUnit 4

@Test

JUnit 4 测试框架的用户最熟悉的就是 Test 注解了。不同于上一节提到的基础解析和递归解析,JUnit 4 的 Test 注解有一个特殊的场景需要支持,即在获取当前类的所有待测试方法时,获取到父类中的 Test 标注的方法。

这是因为我们常常把相似的测试的配置和基础测试方法抽成抽象基类,在根据不同的实现场景实现不同的测试子类。虽然类似的功能可以用 Parameterized Runner 和 Parameter 注解来实现,但是 Parameter 的方案只能支持参数化字段,如果测试方法是有和没有的区别而不是参数的不同,子类是比使用 Parameter 向量并加入 Enable 开关更好的解决方案。

总之,JUnit 4 支持查找父类中标注 Test 的其他方法,此逻辑实现如下。

// TestClass#scanAnnotatedMembers
for (Class<?> eachClass : getSuperClasses(clazz)) {for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) {addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations);}// ensuring fields are sorted to make sure that entries are inserted// and read from fieldForAnnotations in a deterministic orderfor (Field eachField : getSortedDeclaredFields(eachClass)) {addToAnnotationLists(new FrameworkField(eachField), fieldsForAnnotations);}
}// TestClass#addToAnnotationLists
for (Annotation each : member.getAnnotations()) {Class<? extends Annotation> type = each.annotationType();List<T> members = getAnnotatedMembers(map, type, true);T memberToAdd = member.handlePossibleBridgeMethod(members);if (memberToAdd == null) {return;}if (runsTopToBottom(type)) {members.add(0, memberToAdd);} else {members.add(memberToAdd);}
}

其实也很简单,在初始化 TestClass 时遍历测试的候选类及其父类的所有方法和字段,将它们的注解信息存在一个注解类型到被注解对象的列表的映射中。后续需要查找的时候从该映射查找,即可查找到标注对应注解的所有方法或字段。

@RunWith

另一个常见的注解是 RunWith 注解,用于标注运行测试时采用自定义的 Runner 实现。其代码如下

for (Class<?> currentTestClass = testClass; currentTestClass != null;currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {RunWith annotation = currentTestClass.getAnnotation(RunWith.class);if (annotation != null) {return buildRunner(annotation.value(), testClass);}
}

可以看到,是从内到外层层查找的形式。注意这里没有去查找父类的 RunWith 注解,这是由于 RunWith 注解本身被 @Inherited 所标注,调用 Java 提供的基础方法获取类的注解时已经做了相应的处理。

Spring

@SpringBootApplication

SpringBootApplication 可以说是最好的解释 Spring 中重度使用组合注解的例子了。对于这一注解的解析,我们甚至不需要或者说不能列举出任何解析代码,因为 SpringBootApplication 从来没有作为它自己被解析。该注解的定义如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}

这里有两件事情值得关注,分别对应介绍 Spring 的注解解析框架的时候指出的 Spring 的两个关键的增强

  1. 实际使用 SpringBootApplication 时,Spring 框架的解析代码是通过 findAnnotation 查找其组合的注解来实现具体功能的。
  2. SpringBootApplication 通过 AliasFor 支持用户在使用该注解时覆盖其所组合的注解的属性。

从这里我们也看出,组合注解仅仅是一种形式上相关联的组合,与任一形式的继承不同,不会以某种形式继承属性。

@Autowired

Autowired 可以说是 Spring 框架中使用最为广泛的注解之一了,它和 Value 注解以及 JSR-330 的 Inject 注解一起组成了注入 Bean 的核心手段。

Autowired 的处理逻辑在 AutowiredAnnotationBeanPostProcessor 中,即 Bean 被创造和加载之后的一个后处理逻辑或者成为装饰逻辑。其中涉及到 Autowired 等注解的地方主要是筛选出需要为目标注入 Bean 的候选。

首先,在初始化的时候,会将对应的 Autowired 系列注解保存到 autowiredAnnotationTypes 集合字段中。

随后,当 Bean 处理框架调用后处理逻辑时,调用后处理器的 findAutowiringMetadata 方法,通过标记型注解找到需要 Autowired 的候选。整个过程通过反射将被 Autowired 注解的对象及 Autowired 注解中持有的是否必须( required )的信息保存到 InjectElement 中。

再之后,对获取到的所有 InjectElement 调用 inject 方法进行注入。根据不同的被注入对象,注入的逻辑有所不同。例如,对于字段的注入,由 AutowiredFieldElement 对象处理,从 BeanFactory 中根据依赖关系初始化 Bean 并将 Bean 赋值给字段。

这一套逻辑支持了 Bean 注入最常用的字段注入的功能,以及运行配置方法的功能。

Autowired 注解还能被用在参数和构造函数上,其中参数上的标注目前仅用于在 JUnit Jupiter 框架测试时使用,而构造函数的标注广泛替代了直接标注字段的用法,其代码路径存在于 AbstractAutowireCapableBeanFactory 创建 Bean 实例的时候。从结果来说,标注在构造函数的 Autowired 能够将参数对应类型的 Bean 作为构造函数的实参,调用构造函数以构造出对象。

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

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

相关文章

mysql5.7运行按钮_MySQL 5.7.* 启动问题

mysql-5.7.10-winx64启动 mysqld.exe&#xff0c;报错&#xff1a;mysqld: Could not create or access the registry key needed for the MySQL applicationto log to the Windows EventLog. Run the application with sufficientprivileges once to create the key, add the …

控制元素的div属性

1、需求分析 改变元素的宽、高、颜色、显示、重置等属性。 2、技术分析 基础的css、html、js 3、详细分析 如图&#xff0c;单击按钮&#xff0c;改变元素属性: 3.1 HTML部分 根据视图不难发现&#xff0c;内容分两大不分:按钮栏和效果图&#xff0c;所以设置两个div。 <…

使用JMeter和Yourkit进行REST / HTTP服务的性能分析

我的上一篇文章描述了如何使用JMeter完成异步REST / HTTP服务的压力测试或负载测试。 但是&#xff0c;运行这样的测试通常表明被测系统不能很好地应对不断增加的负载。 现在的问题是如何找到瓶颈&#xff1f; 深入研究代码以检测可疑部分可能是另一种选择。 但是考虑到潜在的…

EasyUI 加载时需要显示和隐藏 panel(面板)内容破版问题

1.当页面加载的完成时,如果面板中的内容是未加载的如果把他的状态从隐藏变为显示会导致破版,页面信息显示不全 2.这时需要刷新面板$(#id).panel(open).panel(refresh); 3.它就会重新加载转载于:https://www.cnblogs.com/eagle-xie/p/6892706.html

java中间件_90%的Java程序员,都扛不住这波消息中间件的面试四连炮!

概述大家平时也有用到一些消息中间件(MQ)&#xff0c;但是对其理解可能仅停留在会使用API能实现生产消息、消费消息就完事了。对MQ更加深入的问题&#xff0c;可能很多人没怎么思考过。比如&#xff0c;你跳槽面试时&#xff0c;如果面试官看到你简历上写了&#xff0c;熟练掌握…

python 取array并集_Python内置数据结构原理与性能简易分析

ins ngladc文末左下方阅读原文指向了本人博客链接&#xff0c;不含广告。参考资料中的相关链接&#xff0c;可以在博客文章的最下方获取。推荐苹果手机用户使用浅色模式观看。前言 对于一些算法题&#xff0c;可以使用Python自带的内置函数解决。但很多时候用就用了&#xff0c…

ae合成复制脚本_稀缺资源—这几个AE脚本使用频率很高,赶紧收藏吧!

「第442期」毫无疑问&#xff0c;AE已经成为目前制作短视频比较主流的软件&#xff0c;效果的多样化深受很多创作者的喜爱。随着对软件的熟悉&#xff0c;越发觉得AE主要是基于多图层控制的软件。如果制作一些简单的效果&#xff0c;几个图层几个滤镜就可以搞定&#xff0c;但如…

android activity and fragment活动周期

1.状态 /* 每个活动一共有四种状态 *&#xff1a;1。运行状态&#xff0c;就是栈顶的那个 * 2。暂停状态&#xff1a;就是不处于栈顶&#xff0c;但是依然可见&#xff0c;比如对话框下面的界面 * 3。停止状态&#xff1a;不处于栈顶&#xff0c;并且不可见 * 4。销毁状态 * */…

html css基础知识

1 这是自己学习html时候做的一些记录&#xff0c;供大家参考 <!--2 块和内联3 块元素:独占一行的元素4 div p h ul5 div没有任何语义&#xff0c;就是一个纯粹的快元素6 就是为了方便布局7 …

番石榴的ListenableFuture

Guava中的ListenableFuture试图为Future对象定义一致的API&#xff0c;以注册完成回调。 通过在Future完成时添加回调的功能&#xff0c;我们可以异步有效地响应传入的事件。 如果您的应用程序与许多将来的对象高度并发&#xff0c;我强烈建议您尽可能使用ListenableFuture 。 …

程序员的幸福感和颈椎病

脖子一直疼&#xff01; 去医院检查&#xff0c;拍片子的医生在造影室里冲我喊&#xff1a; “小伙子&#xff0c;你多大年纪啦&#xff1f;” 我说&#xff1a;“我三十来岁&#xff0c;咋啦” 医生说&#xff1a;“怎么这么年轻就得这种病啊&#xff01;” 我当时腿就有点软&…

python实现词语相似度计算分析_相似度计算的方法及Python实现

现实生活中&#xff0c;我们经常提到距离这个词&#xff0c;本文谈的相似度就是基于距离定义的&#xff0c;当两个向量之间的距离特别小时&#xff0c;就说这俩个向量相似度高&#xff0c;反之相似度不高。所以&#xff0c;衡量相似度的指标就是距离度量。经常使用的相似度计算…

poll函数_I/O复用 - 三组I/O复用函数的比较

在之前的文章中 I/O复用 - epoll 和 I/O复用 - select&poll 中我们讨论了三组I/O复用的系统调用&#xff0c;这3组系统调用都能同时监听多个文件描述符。它们将等待由timeout参数指定的超时时间&#xff0c;直到一个或多个文件描述符上有事件发生时返回&#xff0c;返回值是…

HTML适应手机浏览器宽度

在网页的<head>中增加以上这句话&#xff0c;可以让网页的宽度自动适应手机屏幕的宽度: <meta name"viewport" content"widthdevice-width, initial-scale1.0, minimum-scale0.5, maximum-scale2.0, user-scalableyes" /> <meta name&q…

css3画图那些事(三角形、圆形、梯形等)

闲来无事&#xff0c;写写图形。当时巩固一下css3吧.。前端小白&#xff0c;写的不好还请前辈多指教。 三角形 { width: 0;height: 0;border-bottom: 140px solid red ;border-right: 70px solid transparent;border-left: 70px solid transparent; } 圆形 {width: 0px;height…

MyBatis教程– CRUD操作和映射关系–第1部分

CRUD操作 MyBatis是一个SQL Mapper工具&#xff0c;与直接使用JDBC相比&#xff0c;它极大地简化了数据库编程。 步骤1&#xff1a;创建一个Maven项目并配置MyBatis依赖项。 <project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema…

Java开发人员的升级之路

第一部分&#xff1a;对于参加工作一年以内的同学。恭喜你&#xff0c;这个时候&#xff0c;你已经拥有了一份Java的工作。这个阶段是你成长极快的阶段&#xff0c;而且你可能会经常加班。但是加班不代表你就可以松懈了&#xff0c;永远记得我说的那句话&#xff0c;从你入行那…

docker 数据库 mysql_在Docker中体验数据库之MySql

在上一篇在Docker中体验数据库之Mongodb之后&#xff0c;这次记录一下在docker中安装mysql。过程要比Mongodb麻烦一点……参考网址&#xff1a;https://dev.mysql.com/doc/refman/5.7/en/linux-installation-docker.htmlhttps://hub.docker.com/r/mysql/mysql-server/安装过程如…

STL概览——栈( stack )、队列( queue )和优先级队列( priority_queue)

栈&#xff08;stack&#xff09; stack是一种先进后出&#xff08;First In Last Out&#xff0c;FILO&#xff09;的数据结构&#xff0c;它只有一个口&#xff0c;平常在我们写深度优先遍历算法时&#xff0c;&#xff0c;就会用到栈&#xff0c;stack允许我们增加&#xff…

使用JMeter对异步HTTP / REST服务进行压力/负载测试

尽管我一直在使用JMeter进行Web应用程序的压力测试和负载测试好几次&#xff0c;但我们还是花了一些时间才弄清楚如何使用该工具测试基于异步HTTP / REST的服务。 在我们这里&#xff0c;我是指一名程序员&#xff0c; Holger Staudacher &#xff0c;我很荣幸能与当前的一个项…