Java自定义注解简单用法

以前看到好多框架都在用注解,感觉好高端的样子,就是不理解注解的用途和原理,最近看了一些文章,自己也尝试写了一个例子,我将我对注解的理解作以记录,方便日后查看。

注解最主要的还是反射用的多,首先我们看下反射的一些方法,用的时候可以翻出来看下

/**
 * 反射机制的应用场景:
 * 
 * 	逆向代码 ,例如反编译
 * 	与注解相结合的框架 例如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文件,解析器也进行了分析

注解暂时写这么多,希望各位指出不足,并提出自己的观点



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

baoolong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值