反射、注解

1反射

1.1 什么是反射

程序运行期间,使用另外一种技术来创建对象实例,调用方法,后期我们经常使用,但很少去写【框架】

1.2 字节码对象

获取字节码对象

// 对象实例.getClass()
// 类.class
// Class.forName("全限定类名")

代码片段

// Class.forName("全限定类名");
@Test
public void test03() throws Exception {
   Class c1 = Class.forName("com.itheima.b_reflect.b1_class.Student");
   System.out.println(c1);
}

常用方法

public String getSimpleName(); 获取字节码简单名称 类名
public String getName(); 获取字节码全限定类名 包名+类名
public T newInstance(); 创建对象实例 要求:空参构造方法

代码片段

@Test
public void test03() throws Exception {
   // 创建Student对象实例
   Student o = (Student) clazz.newInstance();
   System.out.println(o);
}

1.3 构造器  

通过字节码对象获取构造器

public Constructor[] getConstructors(); 获取所有public修饰的构造器
public Constructor [] getDeclaredConstructors(); 获取所有声明的构造器

public Constructor getConstructor(Class.. 类型列表); 获取指定public修饰的构造器

public Constructor getDeclaredConstructor(Class.. 类型列表); 获取指定声明的构造器

常用方法

public T newInstance(Object... 参数列表); 创建对象实例
public void setAccessible(boolean true); 暴力反射

代码片段

// 获取private修饰的二个参数构造器
@Test
public void test05() throws Exception {
   // 字节码对象
   Class clazz = Class.forName("com.itheima.b_reflect.b2_constructor.Student");
   // 构造器
   // 错误 java.lang.NoSuchMethodException
   // Constructor constructor = clazz.getConstructor(int.class, String.class);
   Constructor constructor = clazz.getDeclaredConstructor(int.class, String.class);
   // java.lang.IllegalAccessException
   constructor.setAccessible(true); // 取消全限检查 (暴力反射)
   // 创建对象实例
   Object o = constructor.newInstance(3, "林允");
   System.out.println(o);
}

1.4 方法

通过字节码对象获取方法

public Method[] getMethods(); 获取所有public修饰的方法,包括父类的
public Method[] getDeclaredMethods();获取所有声明的方法,不包括父类

public Method getMethod(String 方法名,Class... 类型列表); 获取指定public修饰的方法

public Method getDeclaredMethod(String 方法名,Class... 类型列表); 获取指定声明的方法

常用方法

public T invoke(Object 对象实例,Object... 参数列表); 调用对象实例方法

public void setAccessible(boolean true); 取消全限检查(暴力反射)

代码片段


// private修饰的 sleep方法 有返回值
@Test
public void test05() throws Exception {
   // 字节码对象
   Class clazz = Class.forName("com.itheima.b_reflect.b3_method.Student");
   // 方法
   // 错误 java.lang.NoSuchMethodException
   // Method method = clazz.getMethod("sleep");
   Method method = clazz.getDeclaredMethod("sleep");
   // 创建对象实例
   Object o = clazz.newInstance();
   // java.lang.IllegalAccessException
   method.setAccessible(true);// 取消全限检查 (暴力反射)
   // 调用
   Object result = method.invoke(o);
   System.out.println(result);
}

// public修饰的 静态 study 方法
@Test
public void test06() throws Exception {
   // 字节码对象
   Class clazz = Class.forName("com.itheima.b_reflect.b3_method.Student");
   // 方法
   Method method = clazz.getMethod("study");
   // 调用方法
   method.invoke(null);
}

1.5 属性【字段】

因为我们定义的属性,都提供了 setXxx  getXxx 方法

通过字节码对象获取字段


public Field[] getFields(); 获取所有public修饰的字段
public Field[] getDeclaredFields(); 获取所有声明的字段
public Field getField(String 字段名); 获取指定public修饰字段
public Field getDeclaredField(String 字段名); 获取指定声明的字段

常用方法


public void set(Object 对象实例,Object 实参内容); 对象实例赋值
setInt setDouble
public T get(Object 对象实例); 对象实例取值
getInt getDouble
public Class getType(); 当前字段的类型
public void setAccessible(boolean true); 暴力反射

代码片段


// 给 name字段 赋值 取值 并判类型
@Test
public void test() throws Exception {
   // 字节码对象
   Class clazz = Class.forName("com.itheima.b_reflect.b4_field.Student");
   // 获取name字段
   // Field field = clazz.getField("name");
   Field field = clazz.getDeclaredField("name");
   // 创建对象实例
   Object o = clazz.newInstance();
   // 暴力反射
   field.setAccessible(true);
   // 赋值
   field.set(o, "舒淇");
   // 取值
   field.get(o);
   System.out.println(o);
   // 字段类型
   Class<?> type = field.getType();
   System.out.println(type);
}

1.6 反射案例

spring 容器 (创建对象实例、存储对象实例)

需求

编写一个工厂方法,根据配置文件的信息生产任意对象实例

需求分析

编写一个Student类(编号、姓名、性别) 编写一个配置文件(类的信息 全限定类名 属性和对应的值)编写一个工厂方法 加载配置文件 生产对象

步骤分析

1 编写一个Student类(编号、姓名、性别)

2 编写一个配置文件(类的信息 全限定类名 属性和对应的值)

3 编写一个工厂方法 加载配置文件 生产对象

// 读取配置文件信息

// 创建字节码对象

// 创建对象实例

// 根据其他的属性信息 给对象实例赋值

@Test
   public void test() throws Exception {
 
       // 创建properties对象
       Properties pro = new Properties();
       // 加载
       pro.load(new FileReader("student.properties"));

       // 获取配置文件的信息
       String className = pro.getProperty("className");
       String id = pro.getProperty("id");
       String name = pro.getProperty("name");
       String gender = pro.getProperty("gender");

       // 创建字节码对象
       Class clazz = Class.forName(className);

       // 获取public类型的带参构造方法
       Constructor constructor = clazz.getConstructor(int.class, String.class, String.class);

       // 创建对象实例,赋值
       Object o = constructor.newInstance(Integer.parseInt(id), name, gender);
       System.out.println(o);
     
       // 方法二
       // 读取配置文件信息
       InputStream is = TestRef.class.getClassLoader().getResourceAsStream("fun/chenqi/day01/demo01/student.properties");
       Properties properties = new Properties();
       properties.load(is);
       // 创建字节码对象
       Class clazz = Class.forName(properties.getProperty("className"));
       // 创建对象实例
       Object o = clazz.newInstance();
       // 根据其他的属性信息 给对象实例赋值
       Set<String> keys = properties.stringPropertyNames();
       // 遍历key
       for (String key : keys) {
           // 处理 className  跳出本次循环
           if ("className".equalsIgnoreCase(key)) continue;
           // 获取 value
           String value = properties.getProperty(key);
           // 获取field   id name gender
           Field field = clazz.getDeclaredField(key);

           field.setAccessible(true);// 暴力反射
           // 获取当前的字段类型
           Class<?> type = field.getType();
           if (type == int.class) {
               field.setInt(o, Integer.parseInt(value));
           } else {
               // 赋值操作
               field.set(o, value);
           }

       }
       System.out.println(o);

2 注解

2.1 什么是注解

相当于一种标记,是类的组成部分,我们可以给类携带一些额外的信息

给编译器看的(代码检查)

给JVM虚拟机看(代替配置文件)

2.2 jdk常用注解

@Override 声明方法是重写 父类或接口
@Deprecated 声明方法不赞成使用
有二种情况
1. 此方法过时了
2. 此方法可能bug

2.3 自定义注解

注解跟类和接口一样,同一级别

创建类
public class 类名{
   
}
创建注解
public @interface   注解名{


}

注解属性格式

第一种格式:
属性类型 属性名();
第二种格式:
属性类型 属性名() default 默认值;

属性类型


基本数据类型 四类八种
String类型、Class类型、注解类型、枚举类型
以上类型一维数组

枚举:认为被阉割的类,存储常量

代码片段

public @interface MyAnnotation02 {

   int id();

   String name();

   // Date birthday();

   Class clazz();

   MyAnnotation01 m01();

   Test01 t01();

   String[] names();
}

2.4 使用注解

注解在默认情况下可以在类的任意位置使用

拿class举例

@注解名(属性名=属性值,属性名=属性值,属性名=属性值)
public class 类名{}

注意:使用注解时

若注解有属性,那么注解属性必须有值

若注解属性为属性类型且仅为单值时,我们可以省略{}

若注解属性名为value且仅为value赋值时,我们可以省略value

推荐大家按照固定格式 :属性名=属性值

代码片段

@Book05(value = "三国演义",price = 99)
public class TestBook05 {
}

2.5 元注解

修饰注解的注解

@Target(value={ElementType.常量})
作用:声明注解的使用位置,默认任意位置
ElementType 枚举类
 TYPE 接口或类
 CONSTRUCTOR 构造器
 METHOD 方法
 Field 字段
@Retention(value=RetentionPolicy.常量)
       作用:声明注解的使用阶段,默认情况下 CLASS
 RetentionPolicy 枚举类
 SOURCE 源码
 CLASS 字节码
 
 RUNTIME 运行

代码片段

@Target(value = {ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
//@Retention(value = RetentionPolicy.CLASS)
//@Retention(value = RetentionPolicy.RUNTIME)
public @interface Book01 {
}

2.6 注解解析

在程序运行期间,获取注解属性的值

可以在类中,或方法或字段等等位置获取注解信息

public boolean isAnnotationPresent(Class 注解类型); 判断注解是否存在
public Annotation getAnnotation(Class 注解类型); 获取指定注解

Annotation 注解的公共接口 ,类比于  Object 所有类的父类

注意:

要求注解声明使用阶段  RUNTIME

代码片段

     // 解析方法上的注解属性信息
   @Test
   public void test02() throws Exception {
       // 获取Book03Demo字节码对象
       Class clazz = Class.forName("com.itheima.c_annotation.c5_parse.Book03Demo");
       // 获取bookStore的方法
       Method method = clazz.getMethod("bookStore");
       // 判断是否有Book03 注解
       boolean flag = method.isAnnotationPresent(Book03.class);
       if (flag) {
           // 如果有获取注解属性的值
           Book03 book03 = method.getAnnotation(Book03.class);
           System.out.println(book03.name());
           System.out.println(book03.price());
           System.out.println(Arrays.toString(book03.author()));
       }
   }

2.7 注解案例

需求

模拟junit单元测试

需求分析

编写一个普通的类,添加三个方法,其中二个方法增加一个标记(@MyTest),执行带有特殊标记的方法底层执行(main)

步骤分析

1 编写 @MyTest 自定义注解

声明使用位置 method

声明使用阶段 RUNTIME

2 编写一个普通的类,添加三个方法,其中二个方法增加一个标记(@MyTest)

3 编写一个程序入口类 main 获取 普通类信息,并执行带有 @MyTest方法

代码实现

public static void main(String[] args) throws Exception {
   // 获取普通类 字节码对象
   Class clazz = Class.forName("com.itheima.c_annotation.c6_democase.MyTestDemo");
   // 创建对象实例
   Object o = clazz.newInstance();
   // 获取所有public修饰的方法
   Method[] methods = clazz.getMethods();
   for (Method method : methods) {
       // 判断当前方法是否有@MyTest注解
       if (method.isAnnotationPresent(MyTest.class)) {
           // 如果有执行当前方法
           method.invoke(o );
       }
   }
}

发表评论