Java 注解及自定义注解处理器
注解介绍
注解,也被称为元数据(所谓的元数据,就是描述数据的数据)。
所以注解的主要作用就是给指定代码一些描述信息。这些指定代码可以是一个类、一个方法或者是一个属性。
Java注解是在Java SE5中被引入进来的,在Java中内置了三种注解以及四种元注解。
内置注解
内置注解 | 说明 |
---|---|
@Override | 表示当前的方法定义将覆盖超类中的方法,如果方法名或者参数有误,那么编译器就会报错提示。 |
@Deprecated | 用于注解已经过时的代码(方法或者某属性),使用了该注解的方法或者属性编译器会发出警告 |
@SuppressWarnings | 关闭不当的编译器警告信息。如果一个方法调用的方法已过时,或使一个不安全的类型转换,编译器可能会产生一个警告。您可以通过包含使用@SuppressWarnings注解代码的方法标注抑制这些警告。 |
元注解
在Java中提供了四种元注解,这四个注解的主要作用就是用于注解其他注解。
元注解 | 说明 |
---|---|
@Target | 指定注解的作用域 |
@Retention | 指定在那个级别保存该注解信息。 |
@Documented | 指定这个注解的元素可以被javadoc此类的工具文档化 |
@Inherite | 指定该注解类型被自动继承。如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类 |
@Target
@Target:指定注解的作用域,由ElementType枚举指定定义。
该注解的源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType源代码如下:
public enum ElementType {
TYPE, //接口、类、枚举、注解
FIELD, //字段、枚举的常量
METHOD, //方法
PARAMETER, //方法参数
CONSTRUCTOR,//构造函数
LOCAL_VARIABLE,//局部变量
ANNOTATION_TYPE,//注解
PACKAGE,//包
TYPE_PARAMETER,// java8声明
TYPE_USE// java8声明
}
@Retention
@Retention:指定在那个级别保存该注解信息,由RetentionPolicy枚举定义。该注解的源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
RetentionPolicy源码如下:
public enum RetentionPolicy {
SOURCE,//仅仅在源代码中可以使用,可以被编译器识别
CLASS,//在源代码、class文件中可用,不能再runtime中使用,默认就是这个策略
RUNTIME //在源代码、class、runtime中都可以使用
}
自定义注解
在自定义注解时,我们需要使用上面提到的元注解,通过元注解自定义注解。
下面定义一个注解类@UseCase:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
// 注解的元素
public int id();
// 为注解的元素设置默认值
public String description() default "no description";
}
在注解@UseCase中,这里为该注解定义了两个元素,分别是id和description,同时为元素description设置了默认值”no description”。
在给注解的元素指定具体值时,主要是通过键值对的形式赋值。如下所示:
private class PasswordUtils {
@UseCase(id = 47, description =
"Passwords must contain at least one numeric")
public boolean validatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}
@UseCase(id = 48)
public String encryptPassword(String password) {
return new StringBuilder(password).reverse().toString();
}
}
注解元素
在为注解类定义元素时,注解的元素只能是以下类型,否则编译器会报错:
- 八大基本类型(int、short、long、float、double、boolean、char、byte)
- String
- Class
- enum
- Annotation
- 以上类型的数组
注解的元素定义如下,如同普通接口中的方法定义,与普通方法不同的是注解的元素可以设置默认值。
类型 参数名() default 默认值
其中默认值是可选的,可以定义, 也可以不定义。
如果定义的注解中不存在元素,那么这样的注解被称为标记注解。
注解元素必须要有确定的值,要么定义默认值,要么在使用时赋值。对于引用类型,绝对不能出现null的存在。所以在判断元素存在或缺失时可以通过负数、空字符串设定。
如果注解的元素命名为value,并且是唯一需要赋值的元素,那么可以通过快捷方式赋值,即@UseCase(100)
通过反射实现注解处理器
在上面主要介绍了注解和自定义注解相关的知识点。对于注解,如果没有通过注解处理器来对注解进行处理,那么注解的作用和注释就没有什么区别了。
所以这里主要总结两种常见的实现java注解处理器的方式:
- 通过反射实现的运行时注解处理器
- 通过apt工具实现的编译时注解处理器
AnnotatedElement接口
AnnotatedElement,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
- AccessibleObject,
- Class,
- Constructor,
- Field,
- Method,
- Package
通过这些实现类不难知道,AnnotatedElement可以通过反射获取。
在AnnotatedElement中主要定义了四个方法,如下:
接口方法 | 说明 |
---|---|
< T extends Annotation> T getAnnotation(Class annotationClass) | 获取元素上指定类型的注解,如果存在则返回null |
Annotation[] getAnnotations() | 返回元素上存在的所有注释 |
Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释 |
boolean isAnnotationPresent(Class annotationClass) | 如果指定类型的注释存在于此元素上,则返回 true,否则返回 false |
通过反射实现注解处理器
这里通过注解,实现一个简单的Android依赖注入的功能。
首先,定义一个注解@BindView
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
这里需要注意,如果自定义注解需要在运行时使用反射来处理时,那么该注解的@Retention注解一定要是RetentionPolicy.RUNTIME,因为反射是在JVM运行时来进行处理的。
然后开始实现一个注解处理器,代码如下:
public class InjectUtils {
public static void inject(Activity activity) {
//1、 获取指定类上申明的所有字段
Field[] declaredFields = activity.getClass().getDeclaredFields();
for (Field field : declaredFields) {
// 2、获取每次字段中包含BindView注解的字段。
BindView annotation = field.getAnnotation(BindView.class);
if (annotation != null) {
// 3、通过获取注解指定的value,初始化View
int value = annotation.value();
View view = activity.findViewById(value);
try {
field.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
在具体activity中使用如下:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
TextView textView;
@BindView(R.id.button)
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectUtils.inject(this);
textView.setText("text通过注解初始化并设置值");
button.setBackgroundColor(Color.RED);
button.setText("button通过注解实现了初始化");
}
}
通过反射实现注解处理器的大概过程基本如上,这里使用反射都是在JVM运行时实现的,这样在JVM运行时通过反射实现注解处理器的方式在一定成都上会对性能造成一定的影响。所以,在日常开发中很少会使用这样的方式。
我们更加关注的是如何通过apt工具实现注解处理器,这个将在下一个章节具体说明。
源自:https://blog.csdn.net/kaifa1321/article/details/79622715
还没有评论,来说两句吧...