【JavaSE】17-Java反射机制

十七、 Java反射机制

17.1 Java反射机制概述

17.1.1 反射-赋予静态语言动态特性的法宝

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法 。
  • 加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象 (一个类只有一个 Class 对象) ,这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。 这个对象就像一面镜子,透过这个镜子看到类的结构。所以,我们形象地称之为:反射。
正常入射
引入需要的包类名称
通过new实例化
取得实例化对象
反射
实例化对象
getClass方法
取得完整的包类名称

17.1.2 静态语言 & 动态语言

1.动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗地说就是在运行时代码可以根据某些条件改变自身结构。

主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

2.静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如 Java、Go、C、C++。

【注意】Java 不是动态语言,但 Java 可以称为 “准动态语言” 。即 Java 有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java 的动态性让编程的时候更加灵活!

17.1.3 Java 反射机制提供的功能

Java 反射机制提供的功能
【判断类】在运行时判断任意一个对象所属的类
【实例化】在运行时构造任意一个类的对象
【判断属性和方法】在运行时判断任意一个类所具有的成员变量和方法
【获取泛型】在运行时获取泛型信息
【调用属性和方法】在运行时调用任意一个对象的成员变量和方法
【注解】在运行时处理注解
【生成动态代理】

17.1.4 反射相关的主要API

API描述
java.lang.Class代表一个类
java.lang.reflect.Method代表类的方法
java.lang.reflect.Field代表类的属性
java.lang.reflect.Constructor代表类的构造器

17.1.5 使用例子

疑问1

既然通过反射,可以在类的外部调用私有属性、私有构造器和私有方法,那面向对象的封装性岂不是形同虚设了吗?

:这是不矛盾的。private 体现的其实是一种 “提示” 的作用,告诉开发者该结构只是类内部使用,而推荐开发者使用 public 的结构,因为 public 的结构更完备、鲁棒性更高,而且 public 的结构很多时候是已经使用了 private 的结构了。因为封装性解决的是建议调用什么结构的问题;而反射解决的是能不能调用的问题。

疑问2:什么时候用反射?

:当编译的时候无法确定要创建哪一类的对象时,可以使用反射。反射具有动态性,当 Java 程序已经运行起来后,仍然可以通过反射来造对象。举个例子:服务器后台已经部署好 Java 程序,并已经成功运行起来了。此时一个用户通过浏览器访问服务器后台,该用户有可能要 “登录” ,也有可能要 “注册” ,服务器后台此时是不知道用户到底要干什么的。当用户点击登录后,浏览器将 “登录” 请求发送到服务器后台,服务器上的 Java 程序已经运行起来了,不可能 new 一个登录相关的对象了,但是可以通过反射造登录相关的对象。

17.2 理解Class类并获取Class实例(重点)

17.2.1 Class类的理解

1.类的加载过程

程序经过 javac.exe 命令以后,会生成一个或多个字节码文件 (.class结尾) 。

接着使用 java.exe 命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,旧称为运行时类,此运行时类,就作为 Class 的一个实例对象。

image-20220503200434137

【我本人的理解】其实就是所有类的类。加载到内存中的所有运行时类都是 Class 的对象,Class 的实例就对应着一个运行时类。

17.2.2 获取Class实例的4种方式

  • 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过以下 4 种方式来获取此运行时类。其中,前 3 种方式需要熟练掌握,方式三使用频率最高。
public class ReflectTest {
    @Test
    public void testClass() throws ClassNotFoundException {
        // 方式一:调用运行时类的属性.class
        Class<Person> clazz1 = Person.class;
        System.out.println(clazz1);

        // 方式二:通过运行时类的对象获取
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);

        // 方式三:调用Class的静态方法:forName(String classPath)【此方法最常用,体现反射的动态性】
        Class clazz3 = Class.forName("day01.Person");
//        Class clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);

        //对比以上3个Class类的对象的地址值,发现它们地址值相同,指向同一个对象,获取的是内存中同一个运行时类Person
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);

        // 方式四:使用类的加载器ClassLoader(了解)
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("day01.Person");
        System.out.println(clazz4);
    }
}

输出:

class day01.Person
class day01.Person
class day01.Person
true
true
class day01.Person

点进看 Class 的源码,如下所示:

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {

Class 类本身是带泛型的类,泛型就是 Java 中运行时真正的类。

17.2.3 Class实例的对象可以有哪些

  • class:外部类、成员 (成员内部类,静态内部类) 、局部内部类、匿名内部类
  • interface:接口
  • [] :数组
  • enum:枚举
  • annotation:注解@interface
  • primitive type:基本数据类型
  • void

以上所有结构都可以是 Class 的实例对象。

@Test
public void test() {
    Class c1 = Object.class;
    Class c2 = Comparable.class;
    Class c3 = String[].class;
    Class c4 = int[][].class;
    Class c5 = ElementType.class;
    Class c6 = Override.class;
    Class c7 = int.class;
    Class c8 = void.class;
    Class c9 = Class.class;

    int[] a = new int[10];
    int[] b = new int[100];
    Class c10 = a.getClass();
    Class c11 = b.getClass();
    // 只要数组的元素类型与维度一样,就是同一个Class
    System.out.println(c10 == c11);
}

输出:

true

17.3 类的加载与ClassLoader的理解

17.3.1 类的加载过程

image-20220503200553319

image-20220503200718967

17.3.3 类的加载器ClassLoader的理解

image-20220503201055120

类加载器的作用

  • 类加载的作用: 将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java lang Class 对象,作为方法区中类数据的访问入口 。
  • 类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载 缓存 一段时间。不过 JVM 垃圾回收机制可以回收这些 Class 对象

image-20220503201540615

其中系统类加载器才是加载自定义类的类加载器。

@Test
public void test1(){
    // 对于自定义类,使用系统类加载器进行加载
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    System.out.println(classLoader);

    // 调用系统类加载器的getParent()方法:获取扩展类加载器
    ClassLoader classLoader1 = classLoader.getParent();
    System.out.println(classLoader1);

    // 调用扩展类加载器的getParent()方法是无法获取引导类加载器
    // 引导类加载器主要负责加载java的核心类库,无法加载自定义类
    ClassLoader classLoader2 = classLoader1.getParent();
    System.out.println(classLoader2);

    // String就是java的核心类库,其类加载器就是引导类加载器, 一样是无法直接获取的
    ClassLoader classLoader3 = String.class.getClassLoader();
    System.out.println(classLoader3);
}

输出:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@28a418fc
null
null

17.3.4 使用ClassLoader加载配置文件(重点)

回忆:集合中的 Properties,用来读取配置文件。创建如下配置文件:

image-20220503203244354

1.读取配置文件方式一

@Test
public void test2() {
    FileInputStream fis = null;
    try {

        Properties pros = new Properties();
        // 此时的文件默认在当前的module下
        fis = new FileInputStream(new File("jdbc.properties"));
        pros.load(fis);

        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user=" + user + ", password=" + password);

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (fis != null)
                fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出:

user=谢斯航, password=abc123

2.读取配置文件方式二:ClassLoader加载配置文件

image-20220503204743779

@Test
public void test3() {
    InputStream is = null;
    try {
        Properties pros = new Properties();
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        // 此时文件默认在module的src下
        is = classLoader.getResourceAsStream("jdbc1.properties");
        pros.load(is);

        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user=" + user + ", password=" + password);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (is != null)
                is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出:

user=老郭, password=ily666

可以看到,使用类加载器 ClassLoader 加载配置文件的默认路径是 module 下的 src 。在实际开发中,更推荐使用 【方式二】 ,即把配置文件放在 module 下的 src 。因为 Java 程序部署到 Tomcat 服务器上时,project 下和 module 下的配置文件都缺失了,所以配置文件都存放在 module 下的 src。

当然,非要用方式一加载配置文件其实也可以,只需要这样写路径 src\\jdbc1.properties 即可。

17.4 创建运行时类的对象(重点)

17.4.1 创建运行时类的对象

@Test
public void test() throws Exception {
    Class<Person> clazz = Person.class;

    // 调用Class对象的newInstance()方法,可创建运行时类的对象,本质上调用了运行时类的空参构造器
    // 运行时类必须提供空参构造器;同时空参构造器的权限得够(一般设置为public)。
    Person p1 = clazz.newInstance();
    System.out.println(p1);
}

输出:

Person{name='null', age=0}

在 JavaBean 中要求提供一个 public 的空参构造器。原因:

  • 便于通过反射,创建运行时类的对象。
  • 便于子类继承父类继承此运行时类时,默认调用 super() 时,保证父类由此构造器。

17.4.2 体会反射的动态性

@Test
public void test2() {// 体会反射的动态性
    for (int i = 0; i < 10; i++) {// 循环10次
        int num = new Random().nextInt(3);// 随机生成[0, 3)的随机数
        String classPath = "";
        switch (num) {
            case 0:// 当随机到0,创建java.util.Date类的对象
                classPath = "java.util.Date";
                break;
            case 1:// 当随机到1,创建Object类的对象
                classPath = "java.lang.Object";
                break;
            case 2:// 当随机到2,创建Person类的对象
                classPath = "day01.Person";
                break;
        }
        Object obj = null;
        try {
            obj = getInstance(classPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(obj);
    }
}

/**
 * @param classPath: 指定运行时类的全类名
 * @return {{@link Object}}
 * @Author: Sihang Xie
 * @Description: 创建一个指定类的对象
 * @Date: 2022/5/3 21:29
 */
public Object getInstance(String classPath) throws Exception {
    Class<?> clazz = Class.forName(classPath);
    return clazz.newInstance();
}

输出:

Wed May 04 09:10:16 CST 2022
Wed May 04 09:10:16 CST 2022
Wed May 04 09:10:16 CST 2022
java.lang.Object@61e717c2
Person{name='null', age=0}
java.lang.Object@66cd51c3
Wed May 04 09:10:16 CST 2022
Wed May 04 09:10:16 CST 2022
java.lang.Object@4dcbadb4
Person{name='null', age=0}

可见,反射就在程序运行之后,根据特定的条件动态创建不同的运行时类对象,不需要在编写代码的时候苦苦思考代码运行时到底需要创建哪些类的对象,从而提高了代码的通用性。

17.5 获取运行时类的完整结构

17.5.1 提供结构丰富的Person类

为了演示反射的强大,能获取运行时类的所有全部结构,我特意构造了一个结构俱全的Person类,包括继承父类 Creature 、实现 SerializableComparable 和自定义接口、使用泛型结构、使用注解、自定义注解等丰富结构。

1.Person的父类 Creature

public class Creature<T> implements Serializable {
    private char gender;
    public double weight;

    private void breath() {
        System.out.println("生物呼吸");
    }

    public void eat() {
        System.out.println("生物进食");
    }
}

2.自定义注解

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)// 生命周期设置为RUNTIME才能被反射获取
public @interface MyAnnotation {
    String value() default "Hello";
}

3.自定义接口

public interface MyInterface {
    void info();
}

4.Person类

@MyAnnotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>, MyInterface {

    private String name;
    int age;
    public int id;

    public Person() {
    }

    @MyAnnotation(value = "abc")
    private Person(String name) {
        this.name = name;
    }

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @MyAnnotation
    private String show(String nation) {
        System.out.println("我的国籍是: " + nation);
        return nation;
    }

    public String display(String interests, int age) throws NullPointerException, ClassCastException {
        return interests + age;
    }
    
    private static void showDesc() {
        System.out.println("我是一个可爱的人");
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }

    @Override
    public void info() {
        System.out.println("我是一个人");
    }

    @Override
    public int compareTo(String o) {
        return 0;
    }
}

17.5.2 获取运行时类的属性

1.获取属性结构

方法作用
Field[] getFields()获取当前运行时类及其父类中声明为public权限的属性
Field[] getDeclaredFields()获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
@Test
public void testField() {
    Class<Person> clazz = Person.class;

    // 获取属性结构
    // 调用Class对象的getFields()方法:获取当前运行时类及其父类中声明为public权限的属性
    Field[] fields = clazz.getFields();
    for (Field f : fields) {
        System.out.println(f);
    }
    System.out.println();

    // getDeclaredFields(): 获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field f : declaredFields) {
        System.out.println(f);
    }
}

第 7 行代码:

image-20220504100036644

输出:

public int day02.Person.id
public double day02.Creature.weight

private java.lang.String day02.Person.name
int day02.Person.age
public int day02.Person.id

2.获取属性内部结构

如属性的权限修饰符、数据类型、变量名等。

@Test
public void test() {
    Class<Person> clazz = Person.class;
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field f : declaredFields) {
        // 1.权限修饰符
        int modifiers = f.getModifiers();
        System.out.print(Modifier.toString(modifiers) + "\t");

        // 2.数据类型
        Class<?> type = f.getType();
        System.out.print(type.getName() + "\t");

        // 3.变量名
        String name = f.getName();
        System.out.println(name);
    }
}

输出:

private	java.lang.String	name
	int	age
public	int	id

17.5.3 获取运行时类的方法结构

1.获取方法结构

方法作用
Field[] getMethods()获取当前运行时类及其父类中声明为public权限的方法
Field[] getDeclaredMethods()获取当前运行时类中声明的所有方法(不包含父类中声明的方法)
@Test
public void testMethod() {
    Class<Person> clazz = Person.class;

    // 获取方法结构
    // 调用Class对象的getMethods()方法:获取当前运行时类及其父类中声明为public权限的方法
    Method[] methods = clazz.getMethods();
    for (Method m : methods) {
        System.out.println(m);
    }
    System.out.println("-------------------------------------------------------");

    // getDeclaredMethods(): 获取当前运行时类中声明的所有方法(不包含父类中声明的方法)
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method m : declaredMethods) {
        System.out.println(m);
    }
}

输出:

public void day02.Person.info()
public java.lang.String day02.Person.display(java.lang.String)
public int day02.Person.compareTo(java.lang.Object)
public int day02.Person.compareTo(java.lang.String)
public void day02.Creature.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
-------------------------------------------------------
public void day02.Person.info()
private java.lang.String day02.Person.show(java.lang.String)
public java.lang.String day02.Person.display(java.lang.String)
public int day02.Person.compareTo(java.lang.Object)
public int day02.Person.compareTo(java.lang.String)

2.获取方法内部结构

@注解

权限修饰符、返回值类型、方法名 (参数类型1 形参名1,……) throws 异常

@Test
public void testMethodStructure() {
    Class<Person> clazz = Person.class;
    Method[] methods = clazz.getDeclaredMethods();
    for (Method m : methods) {

        // 1.获取方法声明的注解
        Annotation[] annos = m.getAnnotations();
        for (Annotation a : annos) {
            System.out.print(a + "\t");
        }

        // 2.权限修饰符
        int modifiers = m.getModifiers();
        System.out.print(Modifier.toString(modifiers) + "\t");

        // 3.返回值类型
        Class<?> returnType = m.getReturnType();
        System.out.print(returnType.getName() + "\t");

        // 4.方法名
        String name = m.getName();
        System.out.print(name);

        System.out.print("(");
        // 5.形参列表
        Class<?>[] parameterTypes = m.getParameterTypes();
        if (!(parameterTypes == null && parameterTypes.length == 0)) {// 有形参的情况
            for (int i = 0; i < parameterTypes.length; i++) {

                if (i == parameterTypes.length - 1) {// 最后一个形参不带逗号
                    System.out.print(parameterTypes[i].getName() + " args_" + i);
                    break;
                }
                System.out.print(parameterTypes[i].getName() + " args_" + i + ", ");
            }
        }
        System.out.print(")");

        // 6.抛出的异常
        Class<?>[] exceptionTypes = m.getExceptionTypes();
        if (exceptionTypes.length > 0) { // 有异常的情况
            System.out.print(" throws ");
            for (int i = 0; i < exceptionTypes.length; i++) {
                if (i == exceptionTypes.length - 1) {
                    System.out.print(exceptionTypes[i].getName());
                    break;
                }
                System.out.print(exceptionTypes[i].getName() + ", ");
            }
        }
        System.out.println();
    }
}

输出:

public	int	compareTo(java.lang.String args_0)
public volatile	int	compareTo(java.lang.Object args_0)
public	void	info()
@day02.MyAnnotation(value=Hello)	private	java.lang.String	show(java.lang.String args_0)
public	java.lang.String	display(java.lang.String args_0, int args_1) throws java.lang.NullPointerException, java.lang.ClassCastException

17.5.4 获取运行时类的构造器结构

1.获取构造器结构

方法作用
Field[] getConstructors()获取当前运行时类中声明为public权限的构造器(不包含父类中声明的方法)
Field[] getDeclaredConstructors()获取当前运行时类中声明的所有构造器(不包含父类中声明的方法)
@Test
public void testConstructor() {
    Class<Person> clazz = Person.class;

    // 获取构造器结构
    // 调用Class对象的getConstructors()方法:获取当前运行时类中声明为public权限的构造器(不包含父类中声明的方法)
    Constructor<?>[] constructors = clazz.getConstructors();
    for (Constructor<?> c : constructors) {
        System.out.println(c);
    }

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

    // getDeclaredConstructors(): 获取当前运行时类中声明的所有构造器(不包含父类中声明的方法)
    Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
    for (Constructor<?> c : declaredConstructors) {
        System.out.println(c);
    }
}

输出:

public day02.Person()
==========================================
day02.Person(java.lang.String,int)
private day02.Person(java.lang.String)
public day02.Person()

2.获取构造器内部结构

方法同上,不再赘述。

17.5.4 获取运行时类的父类及父类的泛型

@Test
public void testSupperClass() {
    Class<Person> clazz = Person.class;
    // 获取运行时类的父类
    Class<? super Person> superclass = clazz.getSuperclass();
    System.out.println(superclass);

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

    // 获取运行时类的带泛型的父类
    Type genericSuperclass = clazz.getGenericSuperclass();
    System.out.println(genericSuperclass);

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

    // 获取运行时类的父类的泛型
    ParameterizedType paramType = (ParameterizedType) genericSuperclass;
    // 获取泛型类型
    Type[] actualTypeArguments = paramType.getActualTypeArguments();
//    System.out.println(actualTypeArguments[0].getTypeName());
    System.out.println(((Class<?>) actualTypeArguments[0]).getName());
}

输出:

class day02.Creature
--------------------------------------
day02.Creature<java.lang.String>
--------------------------------------
java.lang.String

17.5.5 获取运行时类的接口、所在包、注解等

1.获取运行时类的接口

@Test
public void test1() {
    Class<Person> clazz = Person.class;
    // 获取运行时类的接口
    Class<?>[] interfaces = clazz.getInterfaces();
    for (Class<?> c : interfaces) {
        System.out.println(c);
    }

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

    // 获取运行时类的父类的接口
    Class<?>[] supperClassInterfaces = clazz.getSuperclass().getInterfaces();
    for (Class<?> c : supperClassInterfaces) {
        System.out.println(c);
    }
}

输出:

interface java.lang.Comparable
interface day02.MyInterface
--------------------------------
interface java.io.Serializable

2.获取运行时类所在的包

// 获取运行时类所在的包
@Test
public void test2() {
    Class<Person> clazz = Person.class;
    Package pack = clazz.getPackage();
    System.out.println(pack);
}

输出:

package day02

3.获取运行时类的注解

// 获取运行时类的注解
@Test
public void test3() {
    Class<Person> clazz = Person.class;
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation a : annotations) {
        System.out.println(a);
    }
}

输出:

@day02.MyAnnotation(value=hi)

17.6 调用运行时类的指定结构(重点)

主要指的是:属性、方法、构造器。其中重中之重是调用方法。

17.6.1 调用属性

@Test
public void testField() throws Exception {
    Class<Person> clazz = Person.class;

    // 创建运行时类的对象
    Person p = clazz.newInstance();

    // 获取指定的属性:要求运行时类中的属性声明为public的
    Field id = clazz.getField("id");

    // 设置当前属性的值
    // set(Object obj, Object value): 形参1指明设置哪个对象的属性;形参2将此属性值设置为多少
    id.set(p, 1001);

    // 获取当前对象的属性值
    // get(Object obj): 形参指明获取哪个对象的当前属性值
    int pID = (int) id.get(p);
    System.out.println(pID);
}

输出:

1001

以上代码的 getField() 方法获取的属性要求运行时类的属性必须是 public 的,但在实际开发中,运行时类的绝大多数属性都是 private 的,因此上面的方法不采用。而更常用 getDecalaredField() 方法。以下步骤是要大家掌握的:

@Test
public void testField1() throws Exception {
    Class<Person> clazz = Person.class;

    // 创建运行时类的对象
    Person p = clazz.newInstance();

    // 获取指定的属性: 可以获取private的属性
    Field name = clazz.getDeclaredField("name");

    // 【重要操作】修改private属性前,必须开放访问权
    name.setAccessible(true);

    // 设置当前属性的值
    // set(Object obj, Object value): 形参1指明设置哪个对象的属性;形参2将此属性值设置为多少
    name.set(p, "Tom");

    // 获取当前对象的属性值
    // get(Object obj): 形参指明获取哪个对象的当前属性值
    String pName = (String) name.get(p);
    System.out.println(pName);
}

输出:

Tom

17.6.2 调用方法

@Test
public void testMethod() throws Exception {
    Class<Person> clazz = Person.class;

    // 创建运行时类的对象
    Person p = clazz.newInstance();

    // 获取指定的方法
    Method show = clazz.getDeclaredMethod("show", String.class);

    // 【重要操作】调用private方法前,必须开放访问权
    show.setAccessible(true);

    // 调用invoke()方法去执行, 并接收返回值
    String returnValue = (String) show.invoke(p, "中国");
    // 输出返回值
    System.out.println(returnValue);

    System.out.println("-----------如何调用静态方法-----------");

    Method showDesc = clazz.getDeclaredMethod("showDesc");
    showDesc.setAccessible(true);
    // 如果调用的运行时类中的方法没有返回值,则此invoke()返回null
//    Object returnVal = showDesc.invoke(Person.class);// 两种都行
    Object returnVal = showDesc.invoke(null);
    System.out.println(returnVal);// void 无返回值
}

输出:

我的国籍是: 中国
中国
-----------如何调用静态方法-----------
我是一个可爱的人
null

17.6.3 调用构造器

@Test
public void testConstructor() throws Exception {
    Class<Person> clazz = Person.class;

    // 指明构造器的参数列表
    Constructor<Person> constructor = clazz.getDeclaredConstructor(String.class);

    // 保证此构造器是可访问的
    constructor.setAccessible(true);

    // 调用此构造器创建运行时类的对象
    Person p = constructor.newInstance("Rick");
    System.out.println(p);
}

输出:

Person{name='Rick', age=0, id=0}

这样创建运行时类的对象很少用,在实际开发中还是多采用 Class.newInstance() 方法来创建运行时类的对象。

17.7 动态代理

17.7.1 代理模式与动态代理

1.代理设计模式

代理类和被代理类都要实现同一套接口。使用一个代理将对象包装起来 , 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上 。

2.动态代理

程序运行的时候,根据加载到内存的被代理时类,通过反射动态地创建代理类。

优点:抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。即一个代理类就可以完成全部被代理类的功能。

17.7.2 回顾静态代理

  • 静态代理的特点:代理类和被代理类在编译期间,就确定下来了,即已经写死了,在 Java 程序运行起来之后无法被改变。

例子:耐克工厂静态代理

// 代理类和被代理类实现的同一套接口
interface ClothFactory {
    void manufactureCloth();
}

// 代理类
class ProxyClothFactory implements ClothFactory {

    // 代理类中一定要用被代理类的对象作为属性
    private ClothFactory factory;

    // 构造器
    public ProxyClothFactory(ClothFactory factory) {
        this.factory = factory;
    }

    // 重写接口方法
    @Override
    public void manufactureCloth() {
        System.out.println("代理工厂进行准备工作");

        // 中间部分需要被代理类亲自处理的
        factory.manufactureCloth();

        System.out.println("代理工厂进行后续的收尾工作");
    }
}

// 被代理类
class NikeClothFactory implements ClothFactory {

    // 被代理类亲自制造衣服
    @Override
    public void manufactureCloth() {
        System.out.println("Nike工厂制造衣服");
    }
}


public class StaticProxyTest {
    public static void main(String[] args) {
        // 创建被代理类对象
        NikeClothFactory nikeFactory = new NikeClothFactory();
        // 创建代理类对象,构造器传入被代理类的对象
        ProxyClothFactory proxyFactory = new ProxyClothFactory(nikeFactory);
        // 调用代理类的方法
        proxyFactory.manufactureCloth();
    }
}

输出:

代理工厂进行准备工作
Nike工厂制造衣服
代理工厂进行后续的收尾工作

17.7.3 动态代理

1.动态代理要解决的两个问题

  • 如何根据加载到内存中的被代理类,动态地创建一个代理类及其对象?
  • 如何通过调用动态代理类的对象的方法,动态地调用被代理类的同名方法?

2.例子:被代理类是 Superman,代理类和被代理类都实现 Human 接口。该例子逻辑复杂,建议认真多看几次。

【总思路】:ProxyFactory.getInstance(Object obj) 获取代理类对象 → Proxy.newProxyInstance(classLoader, interfaces, handler) 形参中的 InvocationHandler 接口实现类的 invoke(Object proxy, Method method, Object[] args) 方法指明要调用的方法。

// 代理类和被代理类共同实现的接口
interface Human {
    String getBelief();// 获取信仰的方法

    void eat(String food);// 进食方法
}

// 被代理类
class Superman implements Human {

    @Override
    public String getBelief() {
        return "I believe I can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃" + food);
    }
}

// 动态代理
class ProxyFactory {

    // 静态方法:返回代理类的对象,解决问题一,告知代理类此时要代理哪个被代理类
    // 返回值类型写`Object`的原因:如果写`Human`就写死了一个代理类,不能体现动态性。
    // 我们想要的就是只写一个通用的代理类模板,就可以创建许多个不同类型的代理类对象
    public static Object getProxyInstance(Object obj) {// 形参obj:传入被代理类的对象

        ClassLoader classLoader = obj.getClass().getClassLoader();// 第一个形参
        Class<?>[] interfaces = obj.getClass().getInterfaces();// 第二个形参
        MyInvocationHandler handler = new MyInvocationHandler();// 第三个形参(在下面定义)
        handler.bind(obj);// 将被代理类对象传入`handler`中
        // 调用java.lang.reflect包下的`Proxy`API的方法
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

// 自己定义`InvocationHandler`接口的实现类,用来传入`Proxy.newProxyInstance()`方法的第3个形参中
class MyInvocationHandler implements InvocationHandler {

    private Object obj;// obj:被代理类的对象,通过下面的方法赋值

    // 将被代理类对象传入`handler`中
    public void bind(Object obj) {
        this.obj = obj;
    }

    // 当我们通过代理类对象,调用方法a时,就会自动地调用如下的`invoke()`方法
    // 解决问题二:将被代理类要执行的方法a的功能,声明在下面的`invoke()`方法中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // method:即为代理类的对象想调用的方法,同时也作为被代理类对象要调用的同名方法。
        // obj:被代理类的对象
        // 通过反射调用被代理类的方法
        Object returnVal = method.invoke(obj, args);
        // 上述方法的返回值,就作为`invoke()`方法的返回值
        return returnVal;
    }
}

// 测试
public class ProxyTest {
    public static void main(String[] args) {

        // 创建被代理类的对象
        Superman superman = new Superman();
        // proxyInstance:代理类的对象,传入被代理类对象
        // 代理类和被代理类都实现同一套接口`Human`,注意不能写`Superman`,否则`Superman`既是代理类,又是被代理类
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superman);
        // 通过代理类对象,动态地调用被代理类中的同名方法
        proxyInstance.eat("肥肠");
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
    }
}

输出:

我喜欢吃肥肠
I believe I can fly!

【代理的动态性体现在哪里?】上述代码中无法找到 Superman 的代理类。在编译的时候,没有一个类是代理类。只有在代码运行起来之后,才动态地生成代理类的对象。

【动态地代理上一节的 NikeClothFactory 类】可见,动态代理写好一次,就可以代理多个不同的被代理类。

// 测试
public class ProxyTest {
    public static void main(String[] args) {

        // 此外,还可以动态代理上节中的 NikeClothFactory
        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyClothFactory.manufactureCloth();
    }
}

输出:

Nike工厂制造衣服

【关于上述动态代理代码中,一些方法的详细描述】

java.lang.reflect 包下的 Proxy 类的静态方法
Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • 【作用】返回 Object 类的代理类对象。

  • 【传入的形参描述】

    • 第一个形参是 ClassLoader 类加载器。与被代理类 obj 的类加载器保持一致:obj.getClass().getClassLoader()

    • 第二个形参是 Class<?>[] interfaces 接口数组。因为代理类要与被代理类实现同一套接口,因此要与被代理类 obj 的所有接口保持一致:obj.getClass.getInterfaces()

    • 第三个形参是实现了 InvocationHandler 接口的实现类的对象。这个类/接口还是第一次见,点进去看看是什么结构:

      public interface InvocationHandler {
      

      发现 InvocationHandler 是一个接口。该接口解决的是问题二:通过调用动态代理类的对象的方法,动态地调用被代理类的同名方法。我们需要自己定义 InvocationHandler 接口的实现类:

      // 自己定义`InvocationHandler`接口的实现类,用来传入`Proxy.newProxyInstance()`方法的第3个形参中
      class MyInvocationHandler implements InvocationHandler {
      
          private Object obj;// obj:被代理类的对象,通过下面的方法赋值
      
          // 将被代理类对象传入`handler`中
          public void bind(Object obj) {
              this.obj = obj;
          }
      
          // 当我们通过代理类对象,调用方法a时,就会自动地调用如下的`invoke()`方法
          // 解决问题二:将被代理类要执行的方法a的功能,声明在下面的`invoke()`方法中
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      
              // method:即为代理类的对象想调用的方法,同时也作为被代理类对象要调用的同名方法。
              // obj:被代理类的对象
              // 通过反射调用被代理类的方法
              Object returnVal = method.invoke(obj, args);
              // 上述方法的返回值,就作为`invoke()`方法的返回值
              return returnVal;
          }
      }
      

      重写的接口方法 invoke() 中需传入的形参描述:

      1)Object proxy :代理类的对象。

      2)Method method :即为代理类的对象想调用的方法,同时也作为被代理类对象要调用的同名方法。

      3)Object[] args :被代理类的同名方法中的形参列表。

17.7.4 AOP和动态代理

Spring 框架的两个重要思想:AOP 和 IOC (Inverse Of Controll) 控制反转。

1.AOP (Aspect Orient Programming) :面向切面编程

AOP 通过预编译方式和运行其动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术。

1.思想背景

image-20220509125210501

“硬编码”就是在编码的时候就写死了,现在的需求是能否让蓝色代码块动态起来?想调方法 A 就调 A ,想调方法 B 就调 B 。

2.AOP思想示意图

image-20220509125539193

上下两块橙色代码块都是功能相同的通用方法,中间灰色的代码块就是动态的、可灵活切换的方法。

3.例子

在上一节动态代理的例子中添加一个 HumanUtil 类:

// AOP的体现
class HumanUtil {
    public void method1() {
        System.out.println("**************************通用方法一**************************");
    }

    public void method2() {
        System.out.println("**************************通用方法二**************************");
    }
}

MyInvocationHandler 类的 invoke() 方法中形成两面包夹之势:

// 自己定义`InvocationHandler`接口的实现类,用来传入`Proxy.newProxyInstance()`方法的第3个形参中
class MyInvocationHandler implements InvocationHandler {

    private Object obj;// obj:被代理类的对象,通过下面的方法赋值

    // 将被代理类对象传入`handler`中
    public void bind(Object obj) {
        this.obj = obj;
    }

    // 当我们通过代理类对象,调用方法a时,就会自动地调用如下的`invoke()`方法
    // 解决问题二:将被代理类要执行的方法a的功能,声明在下面的`invoke()`方法中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        HumanUtil util = new HumanUtil();
        util.method1();// 通用方法1

        // method:即为代理类的对象想调用的方法,同时也作为被代理类对象要调用的同名方法。
        // obj:被代理类的对象
        // 通过反射调用被代理类的方法
        Object returnVal = method.invoke(obj, args);

        util.method2();// 通用方法2

        // 上述方法的返回值,就作为`invoke()`方法的返回值
        return returnVal;
    }
}

其余代码不变:

// 测试
public class ProxyTest {
    public static void main(String[] args) {

        // 创建被代理类的对象
        Superman superman = new Superman();
        // proxyInstance:代理类的对象,传入被代理类对象
        // 代理类和被代理类都实现同一套接口`Human`,注意不能写`Superman`,否则`Superman`既是代理类,又是被代理类
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superman);
        // 通过代理类对象,动态地调用被代理类中的同名方法
        proxyInstance.eat("肥肠");

        // 此外,还可以动态代理上节中的 NikeClothFactory
        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyClothFactory.manufactureCloth();
    }
}

输出:

**************************通用方法一**************************
我喜欢吃肥肠
**************************通用方法二**************************

**************************通用方法一**************************
Nike工厂制造衣服
**************************通用方法二**************************

通过上述的代码,就可以实现如下图所示的 AOP 的 ”两面包夹之势“ 的 AOP 动态代理方法。中间灰色的方法是可以灵活地更换,而上下的橙色通用方法固定。 AOP 面向切面编程思想将在 Spring 框架的时候进行更深入的学习。

image-20220509125539193

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡皮巴拉不躺平

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值