注解是JDK1.5引入的新特性,用于对代码进行说明。注解是一种元数据,用于对代码进行说明,可以对包、类、接口、字段、方法、参数、局部变量等进行注解,可以理解为一种特殊的注释。这个注释是用来给程序看的。
注释是写给人看的,注解是写给程序看的。
在没有注解之前,各种框架中几乎所有的配置以XML方式进行配置,因为XML方式可以降低配置和代码的耦合度,但是随着项目越来越庞大,XML的内容越来越复杂,维护成本变高。所有就有人提出了一种标记式的高耦合的配置方式,这是方式就是注解
注解:与源代码紧绑定,耦合度高,但它便捷,易于维护修改。
XML:与源代码无绑定,耦合度低,更容易扩展,但XML内容复杂,不易维护。
各有优劣!!!
生成文档:通过代码里的标识的元数据生成javadoc文档。
编译检查:通过代码里的标识的元数据让编译器在编译期间进行检查验证。
编译动态处理:编译时通过代码里的标识的元数据动态处理,比如说用作动态代码生成。
运行动态处理:运行时通过代码里标识的元数据动态处理,比如说使用反射注入实例。
Java提供了3种注解。
元注解:用于定义注解的注解,指定某个注解的生命周期以及作用目标等信息。
public enum ElementType {
/*类、接口(包括注解类型)或枚举声明*/
TYPE,
/*字段,包括枚举常量*/
FIELD,
/*方法*/
METHOD,
/*形式参数*/
PARAMETER,
/*构造函数*/
CONSTRUCTOR,
/*局部变量*/
LOCAL_VARIABLE,
/*注解*/
ANNOTATION_TYPE,
/*包*/
PACKAGE,
/**
* 表示该注解能写在类型变量的声明语句中(如:泛型声明)
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 表示该注解能写在使用类型的任何语句中
*
* @since 1.8
*/
TYPE_USE
}
public enum RetentionPolicy {
//源码(只能再编译期可加,编译后会被丢弃)
/*注解将会被编译器丢弃*/
SOURCE,
//字节码(会被编译器编译进class文件中,类加载时会被丢弃)-----默认值
/*注解由编译器记录在类文件中,但不需要在运行时由JVM保留*/
CLASS,
//运行期(永久保存)
/*注解由编译器记录在类文件中,运行时由JVM保留,可以通过反射来读取到它们*/
RUNTIME
}
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 "";
}
1.借助Spring提供的工具包
首先引入4个jar包:
代码如下:
/**
* 借助Sping的工具类,扫描指定包,根据的bean定义获取包下的所有的Class对象
* @param packName 包路径
* @return
* @throws ClassNotFoundException
*/
public static Class[] getAllClass1(String packName) throws ClassNotFoundException {
/*
* ClassPathScanningCandidateComponentProvider是Spring提供的工具,可以按照自定义的类型,查找ClassPath下符合要求的class文件
* useDefaultFilters:
* true表示使用默认的typeFileter,会查出来很多自己不想要的class
* false表示不使用默认的typeFileter
*/
ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider =
new ClassPathScanningCandidateComponentProvider(false);
//设置自定义的typeFilter
classPathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(MyAnnotion.class));
//根据自定义的typeFilter,在指定的包下获取满足条件的bean的定义
Set<BeanDefinition> beanDefinitions = 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包:
代码如下:
/**
* 借助reflections反射工具包,扫描指定包,获取包下所有的Class对象
* @param packName 包路径
* @return
*/
public static Class[] getAllClass2(String packName) {
Reflections reflections = new Reflections(PATH);
Set<Class<?>> 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即可
代码如下:
/**
* 自己实现:获取指定包路径下的所有类的Class对象
* @param packName 包路径
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
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;
}
待补充
首先定义一个注解的关键字为"@interface",格式如下:
public @interface 注解名称{
属性列表;
}
首先我们先按照注解的定义创建一个注解MyAnnotation.java,编译之后得到MyAnnotation.class文件
然后再用javap -c MyAnnotation.class命令进行反编译,结果如下
可以看出反编译之后的注解其实就是一个接口,它继承了Annotation接口。
没错,注解的本质就是一个继承了Annotation接口的接口。
解析一个类或者方法的注解有两种方式,一种是编译器的直接扫描,一种是运行期反射。
编译器的扫描指的是编译器在对Java代码编译成字节码的过程中检测到某个类或方法被一下注解修饰,这时会对这些注解进行处理。
运行期反射将会在下面进行详细说明
对于一个类或者接口来说,Class类对象中提供了以下方法用于反射注解。
首先