以前看到好多框架都在用注解,感觉好高端的样子,就是不理解注解的用途和原理,最近看了一些文章,自己也尝试写了一个例子,我将我对注解的理解作以记录,方便日后查看。
注解最主要的还是反射用的多,首先我们看下反射的一些方法,用的时候可以翻出来看下
/**
* 反射机制的应用场景:
*
* 逆向代码 ,例如反编译
* 与注解相结合的框架 例如Retrofit
* 单纯的反射机制应用框架 例如EventBus 2.x
* 动态生成类框架 例如Gson
*
* forName(String className) 根据class的名字生成一个Class对象,构造Class对象。
* getAnnotation(Class<A> annotationClass) 如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
* getAnnotations() 返回此元素上存在的所有注释。 这些元素可以说是变量,方法,或者类等
* getPackage() 获取此类的包。
* getName() 返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
* getMethods() 返回一个包含某些 Method 对象的数组
* getMethod(String name, Class<?>... parameterTypes) 返回指定的 Method 对象
* getField(String name) 返回指定 Field (变量)对象。
* getFields() 返回 Field (变量)对象的数组
* getDeclaredMethods() 获取所有的方法
* getReturnType() 获得方法的放回类型
* getParameterTypes() 获得方法的传入参数类型
* getDeclaredMethod("方法名",参数类型.class,……) 获得特定的方法
* getDeclaredConstructors() 获取所有的构造方法
* getDeclaredConstructor(参数类型.class,……) 获取特定的构造方法
* getSuperclass() 获取某类的父类
* getInterfaces() 获取某类实现的接口
*
* @author MrRight
*/
主要方法在上面,下来我们先写一个注解类,看看注解类主要有什么
@Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
@Target(ElementType.TYPE) // 用于修饰类
public @interface PraseClassMethod {
String name() default "";
}
@Reteniton 的作用是定义被它所注解的注解保留多久,共有三种策略
SOURCE
被编译器忽略,即不被编译,只保留在源文件CLASS
注解将会被保留在Class文件中,但在运行时并不会被VM保留。这是默认行为,所有没有用Retention注解的注解,都会采用这种策略。RUNTIME
保留至运行时。所以我们可以通过反射去获取注解信息。
@Target 的作用是定义被它所注解的注解保留多久,共有三种策略
- METHOD
可用于方法上 -
TYPE
可用于类或者接口上 -
ANNOTATION_TYPE
可用于注解类型上(被@interface修饰的类型)
-
CONSTRUCTOR
可用于构造方法上
-
FIELD
可用于域上,即全局变量上
-
LOCAL_VARIABLE
可用于局部变量上
-
PACKAGE
用于记录java文件的package信息
-
PARAMETER
可用于参数上
定义注解格式: public @interface 注解名 {定义体}
注解参数的可支持数据类型:
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
Annotation类型里面的参数该怎么设定:
- 第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
- 第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
- 第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.例:下面的例子FruitName注解就只有一个参数成员。
例如:下面的例子
@Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
@Target(ElementType.TYPE) // 用于修饰类
public @interface PraseClassMethod {
String name() default "";
public int age();
public float height() default 1f;
long weight();
}
我们将写好的注解类试试用一下看看能不能用
@PraseClassMethod(name = "world", age = 99, weight = 75l)
public class Test {
}
我试了下已经可以添加了,参数有default的可以赋值,如上面height就没有写
可是就这样能干嘛?好像什么也干不了,接下来我们就要说说注解到底能干什么了,要想用注解其实还要写一个处理注解的类让它继承AbstractProcessor,这样就能对注解的变量,类,方法等运行,编译,初始化等之前做一些处理了,例如,在运行方法前做一些权限的申请,在编译前将注解的实体类生成相对应的数据库操作Java文件,还有一些运行前的初始化,编译前文件的生成等。下面看看怎么去解析注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 源码级别, 这里的环境是 jdk 1.8
@SupportedAnnotationTypes("com.helloword.annotation.PraseClassMethod") // 处理的注解类型, 这里需要处理的是 com.helloword.annotation 包下的 PraseClassMethod 注解(这里也可以不用注解, 改成重写父类中对应的两个方法)
public class ParseClassMethodProcessor extends AbstractProcessor{
// 计数器, 用于计算 process() 方法运行了几次
private int count = 1;
// 用于写文件
private Filer filer;
private Types typeUtils;
private Elements elementUtils;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
filer = env.getFiler();
typeUtils = env.getTypeUtils();
messager = env.getMessager();
}
// 处理编译时注解的方法
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("start process, count = " + count++);
// 获得所有类
Set<? extends Element> rootElements = roundEnv.getRootElements();
System.out.println("all class:");
for (Element rootElement : rootElements) {
System.out.println(" " + rootElement.getSimpleName());
}
// 获得有注解的元素, 这里 PraseClassMethod 只能修饰类, 所以只有类
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(PraseClassMethod.class);
System.out.println("annotated class:");
for (Element element : elementsAnnotatedWith) {
PackageElement pkg = elementUtils.getPackageOf(element);
String className = element.getSimpleName().toString();
System.out.println("------------"+pkg+"."+className);
try {
Class<?> class1 = Class.forName(pkg+"."+className);
Method [] methods=class1.getMethods();
for(Method method:methods) {
Parameter[] parameters=method.getParameters();
for(Parameter parameter:parameters) {
System.out.println(method.getName()+"***"+parameter.getName());
}
}
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
System.out.println(" " + className);
String output = element.getAnnotation(PraseClassMethod.class).name();
// 产生的动态类的名字
String newClassName = className + "_New";
// 写 java 文件
createFile(newClassName, output);
}
return true;
}
private void createFile(String className, String output) {
StringBuilder cls = new StringBuilder();
cls.append("package com.helloword.annotation;\n\npublic class ")
.append(className)
.append(" {\n public static void main(String[] args) {\n")
.append(" System.out.println(\"")
.append(output)
.append("\");\n }\n}");
try {
JavaFileObject sourceFile = filer.createSourceFile("com.helloword.annotation." + className);
Writer writer = sourceFile.openWriter();
writer.write(cls.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
解析的主要在procerss()方法里面,在里面可以通过反射做一些处理,上面代码通过解析生成了一个新的java文件,并打印了一些信息,大家想做一些其他操作,可自行改动;
我们运行下看看代码是不是运行正确的,运行方法如下(包名和类名根据自行更改):
此时项目目录如下, 这里 out 目录为手动创建
• out
* production
# com.helloword.annotation
• src
* com.helloword.annotation
在命令行中进入项目根目录, 即 src 文件夹的上一层.
首先编译注解处理器: javac -encoding UTF-8 -d out\production\ src\com\helloword\annotation\ParseClassMethodProcessor.java src\com\helloword\annotation\PraseClassMethod.java
接着执行注解处理器: javac -encoding UTF-8 -cp out\production\ -processor com.helloword.annotation.ParseClassMethodProcessor -d out\production -s src\ src\com\helloword\bin\*.java
执行后打印信息如下
E:\Eclipse\eclipse-workSpace\HelloWord>javac -encoding UTF-8 -cp out\production\ -processor com.helloword.annotation.ParseClassMethodProcessor -d out\production -s src\ src\com\helloword\bin\*.java
start process, count = 1
this class file contain all class: PageInfoBin
this class file contain all annotated class:------------com.helloword.bin.PageInfoBin
setAddress***arg0
setSTORENUM***arg0
setMd5Name***arg0
setCheckState***arg0
wait***arg0
wait***arg1
wait***arg0
equals***arg0
PageInfoBin
start process, count = 2
this class file contain all class: PageInfoBin_New
this class file contain all annotated class:
start process, count = 3
this class file contain all class:this class file contain all annotated class:
因为就给PageInfoBin写了注解,所以只输出这个类的打印信息,后面的PageInfoBin_New是后面生成的java文件,解析器也进行了分析
注解暂时写这么多,希望各位指出不足,并提出自己的观点