其他分享
首页 > 其他分享> > # 二十、反射(完结)

# 二十、反射(完结)

作者:互联网

二十、反射


20.1 类的加载


20.1.1 类的加载概述

程序运行后,某个类在第一次使用时,会将该类的 class 文件读取到内存,并将此类的所有信息存储到一个Class 对象中

20.1.2 类加载的时机

  1. 创建类的实例
  2. 访问类的成员
  3. 使用反射方式来强制创建某个类或接口对应的 java.lang.Class对象。
  4. 初始化某个类的子类,父类会先加载。
  5. 直接使用 java.exe 命令来运行某个主类。
    总结:用到就加载,不用就不加载

20.1.3  类加载的过程介绍

当一个**Java**** 文件要被加载到内存中使用执行的过程**

  1. 需要把当前的 Java 文件通过 Javac 编译成字节码文件(.class 文件)
  2. 字节码文件需要进行 加载 , 连接 , 初始化 三个动作
  3. 字节码数据加载到 jvm 方法区内存中, 在堆内存中创建此类的对象 , Java 程序进行使用

注意 : 把一个字节码文件加载到内存过程 , 就需要用到类加载进行完成
image.png

20.2 类的加载过程各阶段的任务


Java 程序中需要使用到某个类时,虚拟机会保证这个类已经被加载、连接和初始化。而连接又包含验证、准备和解析这三个子过程,这个过程必须严格按照顺序执行

20.2.3 加载

image.png

20.2.2 连接

  1. 验证 : 确保被加载类的正确性。class  文件的字节流中包含的信息符合当前虚拟机要求,不会危害虚拟机自身的安全。
  2. 准备 : 为类的静态变量分配内存,并将其设置为默认值。此阶段仅仅只为静态类变量(即 static  修饰的字段变量)分配内存,并且设置该变量的初始值。(比如 static int num = 5 ,这里只将 num  初始化为0,5的值将会在初始化时赋值)。对于 final static  修饰的变量,编译的时候就会分配了,也不会分配实例变量的内存。
  3. 解析 : 把类中的符号引用转换为直接引用。符号引用就是一组符号来描述目标,而直接引用就是直接指向目标在内存的位置,即地址,如果引用的这个类没加载进内存就会先加载,加载了就直接替换。

image.png

20.2.3 初始化

20.3  类加载器


20.3.1  Java虚拟机自带的类加载器 (了解)

类加载器:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。

Java 中有三种类加载器,它们分别用于加载不同种类的 class

public class Test{
  public static void main(String[] args){
      //通过Class对象,获取类加载器的方法 --> public ClassLoader getClassLoader() : 返回该类的类加载器
      //如果该类由启动类加载器加载(例如核心类库中的类String 等),则将返回 null。 
      
    System.out.println(Test.class.getClassLoader());//sun.misc.Launcher$AppClassLoader
    System.out.println(String.class.getClassLoader());//null
  }
}

总结:

20.3.2  类加载器--双亲委派机制介绍

image.png
上图展示了"类加载器"的层次关系,这种关系称为类加载器的 "双亲委派模型":

public class ClassLoaderDemo4 {
    /*
        当前是根据自己定义的类进行获取的类加载器对象
        getParent方法获取父类加载器
        第一条输出语句打印的是 : 系统类加载器
        第二条输出语句打印的是 : 扩展类加载器
        第三条输出语句打印的是 : null是根类加载器
     */
public static void main(String[] args) {
        ClassLoader classLoader = ClassLoaderDemo4.class.getClassLoader();
        System.out.println(classLoader);// sun.misc.Launcher$AppClassLoader@b4aac2
        System.out.println(classLoader.getParent());// sun.misc.Launcher$ExtClassLoader@16d3586
        System.out.println(classLoader.getParent().getParent());// null
}

20.3.3 双亲委派模型的优点

package java.lang;

/**
 * @author: Carl Zhang
 * @create: 2022-01-07 13:59
 *  举例 : 如果我们自己定义一个包名字叫做java.lang , 在当前这个包下创建一个类叫做MyObject类
 *  因为java.lang包属于核心包,只能由根类加载器进行加载,而根据类加载的双亲委派机制,根类加载不到这个MyObject类的(自定义的)**
 *  所以只能由AppClassLoader进行加载,而这又是不允许的,因为java.lang下的类需要使用根加载器进行加载**
 *  所以会报出"Prohilbited package name:java.lang"(禁止的包名)异常"
 */
public class MyObject {
    public static void main(String[] args) {
        System.out.println(MyObject.class.getClassLoader()); //java.lang.SecurityException: Prohibited package name: java.lang
    }
}

20.4 反射的介绍


20.4.1 反射的引入

20.4.2 反射的概念

20.4.3 反射的使用前提和场景

20.5  Class对象的获取方式


20.5.1 三种获取方法

package com.itheima._03反射;
public class Student{
    
}
public class ReflectDemo01 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 获得Student类对应的Class对象
        Class c1 = Student.class;

        // 创建学生对象
        Student stu = new Student();
        // 通过getClass方法
        Class c2 = stu.getClass();
        System.out.println(c1 == c2);

        // 通过Class类的静态方法获得: static Class forName("类全名")
        Class c3 = Class.forName("com.itheima._03反射.Student");
        System.out.println(c1 == c3); //true
        System.out.println(c2 == c3); //true
    }
}

20.5.2 Class类常用方法

image.png

示例代码

public class ReflectDemo02 {
    public static void main(String[] args) throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得类名字符串:类名
        System.out.println(c.getSimpleName());
        // 获得类全名:包名+类名
        System.out.println(c.getName());
        // 创建对象
        //此方式相当于通过类的空参构造创建对象,如果目标类没有空参构造,会报错 InstantiationException
        Student stu = (Student) c.newInstance();
        System.out.println(stu);
    }
}

20.6  反射之操作构造方法


20.6.1 Constructor类概述

20.6.2 Class类中与Constructor相关的方法

20.6.3 Constructor对象常用方法

示例代码

public class Student{
   private String name;
   private String sex;
   private int age;
   
   //公有构造方法
   public Student(String name,String sex,int age){
       this.name = name;
       this.sex = sex;
       this.age = age;
   }
   //私有构造方法
   private Student(String name,String sex){
       this.name = name;
       this.sex = sex;
   }
}
public class ReflectDemo03 {

    /*
      Constructor[] getConstructors()
           获得类中的所有构造方法对象,只能获得public的
      Constructor[] getDeclaredConstructors()
            获得类中的所有构造方法对象,包括private修饰的
    */
    @Test
    public void test03() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        //  获得类中的所有构造方法对象,只能获得public的
        // Constructor[] cons = c.getConstructors();
        // 获取类中所有的构造方法,包括public、protected、(默认)、private的
        Constructor[] cons = c.getDeclaredConstructors();
        for (Constructor con:cons) {
            System.out.println(con);
        }
    }

    /*
       Constructor getDeclaredConstructor(Class... parameterTypes)
           根据参数类型获得对应的Constructor对象
    */
    @Test
    public void test02() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得两个参数构造方法对象
        Constructor con = c.getDeclaredConstructor(String.class,String.class);
        // 取消权限检查(暴力反射)
        con.setAccessible(true);
        // 根据构造方法创建对象
        Object obj = con.newInstance("rose","女");
        System.out.println(obj);
    }

    /*
        Constructor getConstructor(Class... parameterTypes)
            根据参数类型获得对应的Constructor对象
     */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得无参数构造方法对象
        Constructor con = c.getConstructor();
        // 根据构造方法创建对象
        Object obj = con.newInstance();
        System.out.println(obj);

        // 获得有参数的构造方法对象
        Constructor con2 = c.getConstructor(String.class, String.class,int.class);
        // 创建对象
        Object obj2 = con2.newInstance("jack", "男",18);
        System.out.println(obj2);
    }
}

20.7 反射之操作成员方法


20.7.1 Method类概述

20.7.2 Class类中与Method相关的方法

20.7.3  Method对象常用方法

示例代码

public class Student{
    private void eat(String str){
        System.out.println("我吃:" + str);
    }
    
    private void sleep(){
        System.out.println("我睡觉...");
    }
    
    public void study(int a){
        System.out.println("我学习Java,参数a = " + a);
    }
}
public class ReflectDemo04 {

    // 反射操作静态方法
    @Test
    public void test04() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 根据方法名获得对应的公有成员方法对象
        Method method = c.getDeclaredMethod("eat",String.class);
        // 通过method执行对应的方法
        method.invoke(null,"蛋炒饭");
    }

    /*
     * Method[] getMethods();
        * 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
     * Method[] getDeclaredMethods();
        * 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
   */
    @Test
    public void test03() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
        // Method[] methods = c.getMethods();
        // 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
        Method[] methods = c.getDeclaredMethods();
        for (Method m: methods) {
            System.out.println(m);
        }

    }

    /*
       Method getDeclaredMethod(String name,Class...args);
           * 根据方法名和参数类型获得对应的构造方法对象,
    */
    @Test
    public void test02() throws Exception {
        // 获得Class对象
        Class c = Student.class;

        // 根据Class对象创建学生对象
        Student stu = (Student) c.newInstance();
        // 获得sleep方法对应的Method对象
        Method m =  c.getDeclaredMethod("sleep");
        // 暴力反射
        m.setAccessible(true);

        // 通过m对象执行stuy方法
        m.invoke(stu);
    }

    /*
        Method getMethod(String name,Class...args);
            * 根据方法名和参数类型获得对应的构造方法对象,
     */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        
        // 根据Class对象创建学生对象
        Student stu = (Student) c.newInstance();
        // 获得study方法对应的Method对象
        Method m =  c.getMethod("study");
        // 通过m对象执行stuy方法
        m.invoke(stu);


        /// 获得study方法对应的Method对象
        Method m2  = c.getMethod("study", int.class);
        // 通过m2对象执行stuy方法
        m2.invoke(stu,8);
    }
}

20.8 反射之操作成员变量


20.8.1 Field类概述

20.8.2 Class类中与Field相关的方法

20.8.3 Field对象常用方法

示例代码

public class Student{
    public String name; 
    private String gender;
    
    public String toString(){
        return "Student [name = " + name + " , gender = " + gender + "]";
    }
}
public class ReflectDemo05 {
    /*
        Field[] getFields();
            * 获得所有的成员变量对应的Field对象,只能获得public的
        Field[] getDeclaredFields();
            * 获得所有的成员变量对应的Field对象,包含private的
     */
    @Test
    public void test02() throws Exception {
        // 获得Class对象
        Class c  = Student.class;
        // 获得所有的成员变量对应的Field对象
        // Field[] fields = c.getFields();
        // 获得所有的成员变量对应的Field对象,包括private
        Field[] fields = c.getDeclaredFields();
        for (Field f: fields) {
            System.out.println(f);
        }
    }

    /*
        Field getField(String name);
            根据成员变量名获得对应Field对象,只能获得public修饰
        Field getDeclaredField(String name);
            *  根据成员变量名获得对应Field对象,包含private修饰的
     */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c  = Student.class;
        // 创建对象
        Object obj = c.newInstance();
        // 获得成员变量name对应的Field对象
        Field f = c.getField("name");
        // 给成员变量name赋值
        // 给指定对象obj的name属性赋值为jack
        f.set(obj,"jack");

        // 获得指定对象obj成员变量name的值
        System.out.println(f.get(obj)); // jack
        // 获得成员变量的名字
        System.out.println(f.getName()); // name


        // 给成员变量gender赋值
        // 获得成员变量gender对应的Field对象
        Field f1 = c.getDeclaredField("gender");
        // 暴力反射
        f1.setAccessible(true);
        // 给指定对象obj的gender属性赋值为男
        f1.set(obj,"男");

        System.out.println(obj);

    }
}

20.9 使用反射解析注解

注:注解的基本介绍见 11.6 注解介绍

20.9.1 AnnotatedElement接口介绍

AnnotatedElement  : 是一个接口,定义了解析注解的方法

1. boolean isAnnotationPresent(Class<Annotation> annotationClass)
   参数 : 注解的字节码对象
   作用 : 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
  
2. T getAnnotation(Class<T> annotationClass) 
   参数 : 注解的字节码对象
   返回值 : 根据注解类型获得对应注解对象 , 有了注解对象就可以调用属性(抽象方法),获取属性值

20.9.2 注解的解析原理

前提:Class,Constructor,Method,Field都实现了AnnotatedElement 接口。

解析注解的原理:获取注解作用位置的对象,来调用方法解析注解

相关方法:

20.9.3 注解的解析案例

需求如下:

1. 定义注解 `Book,要求如下:
   - 包含属性:String value()   书名
   - 包含属性:double price()  价格,默认值为 100
   - 包含属性:String[] authors() 多位作者  
   - 限制注解使用的位置 :类和成员方法上 【Target】
   - 指定注解的有效范围 :RUNTIME  【Retention】
2. 定义BookStore类,在类和成员方法上使用Book注解
3. 定义TestAnnotation测试类获取Book注解上的数据

给成员注入四大名著信息 :
    西游记     --- 施耐庵
    水浒传     --- 吴承恩
    三国演义   --- 罗贯中
    红楼梦     --- 曹雪芹 , 高鹗

Book注解 :

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

//定义一个书的注解,包含属性书名,价格(默认100),作者。作者要求可以有多名作者
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {

    String value();

    double price() default 100;

    String[] author();

}

BookStore类 :

@Book(value = "水浒传", author = "施耐庵")
public class BookStore {

    @Book(value = "三国演义", author = {"罗贯中"})
    public void buy() {

    }
}

**TestAnnotation**** 类:**

import java.lang.reflect.Method;
import java.util.Arrays;
/**
	思路:
	1. 类型上的注解,使用Class对象解析
	2. 方法上的注解,使用Method对象解析
*/

public class TestAnnotation {
    public static void main(String[] args) throws NoSuchMethodException {

        //解析类型上的注解
        Class<BookStore> cls = BookStore.class;
        //判断是否有Book注解,如果有进行解析
        if (cls.isAnnotationPresent(Book.class)) {
            //有
            Book book = cls.getAnnotation(Book.class);
            String name = book.value();
            double price = book.price();
            String[] author = book.author();
            System.out.println(name);
            System.out.println(price);
            System.out.println(Arrays.toString(author));
        }

        //解析方法上面的注解
        Method buyMethod = cls.getMethod("buy");
        if (buyMethod.isAnnotationPresent(Book.class)) {
            Book book = buyMethod.getAnnotation(Book.class);
            String name = book.value();
            double price = book.price();
            String[] author = book.author();
            System.out.println(name);
            System.out.println(price);
            System.out.println(Arrays.toString(author));
        }
    }
}

20.10 设计模式 - 代理模式


20.10.1 代理模式介绍

为什么要有 “代理” ?

在代码设计中,代理模式作用主要就是让   "被代理对象"  的某个方法执行之或者执行之加入其他增强逻辑。

前增强 : 例如获取当前时间

被代理对象调用方法

后增强 : 例如获取当前时间

计算方法执行的时间

20.10.2 代理的前提条件

意味着被代理角色和代理角色有着共同的父类型(既抽象角色) , 例如我要租房子, 我只能找房产中介, 不能找票贩子

20.10.3 代理模式的两种实现方式

20.11 静态代理模式


20.11.1 静态代理模式的介绍

20.11.2 静态代理实现的步骤

20.11.3 静态代理案例

案例:以现实中经纪人代理明星

已知存在接口:

// 1.抽象角色
interface Star {
    // 真人秀方法
    double liveShow(double money);

    void sleep();
}

定义被代理类:


// 定义被代理角色(宝强)
class BaoQiang implements Star {

    @Override
    public double liveShow(double money) {
        System.out.println("参加了真人秀节目 , 赚了" + money + "元");
        return money;
    }

    @Override
    public void sleep() {
        System.out.println("宝强累了 , 睡觉了...");
    }
}

定义代理类:

// 定义代理角色(宋喆),增强被代理角色的功能
class SongZhe implements Star {
    BaoQiang baoQiang = new BaoQiang();

    @Override
    public double liveShow(double money) {// 1000
        // 前增强
        System.out.println("宋喆帮宝强接了一个真人秀的活动,获取佣金" + money * 0.8 + "元");

        double v = baoQiang.liveShow(money * 0.2);

        // 后增强
        System.out.println("宋喆帮宝强把赚的钱存起来...");

        return v;
    }

    @Override
    public void sleep() {
        System.out.println("宋喆帮宝强找了一家五星级大酒店...");
        baoQiang.sleep();
        System.out.println("宋喆帮宝强退房..");
    }
}

定义测试类进行测试

public class StaticAgentDemo {
    public static void main(String[] args) {
        // 被代理角色
        BaoQiang baoQiang = new BaoQiang();
        double v = baoQiang.liveShow(1000);
        System.out.println(v);
        baoQiang.sleep();

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

        SongZhe songZhe = new SongZhe();
        double v1 = songZhe.liveShow(1000);
        System.out.println(v1);
        songZhe.sleep();
    }
}

关系图 :宋喆和宝强都有共同的父类型。他们的业务方法都是一样。

20.11.4 静态代理和装饰模式的区别

相同:

不同

注意:设计模式本身是为了提升代码的可扩展性,灵活应用即可,不必生搬硬套,非要分出个所以然来,装饰器模式和代理模式的区别也是如此

20.12  动态代理模式

20.12.1 动态代理模式介绍

20.12.2 动态代理相关API

//获取代理对象的方法
public static Object newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h )  

/**解析:
- 返回值:该方法返回就是动态生成的代理对象

- 参数列表说明:
  1. ClassLoader loader 	- 定义代理类的类加载器 = 被代理对象.getClass().getClassLoader();
  2. Class<?>[] interfaces 	- 代理类要实现的接口列表,要求与被代理类的接口一样。 = 被代理对象.getClass().getInterfaces();
  3. InvocationHandler h 	- 调用处理器:就是具体实现代理逻辑的接口
*/
interface InvocationHandler{
	public Object invoke(Object proxy, Method method, Object[] args);  //代理逻辑
    
    1. 返回值:方法被代理后执行的结果。
    2. 参数列表:
   		1. proxy  - 就是代理对象
  		2. method - 
  	 	3. args   - 代理对象调用方法传入参数值的对象数组.
}

20.12.3 动态代理案例

需求:将经纪人代理明星的案例使用动态代理实现

分析

  1. 把父接口定义
  2. 定义被代理类:宝强
  3. 动态生成代理类对象
  4. 创建执行代理逻辑的调用处理器
  5. 通过代理对象执行代理方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author Carl Zhang
 * @description 动态代理模式对BaoQiang的show方法进行处理
 * @date 2022/1/8 14:37
 */
public class DynamicProxy {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.获取被代理的对象
        BaoQiang baoQiang = new BaoQiang();

        Class<?> aClass = aClass = baoQiang.getClass(); //被代理类的字节码对象
        ClassLoader classLoader = aClass.getClassLoader(); //被代理类的类加载器
        Class<?>[] interfaces = aClass.getInterfaces(); //被代理类实现的所以的接口的数组

        //获取自定义的调用处理器对象 -- 即真正执行代理逻辑的对象
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(baoQiang);

        //2. 获取被代理类的代理对象
        Star songZhe = (Star) Proxy.newProxyInstance(classLoader,
                interfaces, myInvocationHandler);

        //3. 通过代理对象执行要代理的方法 -- 此处会执行调用处理器的invoke方法
        //double v = songZhe.liveShow(1000); -- 如果方法执行完返回null,会报空指针异常
        songZhe.liveShow(1000);
        songZhe.sleep();
    }
}

/**
 * 创建代理对象的调用处理器 -- 用来执行代理的逻辑
 */
@SuppressWarnings("ALL")
class MyInvocationHandler implements InvocationHandler {
    BaoQiang baoQiang; //被代理的对象

    public MyInvocationHandler(BaoQiang baoQiang) {
        this.baoQiang = baoQiang;
    }

    /**
     * 代理行为 - 代理对象的所有方法都会执行此处
     * @param proxy  代理对象,即songZhe
     * @param method 被代理的方法 , 即BaoQiang的show方法的Method对象
     * @param args   被代理方法的参数
     * @return 代理方法执行后的结果
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = null; //用来保存被代理的方法执行的结果

        //如果是show方法就执行代理逻辑
        if (method.getName().equals("liveShow")) {
            double money = (double) args[0];

            //前增强
            System.out.println("宋喆帮宝强接真人秀,赚了" + money * 0.2);

            //查看proxy的运行时类型 -- com.heima.agent.$Proxy0 ,匿名内部类 即动态创建的代理类
            //System.out.println(proxy.getClass().getName());

            //被代理对象执行被代理方法 -- 即baoqiang执行liveShow方法
            //Object money = method.invoke(proxy, args[0]);
            invoke = method.invoke(baoQiang, money * 0.8);

            //后增强
            System.out.println("宋哲帮宝强存钱");
            return invoke;
        }

        invoke = method.invoke(baoQiang, args);
        return invoke;
    }
}


//1.抽象角色
interface Star {
    double liveShow(double money);

    void sleep();
}

//2.被代理角色
class BaoQiang implements Star {

    @Override
    public double liveShow(double money) {
        System.out.println("宝强参加了一个真人秀节目,赚了" + money + "元");
        return money;
    }

    @Override
    public void sleep() {
        System.out.println("宝强累了,睡觉了!!");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

20.12.4 动态代理调用流程

image.png

20.12.5 动态代理的缺点

只能针对接口的实现类做代理对象,普通类是不能做代理对象的。后面框架学习的时候会接触到 CGLibCode Genneration Library )可以实现对类的代理

标签:反射,二十,对象,代理,class,public,完结,Class,加载
来源: https://www.cnblogs.com/Carl-Zhang/p/15779020.html