反射和注解

反射

框架设计的灵魂

概念

将类的组成部分封装成其他对象

好处

  • 在程序运行中操作对象
  • 解耦,提高程序扩展性

Class类对象

JVM通过类加载器(ClassLoader),将硬盘中的.class字节码文件加载进内存并保存为Class类形式

通过该类的Class类对象就可以用来对该类的对象进行操作

获取Class对象的方式

  1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
    • 多用于配置文件,将类名定义在配置文件中,读取文件,加载类
  2. 类名.Class:通过类名的属性获取
  3. 对象.getClass():在Object类中定义

同一个字节码文件他在一次程序的运行过程中只会被加载一次,无论哪一种方式获取的Class对象都是同一个

Class对象的功能

成员变量

从Class中获取

方法 作用
Field getField(String name) 获取所有public修饰的成员变量
Field[] getFields() 获取指定名称的public修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name) 获取指定的成员变量,不考虑修饰符

操作Field

方法 作用
Object get(Object obj) 获取传入实际对象的成员变量的值,需要具体的类对象
void set(Object obj, Object value) 设置传入实际对象的成员变量的值,需要具体的类对象

在访问权限较高的的成员变量时,需要忽略访问权限访问的安全检查(暴力反射)

Field类对象.setAccessible(true)

暴力反射同理可用在构造方法,成员方法中

构造方法

从Class中获取

方法 作用
Constructor[] getConstructors() 获取所有public构造器对象
Constructor getConstructor(类... parameterTypes) 获取指定形参的public构造器对象(参数为String.class/int.class ...
Constructor[] getDeclaredConstructors() 获取所有构造器对象
Constructor getDeclaredConstructor(类... parameterTypes) 获取指定形参的构造器对象

操作Constructor(构造器)

方法 作用
T newInstance(Object... initargs) 创建对象,参数为构造函数具体形参,返回值为创建对象类型

如果创建空参数的构造方法,可以使用Class类对象.newInstance()创建

成员方法

从Class中获取

方法 作用
Method[] getMethods() 获取所有public方法
Method getMethod(String name,类... parameterTypes) 获取指定名称的public方法
Method[] getDeclaredMethods() 获取所有成员方法
Method getDeclaredMethod(String name, 类... parameterTypes) 获取指定名称的方法

操作Method

方法 作用
Object invoke(Object obj,Object... args) 执行方法,需要具体成员类对象,方法实参
String getName() 获取方法名称

获取类名

方法 作用
String getName() 获取类名

样例

不改变代码的前提下,从配置文件中读取需要执行的类的某一个方法并执行

package test;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * 框架类
 */
public class ReflectTest {
    public static void main(String[] args) throws Exception {
         /**
         * 前提,不改变类的任何代码,可以创建任意类对象,可以执行任意方法
         */
        
        //1.创建Properties对象
        Properties pro = new Properties();
        //2.获取文件的路径
        //2.1获取当前类的类加载器
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        //2.2通过类加载器得到配置文件的字节流
        InputStream is = classLoader.getResourceAsStream("path");//path为配置文件的名称,如pro.properties
        //3.加载配置文件,转换为集合
        pro.load(is);
        //4.获取配置文件中定义的数据
        String className = pro.getProperty("key1");//获取类名(具体的类路径)
        String methodName = pro.getProperty("key2");//获取方法名
        //5.加载该类进内存
        Class cls = Class.forName(className);
        //6.创建对象
        Object obj = cls.newInstance();
        //7.获取方法对象
        Method method = cls.getMethod(methodName);
        //8.执行方法
        method.invoke(obj);
    }
}

注解

概念

JKD1.5以后用来说明程序的特性

作用

  • 编写文档:通过代码里标识的注解生成javadoc文档
  • 代码分析:通过代码里标识的注解对代码进行分析(使用反射)
  • 编译检查:通过注解让编译器能够实现基本的编译检查

JDK预定义注解

  • @Override:检测被该注解标注的方法是否继承自父类(接口)
  • @Deprecated:该注解标注的内容表示已过时
  • @SuppressWarnings:压制警告
    • 一般传递参数all:@SuppressWarnings("all")

自定义注解

格式

public @interface name{
    //属性列表
}

本质

public interface name extends java.lang.annotation.Annotation{}

本质上就是一个接口,默认继承java.lang.annotation.Annotation接口

属性

接口中的抽象方法

要求

  • 属性的返回值类型:
    • 基本数据类型
    • Stirng
    • 枚举
    • 注解
    • 以上类型的数组

使用

@注解名(属性名=值)

  1. 如果定义属性时使用default给属性默认化初始化值那么就不用给属性赋值

  2. 如果只有一个属性需要赋值,并且属性的名称时value,直接定义值即可

  3. 数组赋值时值使用{}包裹

    • 如果数组中只有一个值,大括号可以省略

元注解

用于描述注解的注解

分类

  • @Target:描述注解能够作用的位置
    • ElementType取值
      • TYPE:只能作用于类上
      • METHOD:只能作用于方法上
      • FIELD:只能作用于成员变量上
  • @Retention:描述注解能够保留的阶段
    • @Retention(RetentionPolicy.RUNTIME):当前注解被描述的注解会保留到class字节码文件中,并被JVM读取到
    • @Retention(RetentionPolicy.CLASS):当前注解被描述的注解会保留到class字节码文件中,但不会被JVM读取到
    • @Retention(RetentionPolicy.SOURCE):前注解被描述的注解不会保留到class字节码文件中
  • @Documented:描述注解本身会被抽取到api文档中
  • @Inherited:描述注解会被该类子类继承

在程序中使用(解析)注解

获取注解的属性值

注解:

package test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 描述需要执行的类名和方法名
 */
@Target({ElementType.TYPE})//只能定义在类上
@Retention(RetentionPolicy.RUNTIME)//保留到Runtime阶段
public @interface Pro {
    String className();
    String methodName();
}

解析:

package test;

import java.lang.reflect.Method;

/**
 * 框架类
 */
@Pro(className = "test.Test",methodName = "show")//设置类名和方法名
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        //解析注解
        //1.获取该类的字节码文件对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        //2.获取该类上面的所有注解
        //其实就是在内存中生成了一个该注解接口的子类实现对象,并赋值
        Pro an = reflectTestClass.getAnnotation(Pro.class);
        //3.调用注解的实现子类中的方法,获取返回值
        String className = an.className();
        String methodName = an.methodName();

        //4.加载该类进内存
        Class cls = Class.forName(className);
        //5.创建对象
        Object obj = cls.newInstance();
        //6.获取方法对象
        Method method = cls.getMethod(methodName);
        //7.执行方法
        method.invoke(obj);
    }
}
posted @ 2020-01-01 17:04  夜烛灯花  阅读(141)  评论(0编辑  收藏  举报