先引用一下百度百科的名词解析:
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
①编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
下面开始正文:
概述:
就如上面所说,注解有几种用途,第一种是编写文档,这种注解会在javadoc中存在;
第二种是代码分析,这需要使用 反射 的api去解析注解;
第三种类是提供编译时的检查,如 deprecated 这些,编译的时候会产生警告信息。
举几个例子:
1、下面的例子,注解使用了@Documented注解,这意味着,我们的注解会在 javadoc 生成的文档中出现,另外一个@Retention的用途下面会说到
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(); }
使用:
@ClassPreamble( author = "ruby" ) public class Main ...
2、代码分析(我们拙劣地模拟一下Spring Boot的路由注解)
代码目录结构:
RequestMapping注解定义:
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控制器定义:
package com.ruby.controller; import com.ruby.annotation.RequestMapping; public class Home { @RequestMapping("test") public void HelloWorld() { System.out.println("HelloWorld !"); } }
使用方法(命名不是太规范,test一般用于测试文件命名):关键是main方法里面通过反射去解析注解的代码,其他两个方法只是工具方法(用以获取包下面所有类)
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,这个很重要,如果我们要在运行时使用定义的注解,一定不要写错了,默认是不会到运行时还存在的
使用方法:在注解定义处使用:
@Retention(RetentionPolicy.SOURCE) public @interface ClassPreamble { String author(); }
有三个可选的选项:(默认是RetentionPolicy.CLASS)
1、RetentionPolicy.SOURCE,如果使用该选项,我们的注解只会在源代码存在,编译之后就没有了,我们去看编译之后的 class 文件,会发现注解已经不存在了。但是如果我们不写@Retention 的话,编译后的 class 文件还会有该注解,因为默认是RetentionPolicy.CLASS。
2、RetentionPolicy.CLASS(默认),使用该选项,我们的注解会在源代码和class文件中存在