java 注解详解
先引用一下百度百科的名词解析:
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
①编写文档:通过代码里标识的元数据生成文档【生成文档 doc 文档】
② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
下面开始正文:
概述:
就如上面所说,注解有几种用途,第一种是编写文档,这种注解会在 javadoc 中存在;
第二种是代码分析,这需要使用 反射 的 api 去解析注解;
第三种类是提供编译时的检查,如 deprecated 这些,编译的时候会产生警告信息。
举几个例子:
1、下面的例子,注解使用了 @Documented 注解,这意味着,我们的注解会在 javadoc 生成的文档中出现,另外一个 @Retention 的用途下面会说到
1 2 3 4 5 6 7 8 9 10 11 | package com.ruby; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Documented @Retention (RetentionPolicy.SOURCE) public @interface ClassPreamble { String author(); } |
使用:
1 2 3 4 | @ClassPreamble ( author = "ruby" ) public class Main ... |
2、代码分析 (我们拙劣地模拟一下 Spring Boot 的路由注解)
代码目录结构:
RequestMapping 注解定义:
1 2 3 4 5 6 7 8 9 10 11 12 | package com.ruby.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.METHOD) public @interface RequestMapping { String value() default "/" ; } |
Home 控制器定义:
1 2 3 4 5 6 7 8 9 10 | package com.ruby.controller; import com.ruby.annotation.RequestMapping; public class Home { @RequestMapping ( "test" ) public void HelloWorld() { System.out.println( "HelloWorld !" ); } } |
使用方法 (命名不是太规范,test 一般用于测试文件命名):关键是 main 方法里面通过反射去解析注解的代码,其他两个方法只是工具方法 (用以获取包下面所有类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | package com.ruby.test; import com.ruby.annotation.RequestMapping; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; public class TestHome { public static void main(String[] args) throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException, InvocationTargetException{ String route = "test" ; Class[] classes = getClasses( "com.ruby.controller" ); for (Class cls : classes) { Method[] methods = cls.getMethods(); for (Method method : methods) { RequestMapping requestMapping = method.getAnnotation(RequestMapping. class ); if (requestMapping != null ) { String mapping = requestMapping.value(); if (mapping.equals(route)) { method.invoke(cls.newInstance()); } } } } } /** * Scans all classes accessible from the context class loader which belong to the given package and subpackages. * * @param packageName The base package * @return The classes * @throws ClassNotFoundException * @throws IOException */ private static Class[] getClasses(String packageName) throws ClassNotFoundException, IOException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); assert classLoader != null ; String path = packageName.replace( '.' , '/' ); Enumeration<URL> resources = classLoader.getResources(path); List<File> dirs = new ArrayList<File>(); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); dirs.add( new File(resource.getFile())); } ArrayList<Class> classes = new ArrayList<Class>(); for (File directory : dirs) { classes.addAll(findClasses(directory, packageName)); } return classes.toArray( new Class[classes.size()]); } /** * Recursive method used to find all classes in a given directory and subdirs. * * @param directory The base directory * @param packageName The package name for classes found inside the base directory * @return The classes * @throws ClassNotFoundException */ private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException { List<Class> classes = new ArrayList<Class>(); if (!directory.exists()) { return classes; } File[] files = directory.listFiles(); for (File file : files) { if (file.isDirectory()) { assert !file.getName().contains( "." ); classes.addAll(findClasses(file, packageName + "." + file.getName())); } else if (file.getName().endsWith( ".class" )) { classes.add(Class.forName(packageName + '.' + file.getName().substring( 0 , file.getName().length() - 6 ))); } } return classes; } } |
3、编译时检查:如 @SuppressWarnings,如有些废弃的方法调用之后会有警告信息,你可以使用 @SuppressWarnings 注解:@SuppressWarnings("deprecation"),这样编译的时候就不会产生警告信息了。
注解定义
注解定义的方法类似接口定义 (上面有例子了),只是在 interface 关键字前面多了个 @符号,注解定义上面也还可以有其他注解(下面详说),注解体里面的元素基本格式都一致,类型名称 + 变量名 + 括号 + default xxx; 默认值的提供是可选的。类型可以是普通类型,也可以是类啊、异常啊这些。
接下来要说的发图吧,比较清晰:
注解分类:
1、普通注解,如:@Deprecated、@Override、@SuppressWarnings、@SafeVarargs 等
2、注解其他注解的注解 (比较拗口):记重点了,比较关键的是 @Retention、@Target
别的不说,先发图,看图比较清晰:
先说 @Retention,这个很重要,如果我们要在运行时使用定义的注解,一定不要写错了,默认是不会到运行时还存在的
使用方法:在注解定义处使用:
1 2 3 4 | @Retention (RetentionPolicy.SOURCE) public @interface ClassPreamble { String author(); } |
有三个可选的选项:(默认是 RetentionPolicy.CLASS)
1、RetentionPolicy.SOURCE,如果使用该选项,我们的注解只会在源代码存在,编译之后就没有了,我们去看编译之后的 class 文件,会发现注解已经不存在了。但是如果我们不写 @Retention 的话,编译后的 class 文件还会有该注解,因为默认是 RetentionPolicy.CLASS。
2、RetentionPolicy.CLASS(默认),使用该选项,我们的注解会在源代码和 class 文件中存在
3、RetentionPolicy.RUNTIME,使用该选项,我们的注解会一直存在,我们可以在运行的时候去获取注解信息,然后做相应处理。
@Document 这个注解会在 javadoc 生成的文档中出现
@Target,这也算是一个比较重要的注解,指明了该注解可以用在什么地方,好比如,你指定了一个注解的 Target 为 ElementType.METHOD,那么这个注解只能用在方法上,而不能用在 field 或者 type 上,当然你也可以同时指定多个,如:
1 | @Target ({ElementType.FIELD, ElementType.METHOD}) |
可用类型如上图。
@Repeatable 指明该注解是否可以多次使用
好了,就这么多了,其他还不是太了解,上面只是看文档总结的东西,
其实最重要的还是 @Retention,如果要在运行时也用到注解信息,那么必须指定 Retention.RUNTIME 参数,否则通过反射去获取注解是获取不到的。
与此相关的另外的知识就是反射了,有空再补充。