十、注解
一、简介
**Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。**注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
注解(Annotations)是Java中的一种元数据提供机制,它可以在代码中标记类、方法、变量、参数等,从而**在编译时或运行时为代码提供额外的信息。**注解不会直接影响代码的执行逻辑,但可以通过编译器或框架工具进行解析和处理,从而实现自动配置、代码生成、验证等功能。
注解在Java 5中引入,随着Java的发展,注解的使用变得越来越广泛,尤其是在框架如Spring等中,注解大大简化了开发工作。
二、自定义注解
Java允许开发者创建自己的注解,通常用于标记特定的行为或属性。自定义注解可以通过元注解(meta-annotations)来定义其行为。常用的元注解包括:
- @Target:指定注解可以应用的程序元素,如类、方法、字段等。
- @Retention:指定注解的生命周期,是只在源码中保留,还是编译后存在,或者在运行时依然有效。
- @Inherited:指定注解是否可以被子类继承。
- @Documented:指示注解是否应当包含在JavaDoc中。
例如,创建一个自定义注解@MyAnnotation
:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "default value";
}
三、工作中的应用
1.注解简介
注解(Annotations)是Java中的一种元数据提供机制,它可以在代码中标记类、方法、变量、参数等,从而在编译时或运行时为代码提供额外的信息。注解不会直接影响代码的执行逻辑,但可以通过编译器或框架工具进行解释和处理,从而实现自动化配置、代码生成、验证等功能。
注解在Java 5中引入,随着Java的发展,注解的使用变得越来越广泛,尤其是在框架如Spring、Hibernate等中,注解大大简化了开发工作。
注解的本质是一个继承了Annotation
的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代码对象$Proxy1
。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler
的invoke
方法。该方法会从memberValues
这个Map中索引出对应的值。而memberValues
的来源是Java常量池。
2.自定义注解
Java允许开发者创建自己的注解,通常用于标记特定的行为或属性。自定义注解可以通过元注解(meta-annotations)来定义其行为。常用的元注解包括:
- @Target:指定注解可以应用的程序元素,如类、方法、字段等。
- @Retention:指定注解的生命周期,是只在源码中保留,还是编译后存在,或者在运行时依然有效。
- @Inherited:指示注解是否可以被子类继承。
- @Documented:指示注解是否应当包含在JavaDoc中。
例如,创建一个自定义注解@MyAnnotation
:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "default value";
}
3.实际工作中常见的注解
在实际工作中,尤其是使用Spring等框架时,以下注解非常常见:
- @Override
标记方法是重写父类或接口的方法,编译器会对此进行检查,确保该方法确实覆盖了父类的方法。 - @Deprecated
标记某个类、方法或字段已经不再推荐使用,通常会提供替代方案。 - @SuppressWarnings
抑制编译器的警告信息。常用于抑制类型安全相关的警告,如未检查的类型转换。 - @Entity (JPA)
用于标记一个类是JPA实体类,与数据库中的表对应。 - @Autowired (Spring)
用于自动注入Spring容器中的Bean。Spring会根据类型自动寻找匹配的Bean,并将其注入到需要的地方。 - @RequestMapping (Spring MVC)
用于映射HTTP请求到特定的控制器方法。可以指定请求路径、方法类型(GET、POST等)等信息。 - @Transactional (Spring)
声明事务管理,Spring会根据配置自动管理数据库事务的提交和回滚。 - @Configuration (Spring)
用于标记配置类,Spring会将其视为XML配置文件的替代,允许在类中定义Bean。 - @RestController (Spring)
是@Controller
和@ResponseBody
的组合注解,用于处理RESTful Web服务,直接返回JSON或XML等数据格式。
4.举例
自定义注解类编写的一些规则
Annotation
型定义为@interface
,所有的Annotation
会自动继承java.lang.Annotation这一接口,并且不能再继承别的类或是接口。- 参数成员**只能用
public
或默认(default
)**这两个访问权修饰。 - 参数成员只能用基本类型
byte
、short
、char
、int
、long
、float
、double
、boolean
八种基本类型和String
、Enum
、Class
、annotations
等数据类型,以及这一些类型的数组。 - 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取
Annotation
对象,因为你除此之外没有别的获取注解对象的方法。 - 注解也可以没有定义成员,不过这样的注解就没啥用了。
注:自定义注解需要使用到元注解。
自定义注解实例
FruitName.java
/**
* FruitName是一个用于标识水果名称的注解接口
* 该注解用于标记一个字段,表示该字段存储了水果的名称
* 通过此注解,可以在运行时获取到被注解的字段上的水果名称信息
*
* @author LiHao
* @see ElementType#FIELD 注解的目标是字段
* @see RetentionPolicy#RUNTIME 注解的保留策略为运行时,可以在运行时被读取
* @see Documented 将该注解包含进API文档中
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
/**
* 定义注解的属性value,用于指定默认的水果名称
* 如果未指定具体的水果名称,则默认值为空字符串
*
* @return 默认的水果名称
*/
String value() default "";
}
FruitColor.java
import java.lang.annotation.*;
/**
* @author: LiHao
* @description: 水果颜色注解
* thinking:这是一个自定义注解FruitColor,用于表示水果的颜色。它具有以下特点:
* 1、使用@Target(ElementType.FIELD)指定该注解只能用于字段。
* 2、使用@Retention(RetentionPolicy.RUNTIME)指定该注解在运行时可见。
* 3、使用@Documented指定该注解应被生成到API文档中。
* 4、该注解包含一个名为fruitColor的方法,用于指定水果的颜色,默认值为GREEN。
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
*/
public enum Color {
BLUE, RED, GREEN
}
/**
* 颜色属性
*
* @return 默认颜色为GREEN
*/
Color fruitColor() default Color.GREEN;
}
FruitProvider.java
import java.lang.annotation.*;
/**
* @author: LiHao
* @description: 水果供应者注解
* thinking:此Java元注解定义了一个名为FruitProvider的注解,可用于字段上,并可在运行时反射获取。它包含:
* 1、默认编号-1的id;
* 2、默认为空字符串的name;
* 3、默认为空字符串的address。 这些信息可通过Javadoc访问。
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 获取供应商编号
*
* @return 供应商编号,默认为-1
*/
public int id() default -1;
/**
* 获取供应商名称
*
* @return 供应商名称,默认为空字符串
*/
public String name() default "";
/**
* 获取供应商地址
*
* @return 供应商地址,默认为空字符串
*/
public String address() default "";
}
FruitInfoUtil.java
import java.lang.reflect.Field;
/**
* @author: LiHao
* @description: 注解处理器
* thinking:该方法getFruitInfo通过反射获取传入类的所有字段,并检查字段上是否存在特定注解:
* 1、若存在@FruitName注解,则输出水果名称。
* 2、若存在@FruitColor注解,则输出水果颜色。
* 3、若存在@FruitProvider注解,则输出供应商信息,包括编号、名称和地址。
*/
/**
* FruitInfoUtil类用于提取水果相关信息
* 通过使用反射机制,本类的方法能够从动态传入的类中提取关于水果名称、颜色和供应商的信息
*
*/
public class FruitInfoUtil {
/**
* 从给定类中提取水果相关信息
* 本方法通过检查类的字段上是否标注有特定的注解(FruitName, FruitColor, FruitProvider),
* 来决定需要提取哪些信息,并将这些信息打印出来
*
* @param clazz 一个Class对象,表示从哪个类中提取水果信息
*/
public static void getFruitInfo(Class<?> clazz) {
// 初始化用于存储水果名称、颜色和供应商信息的字符串
String strFruitName = " 水果名称:";
String strFruitColor = " 水果颜色:";
String strFruitProvider = "供应商信息:";
// 获取传入类的所有字段
Field[] fields = clazz.getDeclaredFields();
// 遍历所有字段,检查并提取标注了特定注解的字段信息
for (Field field : fields) {
// 检查字段是否标注了FruitName注解
if (field.isAnnotationPresent(FruitName.class)) {
// 如果有,提取并打印水果名称
FruitName fruitName = field.getAnnotation(FruitName.class);
strFruitName += fruitName.value();
System.out.println(strFruitName);
} else if (field.isAnnotationPresent(FruitColor.class)) {
// 检查字段是否标注了FruitColor注解
// 如果有,提取并打印水果颜色
FruitColor fruitColor = field.getAnnotation(FruitColor.class);
strFruitColor = strFruitColor + fruitColor.fruitColor();
System.out.println(strFruitColor);
} else if (field.isAnnotationPresent(FruitProvider.class)) {
// 检查字段是否标注了FruitProvider注解
// 如果有,提取并打印供应商信息
FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);
strFruitProvider = strFruitProvider + " 供应商编号:" + fruitProvider.id() + " 供应商名称:" + fruitProvider.name() + " 供应商地址:" + fruitProvider.address();
System.out.println(strFruitProvider);
}
}
}
}
Apple.java
import lombok.Data;
/**
* @author: LiHao
* @description: 注解使用
* thinking:这段 Java 代码定义了一个名为 Apple 的类,并使用了 Lombok 库的 @Data 注解来自动生成 getter、setter、equals、hashCode 和 toString 方法。
* 1、@FruitName("Apple") 标记 appleName 字段,表示其名称总是 "Apple"。
* 2、@FruitColor(fruitColor = FruitColor.Color.RED) 标记 appleColor 字段,表示苹果的颜色是红色。
* 3、@FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路89号红富士大厦") 标记 appleProvider 字段,表示供应商的信息。
* 4、此外,displayName 方法用于输出一条消息,表明这种水果的名字是苹果。注意这里 @FruitName、@FruitColor 和 @FruitProvider 需要对应的注解处理器支持或是在运行时进行额外的处理。
*/
/**
* Apple类代表了一个苹果实体,包含了苹果的名称、颜色和供应商信息
*/
@Data
public class Apple {
/**
* 苹果的名称
*/
@FruitName("Apple")
private String appleName;
/**
* 苹果的颜色,限定为红色
*/
@FruitColor(fruitColor = FruitColor.Color.RED)
private String appleColor;
/**
* 苹果的供应商信息,包括供应商ID、名称和地址
*/
@FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路89号红富士大厦")
private String appleProvider;
/**
* 显示苹果的名称
* 通过打印固定格式的字符串,来展示水果的名字
*/
public void displayName() {
System.out.println("水果的名字是:苹果");
}
}
FruitRun.java
/**
* @author: LiHao
* @description: 输出结果
* thinking:
*/
public class FruitRun {
public static void main(String[] args) {
// 调用FruitInfoUtil工具类的getFruitInfo方法,传入Apple类作为参数
// 目的是获取关于Apple这种水果的相关信息
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
输出结果
水果名称:Apple
水果颜色:RED
供应商信息: 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦
四、总结
注解的看法与实际应用
注解在Java开发中起到了非常重要的作用,尤其是在配置和元数据处理方面。注解的使用可以使代码更加简洁、可读和易于维护。
但需要注意的是,过度使用注解可能导致代码难以理解和调试,因此在实际工作中应适度使用注解,并配合良好的文档和代码规范,确保代码的可维护性。
此外,注解与反射机制紧密相关,过多依赖注解可能会导致性能问题,尤其是在处理大量注解时。因此开发者在设计系统时,需要在灵活性和性能之间找到平衡。