注解是JDK1.5引入的新特性,用于对代码进行说明。注解是一种元数据,用于对代码进行说明,可以对包、类、接口、字段、方法、参数、局部变量等进行注解,可以理解为一种特殊的注释。这个注释是用来给程序看的。
注释是写给人看的,注解是写给程序看的。
为什么要使用注解在没有注解之前,各种框架中几乎所有的配置以XML方式进行配置,因为XML方式可以降低配置和代码的耦合度,但是随着项目越来越庞大,XML的内容越来越复杂,维护成本变高。所有就有人提出了一种标记式的高耦合的配置方式,这是方式就是注解
注解:与源代码紧绑定,耦合度高,但它便捷,易于维护修改。
XML:与源代码无绑定,耦合度低,更容易扩展,但XML内容复杂,不易维护。
各有优劣!!!
注解作用生成文档:通过代码里的标识的元数据生成javadoc文档。
编译检查:通过代码里的标识的元数据让编译器在编译期间进行检查验证。
编译动态处理:编译时通过代码里的标识的元数据动态处理,比如说用作动态代码生成。
运行动态处理:运行时通过代码里标识的元数据动态处理,比如说使用反射注入实例。
Java提供了3种注解。
元注解:用于定义注解的注解,指定某个注解的生命周期以及作用目标等信息。
- @Target 标明注解使用的范围(作用的目标),有以下取值范围
public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE }
- @Retention 表明注解被保留的阶段,有以下取值范围
public enum RetentionPolicy { //源码(只能再编译期可加,编译后会被丢弃) SOURCE, //字节码(会被编译器编译进class文件中,类加载时会被丢弃)-----默认值 CLASS, //运行期(永久保存) RUNTIME }
- @Documented 被标识的注解,在执行javadoc文档打包时会被保存进doc文档。
- @Inherited 标明注解可继承,也就是说我们的注解修饰了一个类,该类的子类将自动继承父类的该注解。
Java自带标准注解:
@Override 标明重写某个方法,编译器在对Java文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹配对父类中是否具有一个同样方法签名的函数,如果没有,则不能通过编译。
@Deprecated 标明某个类或方法过时
@SuppressWarnings 标明要忽略的警告
**自定义注解:**可以根据自己的需求定义注解
注解实现实现方式有基于Spring框架和不基于Spring框架两种方式。
不管采用哪种方式,都是按照以下步骤实现和使用注解的
1. 声明注解
2. 实现注解处理器
借助反射,获取注解对象(通常是Class对象,Method对象,Field对象,Constructor对象,Annotation对象),读取注解的属性值,然后根据注解及其属性的值做相应处理。
在这一步骤中最重要的是获取指定包路径下的所有的Class对象,只要获取到Class对象,其他对象都可以获取到,就可以任意操作。
3. 使用注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotion { String value() default ""; }不基于Spring框架获取Class对象:
1.借助Spring提供的工具包
首先引入4个jar包:
代码如下:
public static Class[] getAllClass1(String packName) throws ClassNotFoundException { ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false); //设置自定义的typeFilter classPathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(MyAnnotion.class)); //根据自定义的typeFilter,在指定的包下获取满足条件的bean的定义 SetbeanDefinitions = classPathScanningCandidateComponentProvider.findCandidateComponents(packName); if (beanDefinitions.isEmpty()) { return null; } Class[] clazzes = new Class[beanDefinitions.size()]; //根据bean的定义获取bean的名称,然后通过反射获取bean的class对象,将class对象放入数组中 int i = 0; for (BeanDefinition beanDefinition : beanDefinitions) { Class clazz = Class.forName(beanDefinition.getBeanClassName()); if (Objects.isNull(clazz)) { continue; } clazzes[i++] = clazz; } return clazzes; }
2.借助reflections反射工具包
首先引入3个jar包:
代码如下:
public static Class[] getAllClass2(String packName) { Reflections reflections = new Reflections(PATH); Set> typesAnnotatedWith = reflections.getTypesAnnotatedWith(MyAnnotion.class); Class[] classes = new Class[typesAnnotatedWith.size()]; int i = 0; for (Class c : typesAnnotatedWith) { classes[i++] = c; } return classes; }
3.自己实现(采用JDK的API)
只需依赖JDK 1.8即可
代码如下:
public static Class[] getAllClass3(String packName) throws IOException, ClassNotFoundException { URL resource = Thread.currentThread().getContextClassLoader().getResource(packName.replace('.', '/')); String[] files = new File(resource.getFile()).list(); Class[] clazzs = new Class[files.length]; for (int i = 0; i < files.length; i++) { clazzs[i] = Class.forName(packName + "." + files[i].replace(".class", "")); } return clazzs; }不基于Spring框架获取Class对象:
待补充
注解本质(实现原理)首先定义一个注解的关键字为"@interface",格式如下:
public @interface 注解名称{ 属性列表; }
首先我们先按照注解的定义创建一个注解MyAnnotation.java,编译之后得到MyAnnotation.class文件
然后再用javap -c MyAnnotation.class命令进行反编译,结果如下
可以看出反编译之后的注解其实就是一个接口,它继承了Annotation接口。
没错,注解的本质就是一个继承了Annotation接口的接口。
解析一个类或者方法的注解有两种方式,一种是编译器的直接扫描,一种是运行期反射。
编译器的扫描指的是编译器在对Java代码编译成字节码的过程中检测到某个类或方法被一下注解修饰,这时会对这些注解进行处理。
运行期反射将会在下面进行详细说明
对于一个类或者接口来说,Class类对象中提供了以下方法用于反射注解。
- getAnnotation:返回指定的注解
- isAnnotationPresent:判断当前元素是否被指定注解修饰
- getAnnotations:返回所有的注解
- getDeclaredAnnotation:返回本元素的指定注解
- getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的。
首先