文章目录
前言
在开发的过程中,经常会见到一些注解,有时候还需要自定义一些注解来达到我们所期望的效果。那么什么是注解?为什么要使用注解?怎样使用注解?我们今天就来探讨一下。
一、什么是注解?
1. 简介
Java 注解是在 JDK5 时引入的新特性,注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。注解类型定义指定了一种新的类型,一种特殊的接口类型。 在关键词 interface 前加 @ 符号也就是用 @interface 来区分注解的定义和普通的接口声明。目前大部分框架(如 Spring Boot 等)都通过使用注解简化了代码并提高的编码效率。
2. 一些常见的注解
- 标识该方法继承自超类的注解:@Override
- Junit测试的注解:@Test
- Spring的一些注解:@Controller、@RequestMapping、@RequestParam、@ResponseBody、@Service、@Component
- Java验证的注解:@NotNull、@Email
二、为什么要使用注解?
1.XML 与 Annotation
使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。
但随着时间的推移,一些应用开发人员和架构师发现XML的维护变得越来越困难,他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。
而最有趣的是XML配置其实就是为了分离代码和配置而引入的,这两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。
假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。
另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。
目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。
2.XML 的优缺点
目前 web 应用中几乎都使用 xml 作为配置项,xml 之所以这么流行,是因为它的很多优点是其它技术的配置所无法替代的。
XML 的优点:
- xml 作为可扩展标记语言最大的优势在于开发者能够为软件量身定制适用的标记,使代码更加通俗易懂。
- 利用 xml 配置能使软件更具扩展性。例如 Spring 将 class 间的依赖配置在 xml 中,最大限度地提升应用的可扩展性。
- 具有成熟的验证机制确保程序正确性。利用 Schema 或 DTD 可以对 xml 的正确性进行验证,避免了非法的配置导致应用程序出错。
- 修改配置而无需变动现有程序。
虽然有如此多的好处,但毕竟没有什么万能的东西,xml 也有自身的缺点。
XML 的缺点:
- 需要解析工具或类库的支持。
- 解析 xml 势必会影响应用程序性能,占用系统资源。
- 配置文件过多导致管理变得困难。
- 编译期无法对其配置项的正确性进行验证,或要查错只能在运行期。
- IDE 无法验证配置项的正确性。
- 查错变得困难,往往配置的一个手误导致莫名其妙的错误。
- 开发人员不得不同时维护代码和配置文件,开发效率变得低下。
- 配置项与代码间存在潜规则。改变了任何一方都有可能影响另外一方。
3.Annotation 的优缺点
Annotation 的优点:
- 保存在 class 文件中,降低维护成本。
- 无需工具支持,无需解析。
- 编译期即可验证正确性,查错变得容易。
- 提升开发效率。
Annotation 的缺点:
- 若要对配置项进行修改,不得不修改 Java 文件,重新编译打包应用。
- 配置项编码在 Java 文件中,可扩展性差。
三、怎样使用自定义注解?
1.元注解
在创建注解的时候,需要使用一些注解来描述自己创建的注解,就是写在@interface上面的那些注解,这些注解被称为元注解,如在Override中看到的@Target、@Retention等
下面列出一些注解
- Documented:用于标记在生成javadoc时是否将注解包含进去
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
- Target:用于定义注解可以在什么地方使用,默认可以在任何地方使用,也可以指定使用的范围,开发中将注解用在类上(如@Controller)、字段上(如@Autowire)、方法上(如@RequestMapping)、方法的参数上(如@RequestParam)等比较常见。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
在ElementType中可以看到参数可以取的值
参数 | 作用 |
---|---|
TYPE | 类、接口或enum声明 |
FIELD | 域(属性)声明 |
METHOD | 方法声明 |
PARAMETER | 参数声明 |
CONSTRUCTOR | 构造方法声明 |
LOCAL_VARIABLE | 局部变量声明 |
ANNOTATION_TYPE | 注释类型声明 |
PACKAGE | 包声明 |
- @Inherited:允许子类继承父类中的注解,可以通过反射获取到父类的注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
- @Native:可以被生成本机头文件的工具用作提示,以确定是否需要头文件,如果需要,它应该包含哪些声明。
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}
- @Repeatable:用于指示其声明的批注类型注释是可重复的。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}
- @Retention:注解的声明周期,用于定义注解的存活阶段,可以存活在源码级别、编译级别(字节码级别)、运行时级别
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
参数 | 作用 |
---|---|
SOURCE | 源码级别,注解只存在源码中,一般用于和编译器交互,用于检测代码。如@Override, @SuppressWarings。 |
CLASS | 字节码级别,注解存在于源码和字节码文件中,主要用于编译时生成额外的文件,如XML,Java文件等,但运行时无法获得。 如mybatis生成实体和映射文件,这个级别需要添加JVM加载时候的代理(javaagent),使用代理来动态修改字节码文件。 |
RUNTIME | 运行时级别,注解存在于源码、字节码、java虚拟机中,主要用于运行时,可以使用反射获取相关的信息。 |
2.注解的内容
在上面的注解源码中可以看到有的注解中没有任何内容,有的注解的有内容,看似像方法。
注解的内容的语法格式: 数据类型 属性名() default 默认值,数据类型用于描述属性的数据类型,默认值是说当没有给属性赋值时使用默认值,一般String使用空字符串""作为默认值,数组一般使用空数组{ }作为默认值.
SpringMVC中的RequestMapping的注解的声明
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
3.自定义注解
- 创建Maven项目,项目结构如下:
- pom.xml中添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AnnotationDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<!--依赖-->
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--reflections 依赖-->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
</dependencies>
</project>
- 编写IAnimal注解
@Target({ElementType.TYPE,ElementType.METHOD})// TYPE --> @IAnimal注解可以放在类上, METHOD --> @IAnimal注解也可以放在方法上
@Retention(RetentionPolicy.RUNTIME)//项目运行时 启用注解
public @interface IAnimal {
//vaule 默认为空
String value() default "";
}
- 编写Cat类调用注解
@IAnimal(value = "这是一个猫类")
public class Cat {
@IAnimal(value = "这是猫说话的方式")
public void say(){
System.out.println("喵喵喵~~~");
}
}
- 测试
public class AnnotationTest {
@Test
public void test1(){
//使用reflections 快速读取注解
//创建reflections对象 并 扫描 前缀为 animal的文件下所有类
Reflections reflections = new Reflections("animal");
//读取 IAnimal 注解
Set<Class<?>> clazzs = reflections.getTypesAnnotatedWith(IAnimal.class);
//遍历所有使用 @IAnimal注解 的类
for (Class<?> aClass : clazzs) {
//获取当前使用注解的类
IAnimal annotation = aClass.getAnnotation(IAnimal.class);
System.out.println("类名:"+aClass.getSimpleName()+" 注解值: "+ annotation.value() );
//遍历所有使用 @IAnimal注解 的方法
for (Method method : aClass.getMethods()) {
IAnimal annotation1 = method.getAnnotation(IAnimal.class);
//如果方法 没使用 注解 则跳过
if(annotation1 != null){
System.out.println("方法名:"+method.getName()+" 注解值: "+annotation1.value());
}
}
}
}
}
- 运行结果
总结
注解Annotation能够极大程度上方便我们的开发,我们应该灵活地去使用它。