Java 注解在 Android 中的使用场景
Java 元注解有 5 种,常用的是 @Target 和 @Retention 两个。
其中 @Retention 表示保留级别,有三种:
- RetentionPolicy.SOURCE - 标记的注解仅保留在源码级别中,并被编译器忽略
- RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 JVM 会忽略
- RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它
这三个值表示的生命周期为 SOURCE < CLASS < RUNTIME,即 RUNTIME 生命周期最长,涵盖了 SOURCE 和 CLASS,其次是 CLASS 的生命周期涵盖了 SOURCE。
不同的保留级别有不同的应用场景:
1、源码级别应用场景
1.1 APT
Annotation Processor Tools,即注解处理器。它在由源文件(.java)被 javac 编译成为字节码文件(.class)这个过程中,先采集到所有的注解信息并封装到 Element 对象中,然后再由 javac 自动调用注解处理器,无须手动调用。
接下来对使用方法做一个简介。
先创建一个保留级别为源码级别的注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Frank {int value();String id();
}
然后创建一个 Java-Library 模块,来编写注解处理程序。模块的代码结构如下:
注册注解处理器
先注册注解处理器文件。只有注册后它才能被 javac 调用。注册方式有两种,一是按照上图的路径创建同名的文件夹和文件(路径名固定,不要自己发挥),然后在 javax.annotation.processing.Processor 文件中将注解处理器文件的全类名写在里面即可:
com.frank.compiler.MyProcessor
另一种方式就是使用 AutoService 自动生成。首先在模块 gradle 中加入依赖:
dependencies {implementation 'com.google.auto.service:auto-service:1.0-rc6'annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}
AutoService 与 gradle-5.4.1 以上版本有兼容性问题,必须要向上边那样把 annotationProcessor 的依赖也写上才能避免无法自动生成 Processor 文件的情况。在 gradle-6.1.1 上亲测可行。
然后在注解处理器文件的类加上 AutoService 的注解:
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {return false;}
}
AutoService 的值固定为 Processor.class。编译后在 module/build/classes/java/main/META-INF/services/ 目录下就能找到 javax.annotation.processing.Processor 文件了,其内容与手动构建的是一样的。
注解处理器使用
注解处理类需要继承 AbstractProcessor 才具有处理注解的能力,通过重写 process() 可以对注解进行处理:
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {Messager messager = processingEnv.getMessager();messager.printMessage(Diagnostic.Kind.NOTE, "注解处理器输出!");return false;}
}
以上只是简单的输出了一句 log,本节不会具体介绍注解处理器结合具体业务该如何编写,仅介绍一般的使用流程。
想要让这句 log 能打印出来,还需要在类名上边打上 @SupportedAnnotationTypes 注解,表示我们关心的注解类型,其值为我们要处理的注解的全类名,以我们前边的 @Frank 注解为例,就应该写成:
@AutoService(Processor.class)
@SupportedAnnotationTypes("com.example.myapplication.Frank")
public class MyProcessor extends AbstractProcessor {// .....
}
最后,在需要使用注解处理器的模块的 gradle 文件中,引入注解处理器模块 compiler:
dependencies {annotationProcessor project(':compiler')
}
这样在编译后就能看到 log 输出了:
注意输出是在执行 compileDebugJavaWithJavac 任务时输出的,再次说明了,注解处理器由 javac 自动调用并分析了其中的源码。也印证了 APT 是源码保留级别适用的技术。
1.2 IDE 语法检查
之所以叫 IDE 语法检查是因为,检测语法的是 IDE 工具(或其插件),并不是 Java 编译器。
在 Android 中我们需要设计接口以供使用者调用时,如出现需要对方法参数进行类型限定,如限定为资源 ID、布局 ID 等类型参数时,可以利用 Android 为我们提供的语法检查注解,来辅助进行更为直接的参数类型检查与提示。
在 androidx.annotation 包下有很多这样的注解,如 @DrawableRes、@IdRes、@LayoutRes 等等,想要使用它们需要先引入依赖:
implementation 'androidx.annotation:annotation:1.2.0-alpha01'
使用时可以把它们加载方法类型之前:
public final Drawable getDrawable(@DrawableRes int id) {return getResources().getDrawable(id, getTheme());}
这样在调用 getDrawable() 方法时,就必须传入一个 Drawable 的资源值,IDE 就会在实参位置有红色下划线提示,注意编译是不会报错的:
除了上述的注解之外,还有几个元注解 @IntDef、@LongDef、@StringDef,利用它们可以自定义类似于 @DrawableRes、@IdRes、@LayoutRes 这样的注解。典型应用就是用注解替代枚举以节省内存。
Java 中 Enum (枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。比常量多 5 到 10 倍的内存占用。每个枚举值,也就是枚举对象的构成为 12 个字节的对象头 + 所有成员变量,另外还有个 8 字节对齐。
@IntDef 的源码:
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {/** Defines the allowed constants for this element */int[] value() default {};boolean flag() default false;boolean open() default false;
}
通过 value() 可以指定注解能包含哪些值。下面就利用这一点看看如何用注解替换枚举:
public class Gender {/*enum Gender {MALE, FEMALE}*/public static final int MALE = 0;public static final int FEMALE = 1;private int gender;@Retention(RetentionPolicy.SOURCE)@Target(ElementType.PARAMETER)@IntDef({MALE, FEMALE})@interface GenderInt {}public void setGender(@GenderInt int gender) {this.gender = gender;}
}
Gender 类表示性别,这里我们不用枚举,而是把男性和女性分别定义成两个整型常量 0 和 1。使用 @IntDef 注解自定义一个 @GenderInt 注解,用 @Target(ElementType.PARAMETER) 指定作用在参数上,用 @IntDef({MALE, FEMALE}) 规定可选值只有 MALE, FEMALE 两个。
在外部调用 setGender() 时,如果传入的值不在 MALE, FEMALE 之中,IDE 就会提醒:
使用源码级别注解的典型框架是 ARouter。关于 Android 组件化以及 ARouter 原理可以参考 Android 组件化基础(二)—— 仿 ARouter 实现一个路由框架。
2、字节码级别应用场景
字节码增强,也称为字节码插桩,说白了就是在字节码文件里写代码和修改。过程大概是:
.class 文件 -> 用 IO 流读写 class 文件 -> byte[] -> 按照 .class 文件格式进行修改。
这里仅举简单例子,不具体扩展。对字节码插桩感兴趣的可以去看 ASM 字节码插桩入门。
假如要统计每个方法的执行时间,并且不想在源代码的方法中加入时间统计代码,那就可以把需要统计的方法打上注解,编译之后在字节码的方法首末加入统计运行时间的代码:
ARouter、ButterKnife 都是字节码保留级别的典型应用。
3、运行时级别应用场景
最典型的就是反射。
3.1 通过反射实现 findViewById()
一个最简单的示例就是通过反射 + 注解实现 findViewById()。
首先定义一个运行时级别的注解 @Inject:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {int value();
}
值是被 @Inject 修饰控件的资源 ID。
然后编写工具类,拿到控件 ID 后,调用 findViewById() 得到控件对象,最后通过反射把对象设置给控件:
public class InjectUtils {public static void inject(Activity activity) {Class<? extends Activity> aClass = activity.getClass();Field[] fields = aClass.getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(Inject.class)) {Inject annotation = field.getAnnotation(Inject.class);int id = annotation.value();View view = activity.findViewById(id);field.setAccessible(true);try {field.set(activity, view);} catch (IllegalAccessException e) {e.printStackTrace();}}}}
}
给控件打上注解并标明资源 ID,进行测试:
public class MainActivity extends AppCompatActivity {@Inject(R.id.button)private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);InjectUtils.inject(this);button.setText("Inject Success");}
}
3.2 通过反射获取真实泛型类型
当我们对一个泛型类进行反射时,需要得到泛型中的真实数据类型,来完成如 Json 反序列化的操作。此时需要通过 Type 体系来完成。 Type 接口包含了一个实现类 Class 和四个实现接口,他们分别是:
- TypeVariable 泛型类型变量,可以获取泛型上下限等信息
- ParameterizedType 具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
- GenericArrayType 当需要描述的类型是泛型类的数组时,比如 List[]、Map[],此接口会作为 Type 的实现
- WildcardType 通配符泛型,获得上下限信息
下面来介绍一个获取泛型真实类型的典型应用。
典型应用 —— Gson 反序列化
对从服务器接收的响应数据进行反序列化是项目中的常规操作。我们举个例子,假如使用 Gson 进行反序列化,用 Response 表示响应体,用 Data 表示 Response 中携带的真实数据:
public class Response<T> {private T data;private int code;private String message;public Response(T data, int code, String message) {this.data = data;this.code = code;this.message = message;}...
}public class Data {private String result;public Data(String result) {this.result = result;}@Overridepublic String toString() {return "Data{" +"result='" + result + '\'' +'}';}
}
模拟从服务器接收数据进行反序列化的过程:
public class Test {public static void main(String[] args) {// 构造一个 Response 对象模拟服务器返回的数据Response<Data> response = new Response<>(new Data("数据"),1,"成功");Gson gson = new Gson();String json = gson.toJson(response);System.out.println(json);// 反序列化,由于没有提供 Response 内 data 的真实类型,所以反序列化失败抛出异常Response<Data> responseData = gson.fromJson(json, Response.class);System.out.println(responseData.getData().getClass());}
}
执行上述代码会抛出异常:
Exception in thread "main" java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.frank.reflect.Dataat com.frank.reflect.Test.main(Test.java:16)FAILURE: Build failed with an exception.
如果反序列化的 Bean 没有泛型,那么可以使用 public <T> T fromJson(String json, Class<T> classOfT)
方法进行反序列化,但如果像 Response 那样带有泛型,但是却没提供泛型的真实类型,就会抛出上面的异常。
解决方案是使用 TypeToken 提供 Response<Data>
的类型参数 Data 给 fromJson():
public class Test {public static void main(String[] args) {// 构造一个 Response 对象模拟服务器返回的数据Response<Data> response = new Response<>(new Data("数据"), 1, "成功");Gson gson = new Gson();String json = gson.toJson(response);System.out.println(json);// 反序列化,通过创建 TypeToken 的匿名子类获取到 Response 的真实类型,即 DataType type = new TypeToken<Response<Data>>() {}.getType();Response<Data> responseData = gson.fromJson(json, type);System.out.println(responseData.getData().getClass());}
}
这样就能正确输出反序列化结果了:
{"data":{"result":"数据"},"code":1,"message":"成功"}
class com.frank.reflect.Data
解决问题的原理
为何通过匿名子类对象获取到类型参数?原因有二:
- Java 泛型在编译期被擦除,因此运行时无法直接获取到
Response<Data>
上的类型参数 - 匿名子类会保存类型参数。因为编译器会生成一个合成类来表示匿名子类的类型,并且在合成类中会保留泛型信息。这样做的目的是为了在匿名子类中能够正确地访问和使用泛型类型
基于以上两点,我们来看 Gson 源码是如何通匿名子类获取类型参数的:
public class TypeToken<T> {final Class<? super T> rawType;final Type type;final int hashCode;/*** 客户端创建一个空的匿名子类。这样做可以将类型参数嵌入到匿名类的类型层次结构中,* 因此我们可以在运行时重新构造它,尽管它被删除了*/protected TypeToken() {// 1.对匿名子类调用 getSuperclassTypeParameter() 获取类型参数this.type = getSuperclassTypeParameter(getClass());this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);this.hashCode = type.hashCode();}static Type getSuperclassTypeParameter(Class<?> subclass) {// 2.getGenericSuperclass() 会返回 subclass 所表示的实体(类、接口、原始类型或 void)的// 直接父类的类型,这个类型是带有泛型信息的Type superclass = subclass.getGenericSuperclass();if (superclass instanceof Class) {throw new RuntimeException("Missing type parameter.");}ParameterizedType parameterized = (ParameterizedType) superclass;// 3.取所有泛型类型的首个类型做规范化转换后返回return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);}public final Type getType() {return type;}
}public final class $Gson$Types {public static Type canonicalize(Type type) {if (type instanceof Class) {Class<?> c = (Class<?>) type;return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;} else if (type instanceof ParameterizedType) {// 如果是一个参数化类型就返回一个 ParameterizedTypeImpl 的实例ParameterizedType p = (ParameterizedType) type;return new ParameterizedTypeImpl(p.getOwnerType(),p.getRawType(), p.getActualTypeArguments());} else if (type instanceof GenericArrayType) {GenericArrayType g = (GenericArrayType) type;return new GenericArrayTypeImpl(g.getGenericComponentType());} else if (type instanceof WildcardType) {WildcardType w = (WildcardType) type;return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());} else {// type is either serializable as-is or unsupportedreturn type;}}
}
实际上 TypeToken 构造方法上,官方给的注释已经提示了使用匿名子类的原因。具体的实现步骤可以分三步:
- 将 TypeToken 的构造方法声明为 protected,也就是说,在 TypeToken 所在的 com.google.gson.reflect 包之外,想要拿到 TypeToken 或其子类对象,就无法通过 new TypeToken() 的方法,而只能使用创建匿名子类对象的方式,即 new TypeToken(){}。getClass() 拿到的就是这个匿名子类:class com.frank.reflect.Test$1(在 Test 内声明的匿名子类),然后对这个匿名子类调用 getSuperclassTypeParameter()
- 在 getSuperclassTypeParameter() 内先对匿名子类调用 getGenericSuperclass() 获取到带有泛型信息的父类,即 TypeToken<Response<Data>>
- 将父类对象转换为 ParameterizedType,并调用其 getActualTypeArguments() 获取到真实的类型参数数组,取数组的第 0 个(在这个例子中就是
Response<Data>
)传入 G s o n Gson GsonTypes.canonicalize() 将结果进行规范化并返回
TypeToken 设计的巧妙之处就在于用 protected 的构造方法迫使你使用匿名子类才能构建其对象,然后在 getSuperclassTypeParameter() 方法中用这个匿名子类对象对应的 Class 去获取含有泛型的直接父类,就是 TypeToken<T>
。使用匿名子类获取真实类型参数的做法也很值得借鉴。
4、练习
通过反射 + 注解实现被启动 Activity 自动获取 Intent 中携带的数据。详细点说,就是在 Activity1 通过 Intent 启动 Activity2 时,可能会给 Intent 通过 putXxxExtra() 的形式附带一些参数。在 Activity2 中拿到 Intent 对象之后需要一个一个地把数据从 Intent 中拿出来,过程繁琐。我们要优化的就是从 Intent 中手动拿数据这个过程。
首先在 MainActivity 中建立一些要传递的数据,包含多种类型:
public class MainActivity extends AppCompatActivity {// 需要作为 Intent 的参数被传递的变量要提出来做成员变量private String string;private String attr;private int[] array;private UserParcelable userParcelable;private UserParcelable[] userParcelables;private List<UserParcelable> userParcelableList;private UserSerializable userSerializable;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Intent intent = new Intent(MainActivity.this, SecondActivity.class);ArrayList<Parcelable> parcelables = new ArrayList<>();parcelables.add(new UserParcelable("user3"));intent.putExtra("StringKey", "testString").putExtra("attr", "attr").putExtra("array", new int[]{1, 2}).putExtra("userParcelable", new UserParcelable("cat")).putExtra("userParcelables", new UserParcelable[]{new UserParcelable("user1"),new UserParcelable("user2")}).putExtra("userParcelableList", parcelables).putExtra("userSerializable", new UserSerializable("user4"));startActivity(intent);}
}
其中 UserParcelable 与 UserSerializable 两个类型分别模拟实现了 Parcelable 和 Serializable 接口的 JavaBean 类型:
public class UserParcelable implements Parcelable {String name;public UserParcelable(String name) {this.name = name;}protected UserParcelable(Parcel in) {name = in.readString();}public static final Creator<UserParcelable> CREATOR = new Creator<UserParcelable>() {@Overridepublic UserParcelable createFromParcel(Parcel in) {return new UserParcelable(in);}@Overridepublic UserParcelable[] newArray(int size) {return new UserParcelable[size];}};@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(name);}
}
------------------------------------------------------------------
public class UserSerializable implements Serializable {String name;public UserSerializable(String name) {this.name = name;}
}
然后新建一个注解 @InjectExtras:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectExtras {String value() default "";
}
用法是标记接收 Intent 数据一方的成员变量,value() 默认为空,为空时说明 MainActivity 在存数据的时候,key 和变量名是一样的,如果不一样,就把 key 作为 value:
public class SecondActivity extends AppCompatActivity {@InjectExtras("StringKey")private String string;@InjectExtrasprivate String attr;@InjectExtrasprivate int[] array;@InjectExtrasprivate UserParcelable userParcelable;@InjectExtras("userParcelables")private UserParcelable[] userParcelables;@InjectExtrasprivate List<UserParcelable> userParcelableList;@InjectExtrasprivate UserSerializable userSerializable;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);InjectUtils.injectExtra(this);}
}
最后实现工具方法:
/*** 拿到 activity 后,遍历所有成员变量,筛选出被 @InjectExtras 注释的成员,* 获得其对应的属性 Key 值,然后通过 getIntent().getExtras() 获取到对方* 发送的参数 Bundle,然后根据 Key 从中取出 Value,最后通过反射的方式为接* 收方的成员变量赋值。** @param activity 接收参数的 Activity*/public static void injectExtra(Activity activity) {Class<? extends Activity> clazz = activity.getClass();Field[] fields = clazz.getDeclaredFields();Bundle extras = activity.getIntent().getExtras();for (Field field : fields) {if (field.isAnnotationPresent(InjectExtras.class)) {InjectExtras annotation = field.getAnnotation(InjectExtras.class);if (annotation == null) {Log.e(TAG, "Get annotation @InjectExtras failed.");return;}// 找到 field 在发送方存入参数的 key,并取出对应的 valueString key = TextUtils.isEmpty(annotation.value()) ? field.getName() : annotation.value();Object object = extras.get(key);// value 如果是 Parcelable[] 则不能直接赋值,其它类型可以,所以通过 getComponentType()// 获取数组元素类型,如果不是数组则返回 nullClass<?> componentType = field.getType().getComponentType();if (componentType != null && field.getType().isArray() && Parcelable.class.isAssignableFrom(componentType)) {// 确定了 object 是 Parcelable[],进行转换Object[] objArray = (Object[]) object;// 使用工具类将数组内元素转换成 field 对应的类型,即 UserParcelable// 第三个参数实际上要获取的是 UserParcelable[].class 这个类型,只能通过反射获取object = Arrays.copyOf(objArray, objArray.length, (Class<? extends Object[]>) field.getType());}field.setAccessible(true);try {field.set(activity, object);} catch (IllegalAccessException e) {e.printStackTrace();}}}}
获取 Extra 中 key 的规则是,如果 Field 被 @InjectExtras 注解指定了 value,那么就用指定的值,否则默认使用变量名。
拿到 key 之后获取对应的 value,如果 value 类型不是 Parcelable[] 的话就可以直接设置给对应的 Field 了;如果是的话,需要进行一个转化再赋值。这一步中需要注意,如果 field 是 UserParcelable[],那么 field.getType() 得到的是 class [Lxxx.xxx.UserParcelable,再调用 getComponentType() 就拿到了数组内的元素类型 class xxx.xxx.UserParcelable。由于不能将 object 直接做类型强转为 Parcelable[],所以借用 Arrays.copyOf() 做这个类型转换,第三个参数 field.getType() 的结果不能直接用,编译不过,需要将 class [Lxxx.xxx.UserParcelable 转换为 Class<? extends Object[]>
,因为 field.getType() 返回的类型实际上是一个 Class<?>
,强转成 Class<? extends UserParcelable[]>
会有 unchecked warning,但是转成 Class<? extends Object[]>
就不会。
相关文章:

Java 注解在 Android 中的使用场景
Java 元注解有 5 种,常用的是 Target 和 Retention 两个。 其中 Retention 表示保留级别,有三种: RetentionPolicy.SOURCE - 标记的注解仅保留在源码级别中,并被编译器忽略RetentionPolicy.CLASS - 标记的注解在编译时由编译器保…...

【开源】基于Vue和SpringBoot的数字化社区网格管理系统
项目编号: S 042 ,文末获取源码。 \color{red}{项目编号:S042,文末获取源码。} 项目编号:S042,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、开发背景四、系统展示五、核心源码5…...

Go语言简要介绍
Golang是一种编程语言,也称为Go或者Go语言。它是由Google开发的一种编译型、静态类型的语言。Golang的目标是提高程序开发的效率,同时保证程序的性能和安全。 Golang在语法结构上类似于C语言,但是通过引入新的概念和语法,比如gor…...

STM32H7 RTC及PC13问题
程序加了RTC时间过后,发现原本的RTC定时唤醒中断也不好使了,开始以为是PC13入侵检测引脚问题,经过测试,发现了一个大问题,当使用 HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BCD); 函数后,RTC变得…...

AntDB“超融合+流式实时数仓”——颠覆50年未变的数据库内核
流式处理引擎,颠覆50年未变的数据库内核 流式处理的概念 2001年9月11日,美国世贸大楼被袭击,美国国防部第一次将“主动预警”纳入国防的宏观战略规划。而IBM作为当时全球最大的IT公司,承担了大量基础支撑软件研发的任务。其中200…...

TZOJ 1376 母牛的故事(递推和递归)
答案1(递推): #include<stdio.h> int main() {int n0,i0;int a[55] { 0,1,2,3,4 }; //数组下标就相当于过了几年,以第四年母牛生出的第一只小母牛成年为周期,初始化前四年的值while (scanf("%d", …...

五种多目标优化算法(MOPSO、MOAHA、NSGA2、NSGA3、MOGWO)求解微电网多目标优化调度(MATLAB)
一、多目标优化算法简介 (1)多目标粒子群优化算法MOPSO 多目标应用:基于多目标粒子群优化算法MOPSO求解微电网多目标优化调度(MATLAB代码)-CSDN博客 (2)多目标人工蜂鸟算法(MOAHA…...

01_原理-事件循环
01_原理-事件循环 文章目录 01_原理-事件循环一、浏览器的进程模型①:何为进程?②:何为线程?③:浏览器有哪些进程和线程? 二、渲染主线程是如何工作的?三、若干解释①:何为异步&…...

Redis的性能,哨兵模式,集群,
Redis的性能管理; redis的数据保存在内存中 redis-cli info memory redis内存使用info memory命令参数解析 used_memory:236026888 由 Redis 分配器分配的内存总量,包含了redis进程内部的开销和数据占用的内存,以字节(byte)…...

如何选择共模噪声滤波器
在当前电子产品中,绝大多数的高速信号都使用地差分对结构。 差分结构有一个好处就是可以降低外界对信号的干扰,但是由于设计的原因,在传输结构上还会受到共模噪声的影响。 共模噪声滤波器就可以用于抑制不必要的共模噪声,而不会对…...

Python与设计模式--模板模式
23种计模式之 前言 (5)单例模式、工厂模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式、(7)代理模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式、桥梁模式、(11)策略模式、责任链模式、命令模式、中介者模…...

LoadRunner自动化测试工具的应用
目录 第一部分:Loadrunner的简介 1.1 安装注意事项 1.2 协议的选择或者 VUSER 类型的选取 1.3 LR 的基本原理 1.4 测试脚本录制/分配所遵循的几个原则 第二部分:录制脚本 2.1 录制脚本前需要理解的几个基本概念 2.1.1 事务(Transaction) 2.1.2 集合点(Rendezvous) 2.1…...

工厂模式是一种创建对象的设计模式,使用工厂类来创建对象,而不是直接使用 new 关键字来创建对象。
文章目录 示例代码virtual std::string Operation() const = 0;如何理解std::string Operation() const override {这句如何理解?Factory 类包含一个静态方法 CreateProduct,它根据传入的类型参数来创建并返回具体的产品实例。这句话理解?std::unique_ptr<Product> pr…...

NET MVC中使用Element-Plus框架编写组件
一、目的 在NET MVC中使用Element-Plus编写可重复使用的组件。 二、准备工作 2.1 NET MVC项目 2.2 MVC项目中使用Element-Plus框架。不熟悉的可以参考此文章: NET MVC中如何使用Element-Plus-CSDN博客 三、组件编写 3.1、新建一个MVC的部分视图页面ÿ…...

在线文库系统 转码功能源代码展示 支持文档在线预览查阅功能
1、支持 pdf,doc,docx,ppt,pptx,txt,xlsx,xls,csv,zip,epub,ai,psd 格式的文件 2、文库系统的上传界面,用户可以进行上传自己的文件,然后自定义文档售价,来赚取金额。 3、文库系统的部分代码披露: <template><div clas…...

Linux /etc/shadow密码生成操作示例
一. 前言 之前学习过Linux文件系统下/etc/shadow里面保存着各个用户名的密码,并且密码是通过MD5算法加盐的方式生成的。但是一直没有自己真正动手生成过,今天,就来自己动手写代码生成下。 二. 代码验证/etc/shadow中密码 1. 通过passwd命令生…...

seata集成springboot的一些错误小计
1 seata依赖没找到 dependencies.dependency.version for com.alibaba.cloud:spring-cloud-starter-alibaba-seata:jar is missing. line 126, column 21错误原因:未指定具体的seata版本 解决 <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-st…...

springmvc(基础学习整合)
SpringMVC是Spring框架提供的构建Web应用程序的全功能MVC模块。 在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。 springMVC基本介绍: http://t.csdnimg.cn/TOzw9 MVC是一种设计思想,将一个应…...

采集软件大全-全网免费的采集软件大全
采集软件大揭秘:从排名到任意网站采集的全方位解读 在数字时代,信息是黄金,而采集软件就是那把能够淘金的工具。无论是市场调研、竞品分析还是SEO优化,采集软件都扮演着不可或缺的角色。在这个领域里,有许多选择&…...

世微AP5125 DC-DC降压恒流 LED车灯电源驱动IC SOT23-6
产品描述 AP5125 是一款外围电路简单的 Buck 型平均电流检测模式的 LED 恒流驱动器,适用于 8-100V 电压范围的非隔离式大功率恒流 LED 驱动领域。芯片采用固定频率 140kHz 的 PWM 工作模式, 利用平均电流检测模式,因此具有优异的负载调整 率…...

STC15-串口通信打印输出数据printf函数与sprintf函数
STC15-串口通信打印输出数据printf函数与sprintf函数 1.打印输出数据有二种printf函数与sprintf函数,不同之处有:(1)函数的声明不同(2)函数的功能不同(3)用法举例 该问题引用百度知道…...

Android 11.0 默认开启USB调试功能
Android 11.0 默认开启USB调试功能 近来收到项目反馈需求想要默认开启USB调试功能,默认开启USB调试功能主要是在UsbDebuggingActivity.java文件中实现,具体修改参照如下: /vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/and…...

单片机AVR单片机病房控制系统设计+源程序
一、系统方案 设计一个可容8张床位的病房呼叫系统。要求每个床位都有一个按钮,当患者需要呼叫护士时,按下按钮,此时护士值班室内的呼叫系统板上显示该患者的床位号,并蜂鸣器报警。当护士按下“响应”键时,结束当前呼叫…...

C语言——多种方式打印出1000之内的所有的“水仙花数”
所谓水仙花数,是指一个3位数,其各位数字立方和等于该数本身。水仙花数是指一个三位数,它的每个位上的数字的立方和等于它本身。例如,153是一个水仙花数,因为1^3 5^3 3^3 153。 方法一 #define _CRT_SECURE_NO_WARNINGS 1#include <std…...

.net 8 发布了,试下微软最近强推的MAUI
先看下实现的效果: 下面发下XAML文件: <?xml version"1.0" encoding"utf-8" ?> <ContentPage xmlns"http://schemas.microsoft.com/dotnet/2021/maui"xmlns:x"http://schemas.microsoft.com/winfx/2009/…...

【产品经理】AI在SaaS产品中的应用及挑战
随着ChatGPT大模型在全球的爆火,AI迅速在各个行业内,助力于各行业的效率提升。而SaaS领域,AI同样也大有可为。 AI(人工智能,Artificial Intelligence的缩写)近一年来一直处于舆论风口,随着ChatG…...

Python实现一箭穿心
文章目录 🎄效果🏳️🌈Turtle模块🌹代码🌺代码讲解 🎄效果 🏳️🌈Turtle模块 Turtle是一个绘图工具,是Python标准库中的一个模块。它提供了一种简单而直观的方式来创…...

机器人AGV小车避障传感器测距
一、A22超声波传感器 该模块是基于机器人自动控制应用而设计的超声波避障传感器,针对目前市场上对于超声波传感器模组盲区大、测量角度大、响应时间长、安装适配性差等问题而着重设计。 具备了盲区小、测量角度小、响应时间短、过滤同频干扰、体积小、安装适配性高…...

Boost:进程间共享内存
Linux编程:进程间共享内存_linux 判断共享内存是否存在-CSDN博客 介绍了如何在linux的进程间共享内存。 Boost对共享内存进行了封装,可以更为方便的使用共享内存。 1.创建共享内存 #include <boost/interprocess/shared_memory_object.hpp> using namespace boost::i…...

Android Camera Surface显示相关问题总结
1.默认创建的Preview Surface填充RGBA数据显示异常。 //界面创建的Surface format默认为4(RGB_565),而预览界面所需的格式是RGBA_8888 ANativeWindow_setBuffersGeometry(window, width, height, WINDOW_FORMAT_RGBA_8888); 2.调用ANativeWindow的dequeueBuffer出错ÿ…...