【Java 注解】Java注解-最通俗易懂的讲解

【Java 注解】Java注解-最通俗易懂的讲解

原文:https://blog.csdn.net/weixin_43888891/article/details/126963074

====================================================

一、概念

1.1. 什么是注解?

Java注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:

① 编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】

② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】

③ 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】

注解是以‘@注解名’在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(用来描述数据的数据)的访问。

首先一定要明白:注解他本身是没有任何作用的,比如@RequestMapping,在controller层@RequestMapping基本上可以说是必见的,我们都知道他的作用是请求映射,通过他来设置请求的url地址,

举例:将@RequestMapping("config")写在方法上,然后我们就可以通过url地址访问到这个方法了,但是记住这并不是@RequestMapping注解的功能,SpringMVC会通过反射将注解当中设置的属性值config拿到,然后将url和你声明注解的方法进行绑定。

记住:注解只是用来标记,而这个注解真正的功能都是由框架通过反射来实现的。

1.2. 什么是元数据?

元数据是一个非常广泛的概念,元数据的定义也非常简单,只要是描述数据的数据都是元数据。很简单,一个数字170,单看数据你肯定不知道这个数据代表什么,这就需要元数据支持,你才能明白数据代表的事实是什么。它可能是一个人的身高,也可能指一个人的体重,这需要数据对应的标题、单位等信息来描述这个数据,这些就是描述这个数据的元数据了

1.3. 注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface TestAnnotation {

int id();

String msg();

}

上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。

赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开。

@TestAnnotation(id=3,msg="hello annotation")

public class Test {

}

需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface TestAnnotation {

public int id() default -1;

public String msg() default "Hi";

}

TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。

二、根据【注解参数】 分类

根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。

2.1. 标记注解

标记注解不包含成员/元素。它仅用于标记声明。

其语法为:@AnnotationName()

由于这些注解不包含元素,因此不需要括号。例如:@Override

2.2. 单元素注解

单个元素注解仅包含一个元素。

其语法为:@AnnotationName(elementName = "elementValue")

如果只有一个元素,则习惯上将该元素命名为value:@AnnotationName(value = "elementValue")在这种情况下,也可以移除元素名称。元素名称默认为value:@AnnotationName("elementValue")

2.3. 多元素注解

这些注解包含多个用逗号分隔的元素。

其语法为:@AnnotationName(element1 = "value1", element2 = "value2")

三、根据【注解作用】分类

3.1. 预定义的注解

这几个注解都是java.lang包下的,也就是Java提供的基础注解,我们在使用的时候是不需要导包的!

3.1.1. @Deprecated

此注解可以用在方法,属性,类上,表示不推荐程序员使用,但是还可以使用,示例如下:

/**

* 测试Deprecated注解

* @author Administrator

*/

public class DeprecatedDemoTest {

public static void main(String[]args) {

// 使用DeprecatedClass里声明被过时的方法

DeprecatedClass.DeprecatedMethod();

}

}

class DeprecatedClass {

@Deprecated

public static void DeprecatedMethod() {

}

}

3.1.2. @Override

它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。

public interface Test {

public String getStr();

}

class TestImpl implements Test{

// 假如返回参数和方法参数其中一个不一致,就会警告

@Override

public String getStr() {

return null;

}

}

3.1.3. @SuppressWarnings

我们在写代码的时候,不论是导入的包,还是声明的对象,有时候会出现黄线,感觉就很难受!

@SuppressWarnings注解主要用在取消一些编译器产生的警告,警告对于运行代码实际上并没有影响,但是出于部分程序员具有洁癖的嗜好,通常会采用@SuppressWarnings来消除警告。

示例一:警告如图所示

这只是一个service接口:

public interface BannerService {

}

这时候我们在方法上加上@SuppressWarnings注解就可以消除这些警告的产生,注解的使用方式有三种:

@SuppressWarnings(“unchecked”) [^ 抑制单类型的警告] @SuppressWarnings(“unchecked”,“rawtypes”) [^ 抑制多类型的警告] @SuppressWarnings(“all”) [^ 抑制所有类型的警告]

通过源码分析可知@SuppressWarnings其注解目标为类、字段、函数、函数入参、构造函数和函数的局部变量。建议把注解放在警告发生的位置。

消除警告:

这个警告是spring framerwork 4.0以后开始出现的,spring 4.0开始就不推荐使用属性注入,改为推荐构造器注入和setter注入。当然他只是推荐,如果我们想要消除警告也可以直接使用@Resource。尽管他推荐了,但是一般实际开发当中很少会使用构造器注入和setter注入。

@Autowired 是Spring提供的,@Resource 是J2EE提供的。

@Autowired与@Resource都可以用来装配bean,都可以写在字段或setter方法上

@Autowired默认按类型装配,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用。

@Resource,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找。如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

示例二:警告如图所示

通过添加@SuppressWarnings("unchecked")来消除unchecked的警告,这个警告实际上主要是集合没有加泛型导致的!

3.1.4. @SafeVarargs

在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,这样的话,Java编译器就不会报unchecked警告。

使用前提:

①方法必须是由static或者final修饰的方法! ②只能用于标记构造函数和方法 ③只能用在可变长参数的方法上

@SafeVarargs

public static T useVarargs(T... args) {

return args.length > 0 ? args[0] : null;

}

3.1.5. @FunctionalInterface

被@FunctionalInterface注解标记的类型表明这是一个函数接口。从概念上讲,函数接口只有一个抽象方法。也就是一旦不满足函数接口,就会报错,比如他有两个抽象方法。

@FunctionalInterface注解,只能用于接口类。其实,它的应用范围更小,只能应用于接口类型。

@FunctionalInterface

public interface Test {

public String getStr();

}

3.2. 元注解

以下的注解都来源于java.lang.annotation,描述数据的数据都是元数据,描述注解的注解都是元注解!也就是这些注解只能用在修饰注解上,不能使用在其他地方,比如方法、类等等!

@Native注解除外,他的使用范围是FIELD(字段、枚举的常量),但是@Native也是在java.lang.annotation包下!

3.2.1. @Retention

注解按生命周期来划分可分为3类:

RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;也就是编译时有效。 RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;加载时被抛弃。 RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;一直有效!

lombok可以通过@Data注解省去get set 方法,实际上@Data的生命周期就是RetentionPolicy.SOURCE,他是通过注解来标记这个方法要生成get set方法,然后在编译的时候直接会生成get set。生成过后,就被抛弃了。

3.2.2. @Documented

@Documented和@Deprecated注解长得有点像,@Deprecated是用来标注某个类或者方法不建议再继续使用,@Documented只能用在注解上,如果一个注解@B,被@Documented标注,那么被@B修饰的类,生成文档时,会显示@B。如果@B没有被@Documented标注,最终生成的文档中就不会显示@B。这里的生成文档指的JavaDoc文档!

@Deprecated注解基本上所有框架自定义的注解都会添加,所谓javadoc其实就是JDK给我们提供的一个生成文档的工具!

由于篇幅问题,@Documented详解请看这篇文章:https://blog.csdn.net/weixin_43888891/article/details/126981711

或转发的地址:https://www.cnblogs.com/sxdcgaq8080/p/18668690

3.2.3. @Target

@Target:@Target只能用在注解上,指定修饰的注解的使用范围

@Target(ElementType.TYPE) —— 接口、类、枚举、注解 @Target(ElementType.FIELD) —— 字段、枚举的常量 @Target(ElementType.METHOD) —— 方法 @Target(ElementType.PARAMETER) —— 方法参数 @Target(ElementType.CONSTRUCTOR) —— 构造函数 @Target(ElementType.LOCAL_VARIABLE) —— 局部变量 @Target(ElementType.ANNOTATION_TYPE) —— 注解 @Target(ElementType.PACKAGE) —— 包

比如@Target({ElementType.TYPE, ElementType.METHOD}),就代表着@RequestMapping可以用在 接口、类、枚举、注解上、还可以用在方法上!

3.2.4. @Inherited

如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解。

当用了@Inherited修饰的注解的@Retention是RetentionPolicy.RUNTIME,则增强了继承性,在反射中可以获取得到

代码示例:

1.首先自定义一个注解使用@Inherited修饰,然后这个注解用来修饰到父类上

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Inherited

public @interface ATable {

public String name() default "";

}

2.这个注解带不带@Inherited都可以,我们主要用来修饰子类

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface BTable {

public String name() default "";

}

3.定义一个父类跟一个子类,然后在父类上用上我们自定义的@ATable注解

@ATable

public class Super {

public static void main(String[] args) {

Class clazz = Sub.class;

System.out.println("============================AnnotatedElement===========================");

// 获取自身和父级带有@Inherited修饰的注解。如果@ATable未加@Inherited修饰,则获取的只是自身的注解而无法获取父级修饰的@ATable注解

System.out.println(Arrays.toString(clazz.getAnnotations()));

System.out.println("------------------");

}

}

@BTable

class Sub extends Super {

}

4.这个是@ATable带有@Inherited修饰的 运行结果。

5.这个是没有用@Inherited修饰的 运行结果。

3.2.5. @Repeatable

@Repeatable 注解是用于声明其它类型注解的元注解,来表示这个声明的注解是可重复的。@Repeatable的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解。

代码示例:

1.自定义Value注解

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Repeatable(Values.class)

public @interface Value {

String value() default "value";

}

2.自定义Values 注解

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Values {

Value[] value();

}

其中,@Value注解上的元注解@Repeatable中的值,使用了@Values注解,@Values注解中包含的值类型是一个@Value注解的数组!这就解释了官方文档中@Repeatable中值的使用。

3.测试

import java.lang.annotation.Annotation;

import java.lang.reflect.Method;

import java.util.Arrays;

public class AnnotationClass {

@Value("hello")

@Value("world")

public void test(String var1, String var2) {

System.out.println(var1 + " " + var2);

}

public static void main(String[] args) {

Method[] methods = AnnotationClass.class.getMethods();

for (Method method : methods){

if (method.getName().equals("test")) {

Annotation[] annotations = method.getDeclaredAnnotations();

System.out.println(annotations.length);

System.out.println(method.getName() + " = " + Arrays.toString(annotations));

}

}

}

}

4.运行结果

5.假如Value注解没有使用@Repeatable修饰,编译器会报错,是不允许出现注解重复的

注意事项:

@Repeatable 所声明的注解,其元注解@Target的使用范围要比@Repeatable的值声明的注解中的@Target的范围要大或相同,否则编译器错误,显示@Repeatable值所声明的注解的元注解@Target不是@Repeatable声明的注解的@Target的子集 @Repeatable注解声明的注解的元注解@Retention的周期要比@Repeatable的值指向的注解的@Retention得周期要小或相同。

周期长度为 SOURCE(源码) < CLASS (字节码) < RUNTIME(运行)

3.2.6. @Native

@Native注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可!

3.3. 自定义注解

修饰符: 访问修饰符必须为public,不写默认为pubic;关键字: 关键字为@interface;注解名称: 注解名称为自定义注解的名称,使用时还会用到;注解内容: 注解中内容,对注解的描述。我们自定义一个注解,然后这个注解可以在entity当中的set方法上初始化值。只是一个简单案例,供大家参考学习!

第一步:自定义@Init注解

@Documented

@Inherited

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) //可以在字段、枚举的常量、方法

@Retention(RetentionPolicy.RUNTIME)

public @interface Init {

String value() default "";

}

第二步:创建User类

public class User {

private String name;

private String age;

public String getName() {

return name;

}

@Init("louis")

public User setName(String name) {

this.name = name;

return this;

}

public String getAge() {

return age;

}

@Init("22")

public User setAge(String age) {

this.age = age;

return this;

}

}

第三步:创建User的工厂类,所谓工厂类就是专门为了生产User对象

import java.lang.reflect.Method;

public class UserFactory {

public static User create() {

User user = new User();

// 获取User类中所有的方法(getDeclaredMethods也行)

Method[] methods = User.class.getMethods();

try {

for (Method method : methods) {

// 判断方法上是否存在Init注解,存在就返回true,否则返回false

if (method.isAnnotationPresent(Init.class)) {

// 此方法返回该元素的注解在此元素的指定注释类型(如果存在),否则返回null

Init init = method.getAnnotation(Init.class);

// 如果Method代表了一个方法 那么调用它的invoke就相当于执行了它代表的这个方法,在这里就是给set方法赋值

method.invoke(user, init.value());

}

}

} catch (Exception e) {

e.printStackTrace();

return null;

}

return user;

}

}

第四步:测试

public static void main(String[] args) {

User user = UserFactory.create();

user.setAge("30");

System.out.println(user.getName());

System.out.println(user.getAge());

}

第五步:运行结果,我们并没有设置User的name属性,只是在User类的 set方法 添加了一个注解。输出结果如下:

四、反射

JAVA机制反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java注解一旦离开了反射就什么都不是!!!

4.1. 反射常用到的API

1.只能拿到public方法(包括继承的类或接口的方法)

public Method[] getMethods()

2.getDeclaredMethods返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

public Method[] getDeclaredMethods()

3.在Class对象和Method对象是都存在isAnnotationPresent这个方法的,作用是判断它是否应用了某个注解

public boolean isAnnotationPresent(Class annotationClass) {}

4.通过 getAnnotation() 方法来获取 Annotation 对象。

public A getAnnotation(Class annotationClass) {}

5.或者是 getAnnotations() 方法,获取所有的注解

如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。

public Annotation[] getAnnotations() {}

6.获取到Annotation之后,就可以通过annotationType获取注解的class结构信息,有了class信息就可以获取注解的变量名,等等

Class annotationType();

7.getParameterAnnotations :返回表示按照声明顺序对此 Method 对象所表示方法的形参进行注释的那个数组的数组

public Annotation[][] getParameterAnnotations();

4.2. 读取类上的注解

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface TestAnnotation {

public int id() default -1;

public String msg() default "Hi";

}

import java.lang.reflect.Method;

@TestAnnotation(id = 222,msg = "awdawd")

public class Test {

public static void main(String[] args) {

Method[] declaredMethods1 = TestAnnotation.class.getDeclaredMethods();

for (Method meth : declaredMethods1) {

System.out.println("注解的变量名为:" + meth.getName());

}

// 首先判断Test类上是否使用了TestAnnotation注解

boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);

if (hasAnnotation) {

// 获取注解,这个相当于是真正的拿到注解了,只有获取到这个才能获取到注解当中设置的值

TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);

System.out.println("id:" + testAnnotation.id());

System.out.println("msg:" + testAnnotation.msg());

}

}

}

输出结果:

4.3. 读取方法上的注解

public class User {

private String name;

private String age;

public String getName() {

return name;

}

@Init("louis")

public User setName(String name) {

this.name = name;

return this;

}

public String getAge() {

return age;

}

@Init("22")

public User setAge(String age) {

this.age = age;

return this;

}

}

//读取注解信息

public class ReadAnnotationInfoTest {

public static void main(String[] args) throws Exception {

// 测试AnnotationTest类,得到此类的类对象

Class c = Class.forName("com.gzl.cn.springbootnacos.my.User");

// 获取该类所有声明的方法

Method[] methods = c.getDeclaredMethods();

// 声明注解集合

Annotation[] annotations;

// 遍历所有的方法得到各方法上面的注解信息

for (Method method : methods) {

// 获取每个方法上面所声明的所有注解信息

annotations = method.getDeclaredAnnotations();

System.out.println("方法名为:" + method.getName());

if (method.isAnnotationPresent(Init.class)){

Init annotation = method.getAnnotation(Init.class);

System.out.println("注解设置的value值为:" + annotation.value());

}

for (Annotation an : annotations) {

System.out.println("其上面的注解为:" + an.annotationType().getSimpleName());

// 获取注解class对象

Class aClass = an.annotationType();

// 通过class对象获取他的所有属性方法

Method[] meths = aClass.getDeclaredMethods();

// 遍历每个注解的所有变量

for (Method meth : meths) {

System.out.println("注解的变量名为:" + meth.getName());

}

}

}

}

}

输出结果:

4.4. 读取字段上的注解

public class User {

@Init("张三")

private String name;

@Init("33")

private String age;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getAge() {

return age;

}

public void setAge(String age) {

this.age = age;

}

}

public static void main(String[] args) throws IllegalAccessException {

User user = new User();

user.setAge("111");

user.setName("www");

Class userClass = User.class;

Field[] fields = userClass.getDeclaredFields();

for (Field field : fields) {

System.out.println("属性名称是:" + field.getName());

Init annotation = field.getAnnotation(Init.class);

System.out.println("注解的value值是:" + annotation.value());

// 字段是私有的想要获取就需要将Accessible设置为true,否则报错

field.setAccessible(true);

Object o = field.get(user);

System.out.println("user对象的属性值是:" + o);

System.out.println();

}

}

输出的结果:

4.5. 读取方法参数注解

@RequestMapping(value = "/get", method = RequestMethod.GET)

@ResponseBody

public boolean get(@RequestParam("username") String usname) {

return useLocalCache;

}

public static void main(String[] args) throws NoSuchMethodException {

Class configControllerClass = ConfigController.class;

// 获取get方法,第一个参数是方法名,第二个是参数类型

Method get = configControllerClass.getDeclaredMethod("get", String.class);

// 方法上可能有多个参数,每个参数上可能有多个注解,所以是二维数组

// annotations[0][0]表示第1个参数的第1个注解

// annotations[0][1]表示第1个参数的第2个注解

Annotation[][] annotations = get.getParameterAnnotations();

for (int i = 0; i < annotations.length; i++) {

for (int j = 0; j < annotations[i].length; j++) {

if (annotations[i][j] instanceof RequestParam) {

RequestParam myParam1 = (RequestParam) annotations[i][j];

System.out.println(myParam1.value());

}

}

}

}

输出结果:

4.6. 注解配合枚举使用

@RequestMapping当中method属性就是使用的枚举。

@RequestMapping(value = "/get", method = RequestMethod.GET)

@ResponseBody

public boolean get(@RequestParam("username") String usname) {

return useLocalCache;

}

public static void main(String[] args) throws NoSuchMethodException {

Class configControllerClass = ConfigController.class;

// 获取get方法,第一个参数是方法名,第二个是参数类型

Method get = configControllerClass.getDeclaredMethod("get", String.class);

RequestMapping annotation = get.getAnnotation(RequestMapping.class);

RequestMethod[] method = annotation.method();

RequestMethod requestMethod = method[0];

switch (requestMethod) {

case GET:

System.out.println("get请求");

break;

case POST:

System.out.println("post请求");

break;

}

}

输出结果:

相关推荐

5分的硬币值多少钱,各年份5分硬币价格表
beat365亚洲手机平台

5分的硬币值多少钱,各年份5分硬币价格表

07-03 👁️ 4135
ps时间轴怎么用? ps时间轴的详细使用方法
beat365亚洲手机平台

ps时间轴怎么用? ps时间轴的详细使用方法

08-19 👁️ 9921
DNF开服时有几个区——玩家攻略与合服细节解析
office365admin下载

DNF开服时有几个区——玩家攻略与合服细节解析

07-27 👁️ 1187