ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

# 二十、反射(完结)

2022-01-08 18:31:31  阅读:176  来源: 互联网

标签:反射 二十 对象 代理 class public 完结 Class 加载


二十、反射


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 加载

  • 通过类的全类名(包名和类名) , 查找此类的字节码文件,把类的.class 文件中的二进制数据流读入到内存中,并存放在运行时数据区的方法区内,然后利用字节码文件创建一个 Class 对象,用来封装类在方法区内的数据结构并存放在堆区内。
  • 简单来说 : 就是把硬盘中的字节码文件 , 加载到 jvm 中的方法区以字节码对象的形式存在 , 并在堆内存中创建此类对象

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

  • 启动类加载器( Bootstrap ClassLoader ):用于加载系统类库 <JAVA_HOME>\bin目录下的class ,例如:rt.jar
  • 扩展类加载器( Extension ClassLoader ):用于加载扩展类库 <JAVA_HOME>\lib\ext 目录下的 class
  • 应用程序类加载器( Application ClassLoader ):用于加载我们自定义类的加载器。
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
  }
}

总结:

  • 在程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,同时我们还可以自定义类加载器
  • 需要注意的是,java 虚拟机对class 文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的 class 文件加载到内存中生成 class 对象
  • 而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式,即把加载类的请求交由父加载器处理,它是一种任务委派模式

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 双亲委派模型的优点

  • 避免类的重复加载
    • 当父类加载器已经加载了该类时,就没有必要子 ClassLoader  再加载一次 , 字节码文件只加载一次
  • 考虑到安全因素,java 核心 api  中定义类型不会被随意替换,假设通过网络传递一个名为 java.lang.Object 的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心 Java API 发现这个名字的类,发现该类已被加载,并不会重新加载网络传递过来的 java.lang.Object ,而直接返回已加载过的 Object.class这样便可以防止核心 **API**  库被随意篡改!!!
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 反射的引入

  • 需求:如何根据配置文件 re.properties  里的不同的信息,创建指定的对象,调用各自的方法
  • 以前的方式:读取配置文件里的信息,通过 switch 格式判断class 键对应的值,创建不同的对象,调用方法
  • 问题:代码冗余,不方便
  • 引入反射:实现动态创建对象,动态调用方法,这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的 ocp 原则(开闭原则:不修改源码,扩容功能)

20.4.2 反射的概念

  • 反射是一种机制,利用该机制可以在程序运行过程中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性
  • 利用反射可以无视修饰符获取类里面所有的属性和方法。先获取配置文件中的信息,动态获取信息并创建对象和调用方法

20.4.3 反射的使用前提和场景

  • 使用反射操作类成员的前提
    • 要获得该类字节码文件对象,就是 Class  对象
  • 反射在实际开发中的应用:
    • 开发 IDE (集成开发环境),比如 IDEAEclipse
    • 各种框架的设计和学习 比如 SpringHibernateStructMybaits ....

20.5  Class对象的获取方式


20.5.1 三种获取方法

  • 方式1: 类名.class 获得

  • 方式2:对象名.getClass() 方法获得,该方法是继承 Object  类的

  • 方式3:Class 类的静态方法获得: static Class forName("类全名")

    • 每一个类的 Class 对象都只有一个。
  • 示例代码

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

  • String getSimpleName();  获得类名字符串:类名
  • String getName(); 获得类全名:包名+类名
  • T newInstance() ;   创建 Class对象关联类的对象

示例代码

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类概述

  • 反射之操作构造方法的目的
    • 获得 Constructor 对象来创建类的对象。
  • Constructor 类概述
    • 类中的每一个构造方法都是一个 Constructor 类的对象

20.6.2 Class类中与Constructor相关的方法

  • Constructor getConstructor(Class... parameterTypes) :返回单个公共构造方法对象
  • Constructor getDeclaredConstructor(Class... parameterTypes) :返回单个构造方法对象,不受修饰符影响
  • Constructor[] getConstructors() :返回所有公共构造方法对象的数组,只能获得 public
  • Constructor[] getDeclaredConstructors() :返回所有构造方法对象的数组,不受修饰符影响

20.6.3 Constructor对象常用方法

  • T newInstance(Object... initargs) —— 根据指定的参数创建对象
  • void setAccessible(true) :设置"暴力反射"——是否取消权限检查,true 取消权限检查,false 表示不取消

示例代码

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类概述

  • 反射之操作成员方法的目的
    • 操作 Method 对象来调用成员方法
  • Method 类概述
    • 每一个成员方法都是一个 Method 类的对象。

20.7.2 Class类中与Method相关的方法

  • Method getMethod(String name,Class...args); 返回单个公共的成员方法对象
  • Method getDeclaredMethod(String name,Class...args); 返回单个的成员方法对象,不受访问修饰符限制
  • Method[] getMethods(); 返回所有公共的成员方法对象,包括继承的
  • Method[] getDeclaredMethods(); 返回所有的成员方法对象,不包括继承的

20.7.3  Method对象常用方法

  • Object invoke(Object obj, Object... args)
    • 调用指定对象 obj 的该方法
    • args: 调用方法时传递的参数
    • 没有返回值,则返回 null
  • void setAccessible(true)
    设置"暴力访问"——是否取消权限检查,true 取消权限检查,false 表示不取消

示例代码

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类概述

  • 反射之操作成员变量的目的
    • 通过 Field 对象给对应的成员变量赋值和取值
  • Field 类概述
    • 每一个成员变量都是一个 Field 类的对象。

20.8.2 Class类中与Field相关的方法

  • Field getField(String name); 返回单个公共的成员变量对象
  • Field getDeclaredField(String name);  返回单个成员变量,不受修饰符限制
  • Field[] getFields(); 返回所有的公共的成员变量对象
  • Field[] getDeclaredFields(); 返回所有的成员变量对象

20.8.3 Field对象常用方法

  • void set(Object obj, Object value) :给对象obj 的属性设置值
  • Object get(Object obj) :获取对象obj 对应的属性值
  • void setAccessible(true); 暴力反射,设置为可以直接访问私有类型的属性。
  • Class getType(); 获取属性的类型,返回 Class 对象。

示例代码

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 接口。

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

  • 解析类上的注解:借助字节码对象(Class对象)
  • 解析构造方法上的注解 :借助构造器对象(Constructor对象)
  • 解析方法上的注解 :借助方法对象(Method对象)
  • 解析字段上的注解 :借助字段对象(Field对象)

相关方法:

  • isAnnotationPresent() :判断是否存在注解
  • getAnnotation():如果存在获取注解对象

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();
}

定义被代理类:

  • 定义王宝强类,实现Star方法

// 定义被代理角色(宝强)
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 动态代理模式介绍

  • 在实际开发过程中往往我们自己不会去创建代理类而是通过JDK 提供的Proxy类在程序运行时,运用反射机制动态创建而成这就是我们所谓的动态代理
  • 与静态代理之间的区别,在于不用自己写代理类
  • 虽然我们不需要自己定义代理类创建代理对象,但是我们要定义对被代理对象直接访问方法的拦截,原因就是对拦截的方法做增强
  • 动态代理技术在框架中使用居多,例如:很快要学到的数据库框架 MyBatis 框架等后期学的一些主流框架技术(SpringSpringMVC )中都使用了动态代理技术。

20.12.2 动态代理相关API

  • Proxy类
    • java.lang.reflect.Proxy类提供了用于创建动态代理类和对象的静态方法
    • 它还是由这些方法创建的所有动态代理类的超类(代理类的父类是 Proxy )。
//获取代理对象的方法
public static Object newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h )  

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

- 参数列表说明:
  1. ClassLoader loader 	- 定义代理类的类加载器 = 被代理对象.getClass().getClassLoader();
  2. Class<?>[] interfaces 	- 代理类要实现的接口列表,要求与被代理类的接口一样。 = 被代理对象.getClass().getInterfaces();
  3. InvocationHandler h 	- 调用处理器:就是具体实现代理逻辑的接口
*/
  • **InvocationHandler**** 接口**
    • java.lang.reflect.InvocationHandler是代理对象实际处理代理逻辑的接口,具体代理实现逻辑在其 invoke 方法中。
    • 所有代理对象调用的方法,执行是都会经过**invoke**。因此如果要对某个方法进行代理增强,就可以在这个 invoke 方法中进行定义。
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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有