单词Comment可翻译为:评论、议论、解释。
在Java当中,Comment充当注释的含义,Annotation充当注解的含义。
Java中有单行注释、多行注释、文档注释。
注释Comment
单行注释 //
多行注释 /* /
文档注释 /* */
这些注释都是在编译以后不会出现在字节码中的,仅仅是存在于java源文件中给程序员看的,它们对于程序的执行没有任何影响。而注解就不同了,它是代码级别的,是会编译到class字节码当中,对程序的运行产生影响或者对编译产生影响。注解严格意义上来说就是Java中的类成员,它和属性、方法、构造方法是一样的级别。
注解在Java中确实也很常见,但是人们常常不会自己定义一个注解拿来用。我们虽然很少去自定义注解,但是学会注解的写法,注解的定义,学会利用反射解析注解中的信息,在开发中能够使用到,这是很关键的。
代码运行离不开一些配置信息的支撑,有些配置信息我们选择保存在文件中(.xml .properties),再利用IO流的技术读取这些配置信息去使用,它们都是配置和代码分离的形式,这种方式的好处是低耦合,代码已经打包压缩好了不用动,而配置信息修改起来很方便,通过修改配置,可以让代码完成不同的任务,这就很方便。不好的地方在于:开发人员写的代码和配置不在一个文件中,开发过程中翻看就不是很方便。相当于开发麻烦了,维护却简单了。
随着开发的越来越多,人们发现文件中的一些配置,是写到那里很少去更改的,没有人会轻易修改那些重要的配置信息,这些信息就像被写死了一样,所以这些信息就可以用注解写,写到代码里去,因为注解和代码是内聚在一起的,开发过程就很方便。总结来说是各有优劣吧。
@XXX [(一些信息)]
这是使用注解的写法,使用注解前必须先定义注解,我们可以使用像@Override这样的注解,那是因为这些注解在Java中已经写好了,我们直接拿来用就好(下面会讲到如何自定义注解)。
注解中的[一些信息]可能存在,也可能不存在,这需要看注解的定义者是如何定义该注解的。
注解可以放置在:类的上面、属性上面、方法上面、构造方法上面、局部变量上面、参数前面。
注解能够放置在哪里,这也需要看注解的定义者是如何定义该注解的。
注意:注解信息不能随意写,注解信息的类型只能是如下的类型:
通过@interface 定义一个新的注解类型
public @interface MyAnnatation {
}
可以发现注解的写法与接口非常相似(可以利用接口的特点来记忆注解)
public @interface MyAnnatation {
int NUM = 9;//注解中写属性,很少见
String test();//方法要求必须有返回值
}
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。
我们自己定义的注解如果想要拿来使用,光定义还不够 ,还需要做很多细致的说明(需要利用Java提供好的注解来说明)
这就需要使用到元注解(也是注解 不是拿来使用的 是用来说明注解的):
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
更详细的@Target定义可参照下表:
Target类型 | 描述 |
---|---|
ElementType.TYPE | 应用于类、接口(包括注解类型)、枚举 |
ElementType.FIELD | 应用于属性(包括枚举中的常量) |
ElementType.METHOD | 应用于方法 |
ElementType.PARAMETER | 应用于方法的形参 |
ElementType.CONSTRUCTOR | 应用于构造函数 |
ElementType.LOCAL_VARIABLE | 应用于局部变量 |
ElementType.ANNOTATION_TYPE | 应用于注解类型 |
ElementType.PACKAGE | 应用于包 |
ElementType.TYPE_PARAMETER | 1.8版本新增,应用于类型变量) |
ElementType.TYPE_USE | 1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型) |
描述当前的这个注解存在什么作用域中
注解存在的三种作用域:SOURCE、CLASS、RUNTIME
对应于:源代码文件—>编译—>字节码文件—>加载—>内存执行
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
生命周期类型 | 描述 |
---|---|
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
RetentionPolicy.RUNTIME | 由JVM 加载,包含在类文件中,在运行时可以被获取到 |
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。
描述当前这个注解是否能被子类对象继承(不太常用)
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
描述这个注解是否能被Javadoc 或类似的工具文档化(不常用)
自定义一个注解:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Target({METHOD,CONSTRUCTOR,FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String[] value();//方法不是做事情 为了携带信息 搬运给该注解的解析者使用
//按道理讲 注解定义者肯定和注解解析者是同一个人,而注解的使用者,它们无需定义和解析注解
/*方法名刚好是value,并且只有一个方法,使用该注解的时候就可以不指定方法名*/
}
使用自己的注解:
由注解的定义可知,该注解可以放置在方法、构造方法、属性上。
作用范围是运行时RUNTIME
public class Person {
@MyAnnotation("TOM")
private String name;
}
问题1. 在注解里面描述了一个方法,方法没有参数,方法有返回值String[]
使用注解的时候,让我们传递参数,如何理解该过程?
可以理解为:注解的方法做事,把我们传递给它的参数搬运走了,给了别人,别人解析这些注解中的参数,做相应的处理
问题2. 使用别人写好的注解不用写方法名,我们自己定义的方法必须写名字*
如果我们自己定义的注解 只有一个方法 方法名字叫value
在使用的时候就可以省略方法名
如果传递的信息是一个数组 数组内只有一个元素 可以省略数组大括号{}
如果方法是两个以上 每一个方法都要使用 并且每一个方法必须写名字
举例:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Target({METHOD,FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface YouAnnotation {
String [] value();
int count();
double price();
}
public class Student {
//注解中有3个方法,则3个方法都要使用到,并且要指明方法名
//如果数组中只有一个数据,则{}可以省略
@YouAnnotation(value = "qa",count = 8,price = 9.9)
private String name;
}
解析注解其实也很简单,它的思路是:先看该注解声明在了哪里,比如,一个属性name上面声明了一个注解,那么就利用反射,先找到这个类,然后找到这个属性name,根据这个属性,调用getAnnotation()方法,得到这个注解,然后调用这个注解对象的getClass方法获取注解的类型,用注解的类型,调用getMethod(methodName)方法,根据方法名获取注解中的方法,然后,这个方法对象调用 invoke方法去执行方法,方法的参数是那个annotation对象,方法的返回值就是这个注解中携带的信息了。
public class Demo {
public static void main(String[] args) {
Class<?> clazz = Person.class;
try {
Field field = clazz.getDeclaredField("name");
MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
Class<? extends MyAnnotation> aClass = annotation.getClass();
Method method = aClass.getMethod("value");
String[] values = (String[])method.invoke(annotation);
System.out.println(Arrays.toString(values));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Spring的核心特性是控制反转(IOC)和面向切面编程(AOP)。控制反转是指对象的控制权不在我们手里了,而是交给了Spring给我们创建。我们只需要把实体类定义好,提供好无参构造方法和get、set方法就好了,它给你的对象自动赋值了,这就叫依赖注入(DI)。只不过,它给属性赋的值是从数据库中读取出来的,而我们这里选择把属性值写在注解中,然后读取注解中的值,给属性赋值。我这个案例它只能够处理9种属性类型,包括8种基本类型的包装类以及String,其他的类型还不能支持。
package test_annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author 乔澳
* @version 1.0
* @title: MySpringDemo
* @projectName Demo1
* @description:
* @date 2020/8/17 18:22
*/
public class MySpringDemo {
public Object getBean(String className){
Object obj = null;
Class<?> clazz = null;
try {
clazz = Class.forName(className);
//获取无参构造方法
Constructor con = clazz.getConstructor();
//调用无参构造方法创建对象
obj = con.newInstance();
//解析注解中的信息
//注解放在属性上面,首先获取所有的属性
Field[] fields = clazz.getDeclaredFields();
for(int i = 0;i<fields.length;i++){
//根据属性获取属性上面声明的注解
Annotation annotation = fields[i].getAnnotation(MyAnnotation.class);
Class<?> aClass = annotation.getClass();
Method aMethod = aClass.getMethod("value");
//获取注解中的值
String[] values = (String[]) aMethod.invoke(annotation);
//获取属性名
String fieldName = fields[i].getName();
//要给属性赋值,属性是私有的,虽然反射可以操作私有属性,但是很不合理
//我们利用字符串的拼接,得到set方法的名字,再拿到set方法给属性赋值
String firstLetter = fieldName.substring(0,1).toUpperCase();//首字母大写
String otherLetters = fieldName.substring(1);
StringBuilder setMethodName = new StringBuilder("set");
setMethodName.append(firstLetter);
setMethodName.append(otherLetters);
//拿到属性的类型,下面要用到
Class<?> fieldType = fields[i].getType();
//根据set方法名字拿到set方法
Method setMethod = clazz.getMethod(setMethodName.toString(),fieldType);
//调用set方法,给对象赋值,如果属性不是String类型,是基本类型的包装类
//如果属性是Character类型,做单独处理
if (fieldType==Character.class){
//把字符串转化为字符
Character c = values[0].toCharArray()[0];
setMethod.invoke(obj,c);
}else{
//不是Character类型,而是String 或其他7种包装类,调用包装类的带String参数的构造方法,构造值 比如;new Integer(String v)
setMethod.invoke(obj,fieldType.getConstructor(String.class).newInstance(values[0]));
}
//对于属性为数组、集合、对象的情况,这里没有做处理
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
测试:
写一个实体类
package test_annotation;
public class Person {
@MyAnnotation("Tom")
private String name;
@MyAnnotation("18")
private Integer age;
@MyAnnotation("男")
private String sex;
@MyAnnotation("A")
private Character bloodType;//血型
public Person(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Character getBloodType() {
return bloodType;
}
public void setBloodType(Character bloodType) {
this.bloodType = bloodType;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", bloodType=" + bloodType +
'}';
}
}
main方法:
package test_annotation;
public class TestMain {
public static void main(String[] args) {
MySpringDemo msd = new MySpringDemo();
Person p = (Person) msd.getBean("test_annotation.Person");
System.out.println(p);
}
}
参考链接:
https://www.cnblogs.com/bingosblog/p/5852510.html
https://blog.csdn.net/zt15732625878/article/details/100061528?utm_source=app