JavaSE(17)_反射与动态代理

目录

反射描述

理解Class类并获取Class实例 

 java.lang.Class类的理解

Class类的常用方法

获取Class实例的4种方式

Class实例对应的结构的说明

哪些类型可以有Class对象?

类的加载与ClassLoader的理解

类的加载过程

什么时候会发生类初始化?

ClassLoader的类加载器将指定类和配置文件加载到内存中

特定的ClassLoader的类加载器特定的类

ClassLoader加载配置文件

通过反射,创建运行时类的对象

反射的动态性

通过反射获取运行时类的完整结构

提供结构丰富Person类

获取运行时类的属性结构及其属性内部结构

获取运行时类的方法结构及方法内部结构

获取运行时类的构造器结构(没有获取父类的构造器)

获取运行时类的父类(的运行时类)及父类的泛型

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

调用运行时类指定结构(属性、方法、构造器)

反射的应用:动态代理

代理模式与动态代理

静态代理代码举例

动态代理代码举例

AOP与动态代理举例


反射描述

  • Reflection(反射)是被视为动态语言(只有运行时候才知道要创建什么对象)的关键,反射机制允许程序在执行(javac)期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。因为这些信息都是在运行状态后才能获取的

  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

  • 动态语言

    • 是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

  • 静态语言

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

    • Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,是因为我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
    • Java反射机制提供的功能
      • 在运行时判断任意一个对象所属的类
      • 在运行时构造任意一个类的对象
      • 在运行时判断任意一个类所具有的成员变量和方法
      • 在运行时获取泛型信息
      • 在运行时调用任意一个对象的成员变量和方法
      • 在运行时处理注解
      • 生成动态代理
    • 反射相关的主要API
      • java.lang.Class:代表一个类
      • java.lang.reflect.Method:代表类的方法
      • java.lang.reflect.Field:代表类的成员变量
      • java.lang.reflect.Constructor:代表类的构造器

代码演示
反射前创建对象和调用非私有对象属性和方法、反射后创建对象和调用任意对象属性和方法

  1. 在开发中直接使用new的方式调用公共结构
  2. new的方式创建的对象是在编译前确定下来的,而编译时不确定要造哪个类的对象,通过反射就可以动态创建要造那个类的对象。
    比如服务器在运行中,用户通过客户端发送注册请求URL,然后服务器收到注册请求URL携带的参数,解析参数后需要使用一个注册类的实例对象来存放用户名和密码等信息,这时候反射机制就可以动态地创建注册类实例对象。同理,如果用户通过客户端发送登录请求URL,登录类也可以通过反射动态地创建登录类的实例对象接受登录参数......
  3. 反射创建对象和面向对象的封装性private没有矛盾,在设计类时,一些私有化的资源是在提示你这些方法也许不是直接调用的,而是有别的的方法去调用会比较好。因此面向对象的封装性是提示你怎么调用是该类设计的初衷和建议。
package javase17;

import javase17.pojo.Person;
import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Reflect1 {
    //反射之前,对于Person的操作
    @Test
    public void test(){

        //1.创建类的对象
        Person p1 = new Person("jay",21);

        //2.调用对象,调用其内部的属性和方法
        p1.age = 15;
        System.out.println(p1.toString());

        p1.show();

        //在Person类的外部,不可以通过Person类的对象调用其内部私有的结构。比如:name、showNation以及私有的构造器。

    }

    //反射之后 ,堆与Person的操作
    @Test
    public void test2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        Class clazz = Person.class;//实例化Class类

        //通过反射方式,通过Class类对象的构造器,获取Person类的构造器,Class类构造器参数和Person类中的构造器要对应,需要抛出异常NoSuchMethodException可能无此类方法异常
        Constructor cons = clazz.getConstructor(String.class,int.class);//这里是获取Person类非私有的构造器public Person(String name, int age)

        //通过Person类的含参构造创建Person类的对象(除了new以外创建对象的第二种方法),需要抛出多个异常IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException
        Object obj = cons.newInstance("Jon",18);

        //这里不用多态方式因此可以强转成Person类对象
        Person p = (Person) obj;

        System.out.println(p.toString());//Person{name='Jon', age=18}

        //通过反射,调用对象指定的属性和方法
        //调用属性
        Field age = clazz.getDeclaredField("age");//指定类的属性
        age.set(p,10);//指定类属性的类实例对象的属性值
        System.out.println(p/*.toString()*/);//Person{name='Jon', age=10}

        //调用方法
        Method show = clazz.getDeclaredMethod("show");//getDeclaredMethod指定类的方法,参数有指定类的方法名,方法的形参列表的参数(类方法的重载),这里我们调用的show方法没有参数
        show.invoke(p);//你好,我是🔔   ,指定类方法的类实例对象的方法

        //通过反射,是可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
        //调用私有的构造器
        Constructor cons2 = clazz.getDeclaredConstructor(String.class);//这里是获取Person类私有的构造器private Person(String name)
        cons2.setAccessible(true);
        Person p1 = (Person) cons2.newInstance("kalo");//调用私有构造器创建对象,并给Person类私有属性name赋值
        System.out.println(p1);//Person{name='kalo', age=0}

        //调用私有的属性
        Field name = clazz.getDeclaredField("name");//指定私有的属性
        name.setAccessible(true);
        name.set(p1,"Taoyao");//设置私有属性值
        System.out.println(p1);//Person{name='Taoyao', age=0}

        //调用私有的方法
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);//指定方法并指定参数,该方法为private String showNation(String nation)
        showNation.setAccessible(true);
        String nation = (String) showNation.invoke(p1,"中国");//我的国籍是:中国    ,执行私有方法,并接受返回值,相当于String nation = p1.showNation("USA")
    }
}

理解Class类并获取Class实例 

 java.lang.Class类的理解

  1. 类的加载过程:
    1. 程序经过Javac.exe命令后,会生成一个或多个字节码文件(.class结尾),每个类对应一个字节码文件。
    2. 接着我们使用java.exe命令对某个字节码文件进行解析运行。相当于将某个字节码文件加载到内存中,此过程就称为类的加载(后面会细说类的加载过程)。加载到内存中的类,我们就称为运行时类(类本身,用类型.class表示),此运行时类,就作为Class类的一个实例。换句话而言,Class类的实例就对应着一个运行时类。
      以前我们总是把通过类创建对象,现在我们可以把类本身看做是对象,是Class类的对象
    3. 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类

Class类的常用方法

获取Class实例的4种方式

  1. 类名.class;
  2. new 类构造器.getClass();
  3. (常用)forName(String classPath);
  4. 现处的.java的public类.loadClass(String classPath);

代码演示获取运行时类(Class类实例)

package javase17;

import javase17.pojo.Person;
import org.junit.Test;

public class Reflect2 {
    @Test
    public void test3() throws ClassNotFoundException {
        //获取运行类(Class类的实例对象)
        //方式一:
        Class<Person> c1 = Person.class;
        System.out.println(c1);//class javase17.pojo.Person

        //方式二:通过运行时类的对象,调用Object类的getClass()方法
        Person p1 = new Person();
        Class c2 = p1.getClass();
        System.out.println(c2);//class javase17.pojo.Person

        //(常用)方式三:调用Class的静态方法:forName(String classPath),classPath为包路径(完全限定的类名)
        //这种方式更能体现反射的动态性,因为编译的时候不会报错,只有运行的时候才知道类的包路径有没有写错,因此常用,上面两种都在编译时写死了
        Class c3 = Class.forName("javase17.pojo.Person");
        System.out.println(c3);//class javase17.pojo.Person

        //以下结果为true都证明该类被加载到内存中,成为了一个运行时类,此运行时类,就作为Class类的一个实例
        System.out.println(c1 == c2);//true
        System.out.println(c1 == c3);//true

        //方式四:使用类的加载器:ClassLoader  (了解)
        ClassLoader classLoader = Reflect2.class.getClassLoader();//获取该测试类的类装入器
        Class c4 = classLoader.loadClass("javase17.pojo.Person");//通过该类装入器加载指定的类的包路径
        System.out.println(c4);//class javase17.pojo.Person

        System.out.println(c1 == c4);//true,证明该类被加载到内存中,成为了一个运行时类,此运行时类,就作为Class类的一个实例
    }
}

Class实例对应的结构的说明

哪些类型可以有Class对象?

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举,只要数组的元素类型(int)与维度(一维数组)一样,就是同一个Class
  • annotation:注解@interface
  • primitivetype:基本数据类型
  • void:代表没有类型 

代码演示哪些类型可以有Class对象

package javase17;

import org.junit.Test;

import java.lang.annotation.ElementType;

public class Reflect3 {
    /**
     * Class实例可以是哪些结构的说明:
     */
    @Test
    public void test1() {
        //类
        Class s1 = Object.class;
        //接口
        Class s2 = Comparable.class;
        //数组
        Class s3 = String[].class;
        //二维数组
        Class s4 = int[][].class;
        //枚举类
        Class s5 = ElementType.class;
        //注解
        Class s6 = Override.class;
        //基本数据类型
        Class s7 = int.class;
        //返回值
        Class s8 = void.class;
        //其本身也算
        Class s9 = Class.class;

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

类的加载与ClassLoader的理解

类的加载过程

说明:当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。如下图

 详细说明:

  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与(后面会细说类加载器)。
  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段(比如int i1 = 0;),这些内存都将在方法区中进行分配。
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  • 初始化:
    • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器,详细查看代码演示)。
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化(例如:super())。
    • 虚拟机会保证一个类的<clinit>方法在多线程环境中被正确加锁和同步,一个类的<clinit>方法只会被执行一次。

代码演示类加载过程

package javase17.pojo;

//1.把该类的字节码文件内容解析加载到内存中,并将下面静态数据转换成方法区运行时数据结构,然后生成一个代表A类的java.lang.Class对象,该对象作为方法区中类数据的访问入口,加载过程需要用类加载器
public class A{
    //2.链接开始,正式为类变量(static)分配内存并设置类变量默认初始值的阶段,也就是m=0
    //3.开始初始化,m的值由<clinit>()方法执行决定,也就是这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于:
    //<clinit>(){m= 300;m=100}
    static {
        m=300;
    }
    static int m = 100;
}

什么时候会发生类初始化?

  • 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动,先初始化main方法所在的类
    • new一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。(是谁的,谁才会被初始化)如,通过子类引用父类的静态变量,不会导致子类初始化。
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

代码演示类何时发生初始化 

package javase17;

public class Reflect11 {
    static {
        System.out.println("main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        /*1.主动引用
        * 1.1 虚拟机启动的时候,先初始化main方法所在的类
        * 1.2 new 一个类的对象
        * 1.3 初始一个类的时候,如果父类没有被初始化,则会先初始化它的父类*/
//        Son son = new Son();//这里注释掉是因为防止这里触发初始化后下面的操作初始化就没有效果了
//        System.out.println("****************");
/*
        main类被加载
        父类被加载
        子类被加载
*/

        /*1.4 对类进行反射调用的时候*/
//        Class.forName("javase17.Son");//这里注释掉是因为防止这里触发初始化后下面的操作初始化就没有效果了
//        System.out.println("****************");
/*
        main类被加载
        父类被加载
        子类被加载
*/

        /*1.5调用类的静态成员(除了final常量)和静态方法*/
//        int i1 = Son.m;//这里注释掉是因为防止这里触发初始化后下面的操作初始化就没有效果了
/*
        main类被加载
        父类被加载
        子类被加载
*/


        /*2.被动引用,不会产生类的引用的方法*/
        /*2.1 当访问一个静态域时,只有真正声明这个域的类才会被初始化。*/
//        System.out.println(Son.b);//静态属性可以直接通过类名来调用,这个成员变量b是从父类中继承回来的,可以调用但是只有一份,这一份是父类声明的,所有只会初始化父类
/*
        main类被加载
        父类被加载
*/

        /*2.2 通过定义数组类引用,不会发生类的初始化*/
        Son[] array = new Son[5];//只加载main方法
/*
        main类被加载
*/

//        int i2 = Son.M;//引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池了),这里注释掉是因为防止这里触发初始化后下面的操作初始化就没有效果了
/*
        main类被加载
*/
    }

}

class Father {
    static int b = 2;//静态资源也可以被子类继承,子类可以通过子类类名.属性调用

    static {
        System.out.println("父类被加载");
    }
}

class Son extends Father {
    static {
        System.out.println("子类被加载");
        m = 300;
    }

    static int m = 100;
    static final int M = 1;

}

ClassLoader的类加载器将指定类和配置文件加载到内存中

特定的ClassLoader的类加载器特定的类

  • 类加载器(类装载器)的作用:
    • 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
    • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
  • (再次强调)类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。
    • java核心类库有String类,效率高但不能自定义使用
    • 自定义的类都是系统加载器帮我们把类装载到内存中

代码演示特定的ClassLoader的类加载器特定的类

package javase17.pojo;

import org.junit.Test;

public class CustomClassLoader {
    @Test
    public void test1(){
        //对于自定义类,使用系统类加载器进行加载,因此这里是获取系统类加载器
        ClassLoader classLoader = CustomClassLoader.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //调用系统类加载器的getParent():获取扩展类加载器
        ClassLoader classLoader1 = classLoader.getParent();//返回委托的父类装入器
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@279f2327

        //调用扩展类加载器的getParent():无法获取引导类加载器,因为引导类加载器主要负责加载java的核心类库,且无法加载自定义类的。
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);//null
        //String类是引导类加载器加载到内存中的,这种方式也无法获取到引导类加载器,原因引导类加载器主要负责加载java的核心类库,且无法加载自定义类的。
        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3);//null
    }
}

ClassLoader加载配置文件

jdbc.properties配置文件(属性列表)

user=小明
password=123456

 

代码演示(在原有的“代码演示特定的ClassLoader的类加载器特定的类”的代码中拓展)

    //ClassLoader加载配置文件
    @Test
    public void test2() throws IOException {
        Properties pros = new Properties();//创建配置文件类

        //读取配置文件的方式一:使用输入流
        //注意!!!!!!配置文件默认识别为:当前module下
//        FileInputStream fis = new FileInputStream("test\\javase17\\pojo\\jdbc.properties");//通过输入流指向配置文件
//        pros.load(fis);//加载配置文件,抛出IOException异常

        //读取配置文件的方式二:使用ClassLoader
        ClassLoader classLoader = CustomClassLoader.class.getClassLoader();
        //配置文件默认识别为:当前module的src下,如果是在test就是在test下
        InputStream is = classLoader.getResourceAsStream("javase17\\pojo\\jdbc.properties");
        pros.load(is);

        //读取获取到的配置文件信息
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = " + user + ",password = " + password);//user = 小明,password = 123456
    }

通过反射,创建运行时类的对象

  • newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器

  • 要想此方法正常的创建运行时类的对象,要求:

    • 运行时类必须提供空参的构造器

    • 空参的构造器的访问权限得够。通常,设置为public。

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

    • 便于通过反射,创建运行时类的对象

    • 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

代码演示通过反射 创建运行时类的对象

package javase17;

import javase17.pojo.Person;
import org.junit.Test;

import java.util.Random;

public class Reflect4 {
    //通过反射创建对应的运行时类的对象
    @Test
    public void test1() throws Exception {
        Class<Person> clazz = Person.class;//通过泛型参数设置确定运行时类的所有泛型
        /**
         * newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
         *
         * 要想此方法正常的创建运行时类的对象,要求:
         * 1.运行时类必须提供空参的构造器
         * 2.空参的构造器的访问权限得够。通常,设置为public。
         *
         * 在javabean中要求提供一个public的空参构造器。原因:
         * 1.便于通过反射,创建运行时类的对象
         * 2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
         */
        //newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
        Person obj = clazz.newInstance();//这个就是我们的Person类对象
        System.out.println(obj);//Person{name='null', age=0}
    }
}

反射的动态性

说明:将创建哪个类的主动权交给用户,而不是用new构造构造器方式写死在系统中 

代码演示反射的动态性(在Reflect4类中补充)


    //反射的动态性
    @Test
    public void test2(){
        for(int i = 0;i < 100;i++){
            int num = new Random().nextInt(3);//0,1,2中选一个整数,java特性含头不含委
            String classPath = "";
            switch(num){
                case 0:
                    classPath = "java.util.Date";
                    break;
                case 1:
                    classPath = "java.lang.Object";
                    break;
                case 2:
                    classPath = "javase17.pojo.Person";
                    break;
            }
            try {
                //自定义的方法获取类的实例,但这个对象时不确定的,只需要用户提供指定的全类名字符串,服务端就知道创建哪一个对象
                Object obj = getInstance(classPath);
                System.out.println(obj);//打印获取类的实例,随机打印100次
                /*
                * java.lang.Object@722c41f4
                * Sat Jul 30 20:28:10 CST 2022
                * Person{name='null', age=0}
                * */
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public Object getInstance(String classPath) throws Exception {
        Class clazz =  Class.forName(classPath);//指定类的全类名来获取该类对应的Class实例
        return clazz.newInstance();//返回该类的实例
    }

通过反射获取运行时类的完整结构

提供结构丰富Person类

file:/E:/JavaSE/Project/test/javase17/pojo/wholepojo/Creature.java

package javase17.pojo.wholepojo;

import java.io.Serializable;

/**Person类的父类*/
public class Creature<T> implements Serializable {//父类带泛型参数,实现了序列化接口
    private char gender;//性别
    public double weight;//体重

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

    public void eat(){
        System.out.println("生物吃饭");
    }
}

file:/E:/JavaSE/Project/test/javase17/pojo/wholepojo/MyAnnotation.java

package javase17.pojo.wholepojo;

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

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})//使用范围
@Retention(RetentionPolicy.RUNTIME)//生命周期,反射是在运行时获取的,因此我们这里要把生命周期改成RUNTIME运行时也要存活
public @interface MyAnnotation {
    String value() default "hello";//带有成员变量的元数据注解,只有一个参数成员,并设定了默认参数为hello
}

file:/E:/JavaSE/Project/test/javase17/pojo/wholepojo/MyInterface.java

package javase17.pojo.wholepojo;

public interface MyInterface {
    void info();
}

file:/E:/JavaSE/Project/test/javase17/pojo/wholepojo/Person.java

package javase17.pojo.wholepojo;


@MyAnnotation(value = "hi")//在Person类上使用自定义注解,并重新指定值
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){
        return interests;
    }

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

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

获取运行时类的属性结构及其属性内部结构

package javase17;

import javase17.pojo.wholepojo.Person;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class Reflect5 {
    //获取当前运行类的属性结构
    @Test
    public void fieldTest1(){
        Class clazz = javase17.pojo.wholepojo.Person.class;

        //getFields()获取运行时类及其父类所有public权限修饰符的属性
        Field[] fields = clazz.getFields();

        for(Field field : fields){
            System.out.println(field.toString());
            /*
            * public int javase17.pojo.wholepojo.Person.id
            * public double javase17.pojo.wholepojo.Creature.weight
            * */
        }
        System.out.println("**************************");

        //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
            /*
            * private java.lang.String javase17.pojo.wholepojo.Person.name
            * int javase17.pojo.wholepojo.Person.age
            * public int javase17.pojo.wholepojo.Person.id
            * */
        }

        System.out.println("**************************");
    }

    //获取当前运行时类中声明的属性内部结构。(不包含父类中声明的属性)的权限修饰符、数据类型、变量名
    @Test
    public void fieldTest2() {
        Class clazz = Person.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            //1.权限修饰符
            int modifier = f.getModifiers();
            System.out.print(Modifier.toString(modifier) + "\t");//"\t"表示制表符
            System.out.println("+++++++++++++++++++++++++++");
            //2.数据类型
            Class type = f.getType();
            System.out.print(type.getName() + "\t");
            System.out.println("***************************");
            //3.变量名
            String fName = f.getName();
            System.out.print(fName + "\t");
            System.out.println();
        }
        /*
        * private	+++++++++++++++++++++++++++
        * java.lang.String	***************************
        * name
        * 	+++++++++++++++++++++++++++
        * int	***************************
        * age
        * public	+++++++++++++++++++++++++++
        * int	***************************
        * id
        * */
    }
}

获取运行时类的方法结构及方法内部结构

package javase17;

import javase17.pojo.wholepojo.Person;
import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * 获取运行时类的方法结构
 */
public class Reflect6 {
    //获取方法结构
    @Test
    public void methodTest1() {
        Class clazz = Person.class;


        Method[] methods = clazz.getMethods();//getMethods()方法获取所有的指定运行时类及其父类以上(Object类)的public修饰符的方法
        for(Method m : methods){
            System.out.println(m);
        }

        System.out.println("************************");
        /*
        public int javase17.pojo.wholepojo.Person.compareTo(java.lang.String)  #跟泛型有关系
        public int javase17.pojo.wholepojo.Person.compareTo(java.lang.Object)
        public void javase17.pojo.wholepojo.Person.info()
        public java.lang.String javase17.pojo.wholepojo.Person.display(java.lang.String)
        public void javase17.pojo.wholepojo.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()
        */

        Method[] declaredMethods = clazz.getDeclaredMethods();//getDeclaredMethods()只获取当前运行类的所有方法结构
        for(Method m : declaredMethods){
            System.out.println(m);
        }
        /*
        public int javase17.pojo.wholepojo.Person.compareTo(java.lang.String)
        public int javase17.pojo.wholepojo.Person.compareTo(java.lang.Object)
        public void javase17.pojo.wholepojo.Person.info()
        public java.lang.String javase17.pojo.wholepojo.Person.display(java.lang.String)
        private java.lang.String javase17.pojo.wholepojo.Person.show(java.lang.String)
        */
    }

    //只获取运行时类的方法内部结构 权限修饰符 返回类型 方法名(参数类型1 形参名1,...) 方法抛出的异常
    @Test
    public void methodTest2() {
        Class clazz = Person.class;
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            //1.获取方法声明的注解,不包括构造方法,该注解必须在运行时都存活着,否则无法获取
            Annotation[] annos = m.getAnnotations();//一个方法上可以声明多个注解,因此可以这里使用注解数组Annotation[]
            for (Annotation a : annos) {
                System.out.println(a+"\t"+"注解");//这里执行顺序不同是因为内部执行顺序不同
            }

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

            //3.返回值类型
            System.out.print(m.getReturnType().getName() + "\t");

            //4.方法名
            System.out.print(m.getName());
            System.out.print("(");
            //5.形参列表
            Class[] pTs = m.getParameterTypes();
            if(!(pTs == null && pTs.length == 0)){//判断是否有参数
                for(int i = 0;i < pTs.length;i++){
                    if(i == pTs.length - 1){
                        System.out.print(pTs[i].getName() + " args_" + i);
                        break;
                    }
                    System.out.print(pTs[i].getName() + " args_" + i + ",");
                }
            }
            System.out.print(")");

            //6.抛出的异常
            Class[] eTs = m.getExceptionTypes();
            if(/*!(eTs == null && eTs.length == 0),该方法不能过滤没有抛出异常的方法*/eTs.length>0){
                System.out.print("throws ");
                for(int i = 0;i < eTs.length;i++){
                    if(i == eTs.length - 1){
                        System.out.print(eTs[i].getName());
                        break;
                    }
                    System.out.print(eTs[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(){}
    @javase17.pojo.wholepojo.MyAnnotation(value=hello)	注解
    private	java.lang.String	show(java.lang.String args_0){}
    public	java.lang.String	display(java.lang.String args_0)throws java.lang.NullPointerException,java.lang.ClassCastException{}

*/
}

获取运行时类的构造器结构(没有获取父类的构造器)

package javase17;

import javase17.pojo.wholepojo.Person;
import org.junit.Test;

import java.lang.reflect.Constructor;

/**
 * 获取构造器结构
 */
public class Reflect7 {
    //获取当前运行时类所有构造器
    @Test
    public void test1() {
        Class clazz = Person.class;
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor c : declaredConstructors) {
            System.out.println(c);
        }
/*
        javase17.pojo.wholepojo.Person(java.lang.String, int)
        private javase17.pojo.wholepojo.Person(java.lang.String)
        public javase17.pojo.wholepojo.Person()
*/

    }

    //获取当前运行时类public的构造器
    @Test
    public void test2() {
        Class clazz = Person.class;
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c);
        }

/*
        public javase17.pojo.wholepojo.Person()
*/
    }
}

获取运行时类的父类(的运行时类)及父类的泛型

用途:一个类继承了一个带有泛型的父类,然后声明泛型参数。此类所有方法和属性就有具体的数据类型了。以后我们写sql语句从数据库中获取字段+值时就需要用具体的数据类型的对象的属性和属性值来存储。也就是把sql查询的数据用java对象来存储。

package javase17;

import javase17.pojo.wholepojo.Person;
import org.junit.Test;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**获取运行时类的父类及父类的泛型*/
public class Reflect8 {
    @Test
    public void test1() {
        Class clazz = Person.class;

        //获取运行时类的父类(的运行类)
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);//class javase17.pojo.wholepojo.Creature

        //获取运行时类带泛型的父类(的运行时类)的类型
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);//javase17.pojo.wholepojo.Creature<java.lang.String>
    }

    //获取运行时类带泛型的父类(的运行类)的泛型类型
    @Test
    public void test2() {
        Class clazz = Person.class;

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

        ParameterizedType paramType = (ParameterizedType) genericSuperclass;//强转成Type类型类的子类_带参数的类型
        Type[] actualTypeArguments = paramType.getActualTypeArguments();//获取泛型类型参数数组,这里是数组的原因是泛型类型参数可能不止一个,如Map就有两个参数KV
        for(Type t : actualTypeArguments){
//            System.out.println(t);//class java.lang.String,获取类型参数
//            System.out.println(t.getTypeName());//java.lang.String,通过类型参数获取类型名称
            System.out.println(((Class)t).getName());//java.lang.String,通过强转成子类Class后获取运行时类的名称
        }
    }
}

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

说明:在实现接口中,有使用代理设计模式。代理类和被代理类都实现同一个接口,代理类对象作为参数传入到被代理类的构造器中创建被代理类的对象,代理类此时拥有被代理类的对象,意味着可以调用被代理类的方法,也有被代理类对象为代理类拓展的方法,因为他们两实现的是同一个接口。
回顾完代理模式了,我们在结合反射,先不指明代理类是谁,而是在java运行时,根据你被代理类来造一个代理类。此时我们就可以通过加载被代理类,然后通过反射分别获取被代理类实现的接口、代理类实现的接口,两者对应之后再创建代理类。(后面讲JDK动态代理细说)

package javase17;

import javase17.pojo.wholepojo.Person;
import org.junit.Test;

import java.lang.annotation.Annotation;

/**
 * 获取运行时类的接口、所在包、注解等
 */
public class Reflect9 {
    //获取运行时类的接口
    @Test
    public void test1() {
        Class clazz = Person.class;
        //获取该运行时类实习的接口
        Class[] interfaces = clazz.getInterfaces();//是数组的原因是因为java多实现
        for (Class i : interfaces) {
            System.out.println(i);
        }
/*
        interface java.lang.Comparable
        interface javase17.pojo.wholepojo.MyInterface
*/
        //获取该运行时类的父类实现的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for (Class i : interfaces1) {
            System.out.println(i);
        }
/*
        interface java.io.Serializable
*/
    }

    //获取运行时类所在的包
    @Test
    public void test2() {
        Class clazz = Person.class;

        //获取运行时类所在的包
        Package aPackage = clazz.getPackage();
        System.out.println(aPackage);
/*
        package javase17.pojo.wholepojo
*/
        //获取运行时类的父类所在的包
        Package aPackage1 = clazz.getSuperclass().getPackage();
        System.out.println(aPackage1);
/*
        package javase17.pojo.wholepojo
*/
    }

    //获取运行时类上声明的注解
    @Test
    public void test3() {
        Class<Person> personClass = Person.class;
        //在使用时确保注解是java运行时也是存活的,否则无法获取(反射特性)
        Annotation[] annotations = personClass.getAnnotations();//这里是数组的原因是因为注解可以声明多个
        for(Annotation a : annotations){
            System.out.println(a);
        }
/*
        @javase17.pojo.wholepojo.MyAnnotation(value=hi)
*/
    }
}

调用运行时类指定结构(属性、方法、构造器)

package javase17;

import javase17.pojo.wholepojo.Person;
import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 调用运行时类中指定的结构:属性、方法、构造器
 */
public class Reflect10 {
    @Test
    public void testField() throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        Class<Person> clazz = Person.class;

        //通过反射,内部调用运行时类空参构造器创建运行是类的对象
        Person p1 = clazz.newInstance();

        //.getField(String name)方法调用运行时类的指定的public修饰的属性,需要抛出异常,因为可能找不到该属性
//        Field id = clazz.getField("id");
//        //为指定对象的指定属性的值赋值,因为修饰符是public,因此与变量.属性无异
//        id.set(p1, 100);
//        p1.id = 200;
//        //获取当前属性值,因为修饰符是public,因此与变量.属性无异
//        System.out.println(p1.id);//200
//        System.out.println(id.get(p1));//200

        //鉴于上面的.getField(String name)只能用于public 修饰的属性,我们采用如下方法
        //使用getDeclaredField(String name)调用运行是类的指定的属性,不受权限修饰符限制,需要抛出异常
        Field age = clazz.getDeclaredField("age");
        //为指定对象的指定属性的值赋值,因为修饰符是缺省而非public,即使拿到却不能set和get操作,需要加一个操作静止语法检查才能进行下一步set和get操作
        age.setAccessible(true);//设置当前对象此属性是可访问的,true表示反射对象在使用该属性时应该禁止Java语言访问检查
        age.set(p1, 18);
//        p1.age = 18;//默认修饰符只能同包下访问,因此不能使用变量.属性
        //获取当前属性值
//        System.out.println(p1.age);//默认修饰符只能同包下访问,因此不能使用变量.属性
        System.out.println(age.get(p1));//18

        //修改Person类对象的属性和获取person类的属性,与上同理
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1, "小明");
        System.out.println(name.get(p1));//小明
    }

    @Test
    public void testMethod() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<Person> clazz = Person.class;

        //非静态方法:private String show(String nation)
        Person person = clazz.newInstance();//创建运行时类的对象,需要抛出异常
        //指定要使用运行是类的具体哪个方法,该方法不受修饰符影响,参数为方法名、参数列表的参数对应的运行时类,需要抛出异常
        Method show = clazz.getDeclaredMethod("show", String.class);
        //true表示反射对象在使用该方法时应该禁止Java语言访问检查
        show.setAccessible(true);
        //执行Person类对象的show(String)方法,参数为运行时类的对象、方法参数.并接收返回值,是invoke()方法的返回值,该返回值为对应类中调用方法的返回值,因此值为String类型,但是时多态,所以是Object
        String showReturn = (String)show.invoke(person, "CHN");
        System.out.println(showReturn);//可爱的人


        //静态方法:private static void showDesc()
        Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
/*
        执行Person类对象的静态方法showDesc(),参数只需要指定运行时类或者null就可以了和传给方法参数(要是无参静态方法可以不写)
        第一个参数可以写运行是类或者null时是因为getDeclaredMethod("showDesc")获取的是一个静态的方法,意味着
        意味着该类的实例用的都是同一个方法或者只需要类名就能调用方法,除了可能传入参数不一样其他都一样,就不需要可以指定某个对象
*/
//        Object invoke = showDesc.invoke(Person.class);
        Object invoke = showDesc.invoke(null);
        System.out.println(invoke);//null,因为invoke调用的运行类中的方法没有返回值
    }

    @Test
    public void testConstructor() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/*
        clazz.newInstance();通过反射调用无参构造器创建对象使用最频繁,因为通用性更强.
        比如我们在获取数据转换成java对象时,通过反射调用无参构造器创建对象,然后通过反射调用getter setter方法完成属性赋值,
        而不是通过调用某个构造器方式来给属性赋值,因为通用性差
*/
        //针对具体情况使用自定义构造器
        Class<Person> clazz = Person.class;

        //自定义构造器:private Person(String name)
        //参数只需要传给构造方法的参数运行时类就可以了(指明构造器的参数列表),抛出异常
        Constructor<Person> constructor = clazz.getDeclaredConstructor(String.class);
        //设置构造器可以访问
        constructor.setAccessible(true);
        //调用构造器创建运行时类的对象,参数为传给构造器对应的参数,抛出异常
        Person tom = constructor.newInstance("Tom");
        System.out.println(tom);//Person{name='Tom', age=0, id=0}

    }
}

反射的应用:动态代理

代理模式与动态代理

  • 代理设计模式的原理:

    使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。(代理类来为被代理类拓展功能;对于被代理类自己能干的活由代理类创建被代理类对象并调用被代理类方法。)

  • 之前为大家讲解过代理机制的操作,

    属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能

  • 动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

  • 动态代理使用场合:

    • 调试
    • 远程方法调用
  • 动态代理相比于静态代理的优点

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

静态代理代码举例

特点:代理类和被代理类在编译期间就被确定下来了

package javase17.proxy;

/**
 * 静态代理举例,之前的接口设计模式就有提及过,这里重温一边
 */
public class StaticProxyTest {
    public static void main(String[] args) {
        NikeClothFactory nike = new NikeClothFactory();//创建一个被代理对象
        ProxyClothFactory proxy = new ProxyClothFactory(nike);//创建一个代理对象,并传入被代理对象参数
        proxy.produceCloth();//执行代理类对象方法,并且方法也会调用被代理类方法
/*
        代理工厂做一些准备
        NIKE生产了一批运动服
        代理工厂做一些后续工作
*/
    }
}

/**
 * 被代理类和代理类都实现的接口
 */
interface ClothFactory {
    void produceCloth();//衣服工厂都有生产衣服的功能
}

/**
 * 代理类
 */
class ProxyClothFactory implements ClothFactory {

    private ClothFactory factory;//该变量存放被代理类对象

    public ProxyClothFactory(ClothFactory factory) {
        this.factory = factory;
    }

    @Override
    public void produceCloth() {//实现接口功能
        System.out.println("代理工厂做一些准备");

        factory.produceCloth();

        System.out.println("代理工厂做一些后续工作");
    }
}

/**
 * 被代理类
 */
class NikeClothFactory implements ClothFactory {

    @Override
    public void produceCloth() {
        System.out.println("NIKE生产了一批运动服");
    }
}

动态代理代码举例

动态生成代理类,顺便使用动态生成代理类方法来为之前的静态被代理类动态生成代理类

package javase17.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 动态代理举例,此时的代理类变成了根据被代理类来创建
 */
public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();//创建一个被代理类对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);//传入方法中,动态地创建了一个代理对象
        String belief = proxyInstance.getBelief();
        //当通过代理类对象调用方法时,都会自动的调用被代理类同名的方法
        System.out.println(belief);//I believe I can fly
        proxyInstance.eat("饭");//超人喜欢吃饭

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

        //使用动态生成代理类方法来为之前的静态被代理类动态生成代理类
        NikeClothFactory nikeClothFactory = new NikeClothFactory();//生成了一个代理类
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);//获取代理类对象
        proxyInstance1.produceCloth();//NIKE生产了一批运动服,调用被代理类的produceCloth()方法

    }
}

/**
 * 代理类和被代理类都实现的接口
 */
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);
    }
}

/**
 * 问题:
 * 根据加载到内存的被代理类,动态创建一个被代理类对象及代理类对象
 * 当代理类的对象调用方法a时,代理类也能动态地去调用被代理类的同名方法a
 */
/**生产代理类的工厂类*/
class ProxyFactory {
    /*
        通过该方法创建代理类对象,不用创建生产代理类的工厂类对象,直接通过类名.方法名就可以,该方法需要的参数就是被代理类对象,
        解决问题:"根据加载到内存的被代理类对象,动态创建一个被代理类对象及代理类对象"
    */
    public static Object getProxyInstance(Object obj) {

        MyInvocationHandler handler = new MyInvocationHandler();//实例化实现InvocationHandler接口自定义类
        handler.bind(obj);
        /*
         * Proxy是通过反射的运行时类,通过其newProxyInstance方法输入如下三种有关于被代理类的参数可以返回一个代理类对象,参数分别为
         * 1.将输入进来的被代理类使用的自定义类加载器传入,该加载器用作将代理类装进内存中,
         * 2.被代理类实现的接口代理类也需要,JDK动态代理中,代理类和被代理类要实现同一个接口
         * 3.实现InvocationHandler接口自定义类对象,该类里面的invoke()方法写我们的拓展业务和调用目标对象方法
         * */
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}

//提供Proxy.newProxyInstance()方法需要的参数reflect.InvocationHandler h
class MyInvocationHandler implements InvocationHandler {

    private Object obj;//声明被代理类对象

    /**
     * 实例化被代理类方法
     */
    public void bind(Object obj) {
        this.obj = obj;
    }

    /*
        当我们通过代理类对象调用方法a时,就会自动调用如下的invoke()方法   回忆:我们之前是通过反射调用invoke()方法来调用运行时类(对象)方法,与这个有一丢丢的关系
        因此我们将被代理类要执行的方法a的功能声明在invoke()中,实现代理类对象动态地调用被代理类对象的方法
        invoke方法参数分别为:
        1.代理类对象 Object proxy
        2.调用代理类的什么方法 Method method
        3.代理类对象方法需要的参数 Object[] args
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

/*
        obj:被代理类对象
        args:方法参数
        返回值:我的理解是通过反射调用被代理对象的方法,前提是需要在该实现InvocationHandler类中声明被代理类的对象obj,
        并且该方法是调用被代理类的方法a,因此可能会有返回值返回,因此该方法有返回值,可以把被代理类的返回值返回给代理类,
        代理类再把返回值返回给调用它的方法
*/
        return method.invoke(obj, args);
    }
}

AOP与动态代理举例

说明:前面介绍的Proxy(动态代理)和InvocationHandler(调用被代理方法),很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制

下图说明:

  • 我们三个代码段都用了相同的代码,将这三个代码都用到的相同代码包装在一个类的方法中,然后三个代码传参调用该类的该方法就可以了。但代码段又绑定了调用的方法A,与方法A高度耦合,因此需要在三个代码段中动态生成调用方法A的方法,此时就需要反射

 下图说明:

  • (强调)使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理
  • 下图这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。相当于为目标方法拓展了固定的功能,且动态调用的目标方法

代码演示AOP动态代理 (在原来的test/javase17/proxy/ProxyTest.java中修改)

package javase17.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 动态代理举例,此时的代理类变成了根据被代理类来创建
 */
public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();//创建一个被代理类对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);//传入方法中,动态地创建了一个代理对象
        String belief = proxyInstance.getBelief();
        //当通过代理类对象调用方法时,都会自动的调用被代理类同名的方法
        System.out.println(belief);//I believe I can fly
        proxyInstance.eat("饭");//超人喜欢吃饭

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

        //使用动态生成代理类方法来为之前的静态被代理类动态生成代理类
        NikeClothFactory nikeClothFactory = new NikeClothFactory();//生成了一个代理类
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);//获取代理类对象
        proxyInstance1.produceCloth();//NIKE生产了一批运动服,调用被代理类的produceCloth()方法

        //使用AOP动态代理的执行效果
/*
        ================通用方法1===============
        ================通用方法2===============
        I believe I can fly
        ================通用方法1===============
        超人喜欢吃饭
        ================通用方法2===============
        **********************
        ================通用方法1===============
        NIKE生产了一批运动服
        ================通用方法2===============
*/

    }
}

/**
 * 代理类和被代理类都实现的接口
 */
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);
    }
}

/**
 * 该类提供AOP动态代理,可以在动态代理类调用被代理类方法之前、之后插入一些通用不变的操作。
 * 相当于为目标方法拓展了固定的功能,且动态调用的目标方法
 */
class HumanUtil {
    //动态代理增加的通用方法1
    public void method1() {
        System.out.println("================通用方法1===============");
    }
    //动态代理添加的通用方法2
    public void method2() {
        System.out.println("================通用方法2===============");
    }
}

/**
 * 问题:
 * 根据加载到内存的被代理类,动态创建一个代理类及代理类对象
 * 当代理类的对象调用方法a时,代理类也能动态地去调用被代理类的同名方法a
 */

/**
 * 生产代理类的工厂类
 */
class ProxyFactory {
    /*
        通过该方法创建代理类对象,不用创建生产代理类的工厂类对象,直接通过类名.方法名就可以,该方法需要的参数就是被代理类对象,
        解决问题:"根据加载到内存的被代理类,动态创建一个被代理类对象及代理类对象"
    */
    public static Object getProxyInstance(Object obj) {

        MyInvocationHandler handler = new MyInvocationHandler();//实例化实现InvocationHandler接口自定义类
        handler.bind(obj);
        /*
         * Proxy是通过反射的运行时类,通过其newProxyInstance方法输入如下三种有关于被代理类的参数可以返回一个代理类对象,参数分别为
         * 1.将输入进来的被代理类使用的自定义类加载器传入,该加载器用作将代理类装进内存中,
         * 2.被代理类实现的接口代理类也需要,JDK动态代理中,代理类和被代理类要实现同一个接口
         * 3.实现InvocationHandler接口自定义类对象,该类里面的invoke()方法写我们的拓展业务和调用目标对象方法
         * */
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}

//提供Proxy.newProxyInstance()方法需要的参数reflect.InvocationHandler h
class MyInvocationHandler implements InvocationHandler {

    private Object obj;//声明被代理类对象

    /**
     * 实例化被代理类方法
     */
    public void bind(Object obj) {
        this.obj = obj;
    }

    /*
        当我们通过代理类对象调用方法a时,就会自动调用如下的invoke()方法   回忆:我们之前是通过反射调用invoke()方法来调用运行时类(对象)方法,与这个有一丢丢的关系
        因此我们将被代理类要执行的方法a的功能声明在invoke()中,实现代理类对象动态地调用被代理类对象的方法
        invoke方法参数分别为:
        1.代理类对象 Object proxy
        2.调用代理类的什么方法 Method method
        3.代理类对象方法需要的参数 Object[] args
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        /*AOP动态代理操作*/
        HumanUtil humanUtil = new HumanUtil();
        humanUtil.method1();//AOP动态代理,动态代理类执行目标方法前的操作
/*
        obj:被代理类对象
        args:方法参数
        返回值:我的理解是通过反射调用被代理对象的方法,前提是需要在该实现InvocationHandler类中声明被代理类的对象obj,
        并且该方法是调用被代理类的方法a,因此可能会有返回值返回,因此该方法有返回值,可以把被代理类的返回值返回给代理类,
        代理类再把返回值返回给调用它的方法
*/
        Object invoke = method.invoke(obj, args);

        humanUtil.method2();//AOP动态代理,动态代理类执行目标方法后的操作

        return invoke;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值