10-javase-注解&反射-ydl学习笔记
一、注解
概念:
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种机制。Java语言中的类、方法、变量、参数和包等都可以被标注。
1. 注解(Annotation)的定义
1.1 定义注解并使用
定义的格式是:String name();
代码如下(示例):
public @interface MyAnnotation {
String name();
}
代码如下:
public class Dog {
private String name;
private int age;
@MyAnnotation(name = "小黄")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
1.2 添加默认值
可以有默认值,也可以没有,如果没有默认值在使用的时候必须填写对应的值。默认值使用default添加。
1.3 方法名字定义为value()
如果想在使用的时候不指定具体的名字,方法名字定义为value() 即可。
注意:如果有多个参数时必须指定具体名字
2. 注解(Annotation)组成部分
我们使用javap查看生成的注解类:
F:\develop\SSM\Spring\donglijiedian_spring\spring\d3-ioc-homework\target\classes\com\it\Annotation>javap -v MyAnnotation.class
Classfile /F:/develop/SSM/Spring/donglijiedian_spring/spring/d3-ioc-homework/target/classes/com/it/Annotation/MyAnnotation.class
Last modified 2022-3-9; size 260 bytes
MD5 checksum 113ec4cabaa9715f2aaba823edd7bb69
Compiled from "MyAnnotation.java"
// 我们发现字节码中注解其实也是一个接口统一继承字java.lang.annotatoin.Annotation
public interface com.it.Annotation.MyAnnotation extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #12 // com/it/Annotation/MyAnnotation
#2 = Class #13 // java/lang/Object
#3 = Class #14 // java/lang/annotation/Annotation
#4 = Utf8 name
#5 = Utf8 ()Ljava/lang/String;
#6 = Utf8 AnnotationDefault
#7 = Utf8 小黑
#8 = Utf8 value
#9 = Utf8 ()I
#10 = Utf8 SourceFile
#11 = Utf8 MyAnnotation.java
#12 = Utf8 com/it/Annotation/MyAnnotation
#13 = Utf8 java/lang/Object
#14 = Utf8 java/lang/annotation/Annotation
{
// 生成了两个抽象方法
public abstract java.lang.String name();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#7
public abstract int value();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "MyAnnotation.java"
F:\develop\SSM\Spring\donglijiedian_spring\spring\d3-ioc-homework\target\classes\com\it\Annotation>
java Annotation 的组成中,有 3 个非常重要的主干类。它们分别是:
2.1 Annotation.java
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
2.2 ElementType.java
ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。大白话就是,说明了我的注解将来要放在哪里。
package java.lang.annotation;
public enum ElementType {
// 类、接口(包括注释类型)或枚举声明
TYPE,
// 字段声明(包括枚举常量
FIELD,
// 方法声明
METHOD,
// 参数声明
PARAMETER,
// 构造方法声明
CONSTRUCTOR,
// 局部变量声明
LOCAL_VARIABLE,
// 注释类型声明
ANNOTATION_TYPE,
// 包声明
PACKAGE
}
2.3 RetentionPolicy.java
RetentionPolicy 是 Enum 枚举类型,它用来指定 Annotation 的策略。通俗点说,就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。
1.
若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,“@Override” 就没有任何作用了。
2.
若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是 Annotation 的默认行为。
3.
若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并且可由JVM读入。
package java.lang.annotation;
public enum RetentionPolicy {
//Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了
SOURCE,
//编译器将Annotation存储于类对应的.class文件中。但不会加载到JVM中。默认行为
CLASS,
// 编译器将Annotation存储于class文件中,并且可由JVM读入,因此运行时我们可以获取。
RUNTIME
}
3. java自带的 注解(Annotation)
Annotation 实现类的语法定义:
3.1 内置的注解
Java 定义了一套注解,共有10 个,6个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
(1)作用在代码的注解是
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable - Java8 开始支持,标识某注解可以在同一个声明上使用多次。
(2)作用在其他注解的注解(或者说 元注解)是:
@Retention -标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented -标记这些注解是否包含在用户文档中。
@Target - 标记这个注解可以修饰哪些 Java 成员。
@Inherited -如果一个类用上了 @Inherited修饰的注解,那么其子类也会继承这个注解
3.2 常用的注解
通过上面的示例,我们能理解:@interface 用来声明 Annotation,@Documented 用来表示该 Annotation 是否会出现在 javadoc 中, @Target 用来指定 Annotation 的类型,@Retention 用来指定 Annotation 的策略。
@Documented 标记这些注解是否包含在用户文档中。
@Inherited
@Inherited 的定义如下:加有该注解的注解会被子类继承,注意,仅针对类,成员属性、方法并不受此注释的影响。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Deprecated
@Deprecated 的定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}
说明:@Deprecated 所标注内容,不再被建议使用。
加上这个注解在使用或者重写时会有警告:
@SuppressWarnings
@SuppressWarnings 的定义如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
说明:
SuppressWarnings 的作用是,让编译器对"它所标注的内容"的某些警告保持静默,用于抑制编译器产生警告信息。。例如,“@SuppressWarnings(value={“deprecation”, “unchecked”})” 表示对"它所标注的内容"中的 "SuppressWarnings 不再建议使用警告"和"未检查的转换时的警告"保持沉默
关键字 | 用途 |
---|---|
all | 抑制所有警告 |
boxing | 抑制装箱、拆箱操作时候的警告 |
fallthrough | 抑制在switch中缺失breaks的警告 |
finally | 抑制finally模块没有返回的警告 |
rawtypes | 使用generics时忽略没有指定相应的类型 |
serial | 忽略在serializable类中没有声明serialVersionUID变量 |
unchecked | 抑制没有进行类型检查操作的警告 |
unused | 抑制没被使用过的代码的警告 |
4. 注解(Annotation)的作用
(1)Annotation 具有"让编译器进行编译检查的作用“,这个讲了很多了。
(2)利用反射,和反射配合使用能产生奇妙的化学反应。
二、反射
1.反射入门
我们都知道光是可以反射的,我们无法直接接触方法区中一个类的方法、属性、注解等,那就可以通过一面镜子观察它的全貌,这个镜子就是JDK给我们提供的Class类。
首先我们看一下Class这个类,初步简单的分析一下。我们发现这个类并没有什么成员变量,仅仅存在许多的方法,还有不少是本地方法。通过这些方法的名字我们大致能猜出,这个类能帮我们获取方法、构造器、属性、注解等。
代码如下(示例):
public final class Class<T> {
// 获得他实现的接口
public Class<?>[] getInterfaces() {
ReflectionData<T> rd = reflectionData();
if (rd == null) {
// no cloning required
return getInterfaces0();
} else {
Class<?>[] interfaces = rd.interfaces;
if (interfaces == null) {
interfaces = getInterfaces0();
rd.interfaces = interfaces;
}
// defensively copy before handing over to user code
return interfaces.clone();
}
}
private native Class<?>[] getInterfaces0();
// 获得方法
@CallerSensitive
public Method[] getMethods() throws SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return copyMethods(privateGetPublicMethods());
}
// 获得他的构造器
@CallerSensitive
public Constructor<?>[] getConstructors() throws SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return copyConstructors(privateGetDeclaredConstructors(true));
}
// 获得他的属性
@CallerSensitive
public Field getField(String name)
throws NoSuchFieldException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Field field = getField0(name);
if (field == null) {
throw new NoSuchFieldException(name);
}
return field;
}
}
我们已经学过了类的加载过程,这里我们要介绍的是,每一个类加载完成后会在方法区生成一个Class类型的对象,辅助我们访问这个的方法、构造器、字段等。这个对象是Class的子类,每个类【有且仅有】一个Class类,也叫类对象。
2. 获取类对象的方法
2.1 获取方式
代码如下(示例):
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
// 1、使用类名
Class<Dog> dogClass1 = Dog.class;
// 2、使用对象
Dog dog = new Dog();
Class<? extends Dog> dogClass2 = dog.getClass();
// 3、formName使用全限定名
Class<?> dogClass3 = Class.forName("com.it.Annotation.Dog");
System.out.println(dogClass1 == dogClass2);
System.out.println(dogClass2 == dogClass3);
}
}
2.2 对类对象操作
代码如下(示例):
import com.it.Annotation.Dog;
import org.junit.Before;
public class ReflectTest {
Class<Dog> dogClass = null;
@Before
public void before(){
dogClass = Dog.class;
}
代码如下(示例):
@Test
public void createTest() throws InstantiationException, IllegalAccessException {
//获取类名字
String name = dogClass.getName();
//获取类加载器
ClassLoader classLoader = dogClass.getClassLoader();
//获取资源
URL resource = dogClass.getResource("");
//得到父类
Class superclass = dogClass.getSuperclass();
//判断一个类是不是接口,数组等等
boolean array = dogClass.isArray();
boolean anInterface = dogClass.isInterface();
//重点,使用class对象实例化一个对象
Object instance = dogClass.newInstance();
}
3. 对成员变量的操作
在java中万物皆对象成员变量也是对象,他拥有操作一个对象的成员变量的能力。
3.1 获取成员变量
getFields只能获取被public修饰的成员变量,当然反射很牛,我们依然可以使用getDeclaredFields方法获取所有的成员变量。
代码如下(示例):
// 这个方法只能拿到public修饰的成员变量
Field[] fields = dogClass.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
// 这个方法可以获取所有的成员变量
Field[] fields1 = dogClass.getDeclaredFields();
for (Field field : fields1) {
System.out.println(field.getName());
}
3.2 获取对象属性
代码如下(示例):
// 这个方法可以获取所有的成员变量
Field[] fields1 = dogClass.getDeclaredFields();
for (Field field : fields1) {
System.out.println(field.getName());
}
当然你要是明确类型你还能用以下方法:
代码如下(示例):
Int i = age.getInt(dog);
xxx.getDouble(dog);
xxx.getFloat(dog);
xxx.getBoolean(dog);
xxx.getChar(dog);
//每一种基本类型都有对应方法
3.3 设置对象d1属性
代码如下(示例):
// 成员变量的本质就是赋值和取值,现在的问题是给谁赋值
Dog dog = new Dog();
Field name = dogClass.getDeclaredField("name");
// 直接设置值,不能是private
// 暴力注入
name.setAccessible(true); // 打开权限
name.set(dog, "xiaohei");
System.out.println(name.get(dog));
当然如果你知道对应的类型,我们可以这样:
xxx.setBoolean(dog,true);
xxx.getDouble(dog,1.2);
xxx.getFloat(dog,1.2F);
xxx.getChar(dog,'A');
//每一种基本类型包装类都有对应方法
4. 对方法的操作
4.1 获取方法
代码如下(示例):
// 获取所有的方法
Method[] methods = dogClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
// 获取某一个方法
Method getName = dogClass.getDeclaredMethod("getName");
Method getNam2 = dogClass.getDeclaredMethod("main", String[].class);
4.2 调用方法
代码如下(示例):
// 方法而言只有一个核心方法,就是调用
Method eat = dogClass.getDeclaredMethod("eat", String.class);
Dog dog = new Dog();
eat.setAccessible(true);
eat.invoke(dog,"骨头😝");
5. 对构造器的操作
5.1 获取构造器对象
代码如下(示例):
// 获取构造器对象
Constructor<Dog> constructor = dogClass.getDeclaredConstructor();
System.out.println(constructor.getName());
5.2 获取无参构造器
代码如下(示例):
// 获取无参构造器对象并且构造对象
Constructor<Dog> constructor = dogClass.getDeclaredConstructor();
Dog dog = constructor.newInstance(); // 这行代码与 new Dog(); 是一样的
5.3获取有参构造器并且实力化对象
代码如下(示例):
// 获取有参构造器并且实例化对象
Constructor<Dog> constructor1 = dogClass.getDeclaredConstructor(String.class);
Dog dog1 = constructor1.newInstance("🐕二哈");
System.out.println(dog1.getName());
6. 对注解的操作
代码如下(示例):
// 元注解 要加上runtime
// 类上
MyAnnotation declaredAnnotation = dogClass.getDeclaredAnnotation(MyAnnotation.class);
String name1 = declaredAnnotation.name();
System.out.println("name1 = " + name1);
Annotation[] annotation = dogClass.getAnnotations();
// 字段上
Field name = dogClass.getDeclaredField("name");
Annotation[] annotation1 = name.getAnnotations();
// 方法上
Method eat = dogClass.getDeclaredMethod("eat", String.class);
Annotation[] annotation2 = eat.getAnnotations();
代码如下(示例):
7. 利用类加载器获取资源
public class ClassLoderTest {
@Test
public void test1() throws IOException {
// ①:创建properties实列
Properties properties = new Properties();
// ②:通过反射获取Class --> 在获取类加载器 --> 通过类加载器获取资源IO流
InputStream stream =
ClassLoderTest.class // 通过类名进行反射获取大Class
.getClassLoader() // getClassLoader() 获取这个类的加载器
.getResourceAsStream("com/it/jdbc.properties"); // 以IO流的形式获取资源 返回的就是一个流
// ③:使用properties 的 load() 方法加载 IO流
properties.load(stream); // properties 通过 load() 方法加载,刚好需要一个流
// ④:通过key获取值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
System.out.println("user = " + user);
System.out.println("password = " + password);
}
}