JavaEE之反射、注解、代理设计模式

什么是字节码,什么是字节码对象?

  • 让我们重新审视类实例化对象的过程
    • 编译
      • 首先源代码文件Student.java编译为Student.class字节码文件
    • 加载字节码
      • 当做类似于new Student()创建对象操作时,JVM先将文件Student.class读入内存。
      • 将里面的字节码指令、方法定义、成员变量等核心数据存入到JVM方法区中,这部分数据是"死的",它们是类的模板数据。
    • 创建字节码对象(Class对象)
      • JVM会根据加载出的字节码,再JVM堆内存中创建一个字节码对象
      • 这个Class对象内部会持有指向方法区Student类元数据的引用
      • 可以通过Class.forName("Student类的全路径")获取到这个堆中的Class对象。
    • 实例化普通对象:
      • JVM根据堆中的Student.class 字节码对象,找到再JVM方法区中对应的字节码模板
      • 然后根据这个模板,在堆内存中开辟空间,创建出一个真正的Student实例对象
  • 由此可以看出一个类的字节码对象,就是指向其字节码的对象,因此如果我们可以获得字节码对象,就能手动获得类的信息,甚至在没有源码的情况下根据字节码反编译出类源码信息。
  • 所以字节码对象(Class对象)是 JVM 在堆内存中创建的、用于封装和代表方法区中类字节码数据的一个特殊"代理对象",它是连接 Java 代码与底层类结构的唯一入口

反射

什么是反射

  • 反射就是:用字节码对象加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)。
  • 在IDE中的代码提示功能就是利用了反射的原理
  • 反射有哪些知识点要学的
    • 学习获取类的信息、操作它们
      • 反射第一步:加载类,获取类的字节码对象:Class对象
      • 获取类的构造器:Constructor对象
      • 获取类的成员变量:Field对象
      • 获取类的成员方法:Method对象
    • 全部认识完后,再看反射的应用场景

反射:获取字节码对象三种方式

  • 第一步先获得字节码对象

  • 三种方式的语法

    • 通过类名Class c1 = 类名.class
    • 调用Class提供的方法 + 类的全路径:public static Class forName(String package);,package为类的全路径(全类名)
    • 基类Object提供的方法:public Class getClass(); Class c3 = 对象.getClass();
  • 代码示例

    java 复制代码
    package com.ita._02反射第一步获取字节码对象;
    public class Demo021 {
        public static void main(String[] args) throws Exception {
            //目标:反射第一步:或者class对象黑马程
            // Class c1 = 类名.class
            Class clazz1 = Demo021.class;
            // 调用Class提供方法: public static Class forName(String package);
            Class clazz2 = Class.forName("com.ita._02反射第一步获取字节码对象.Demo021");
            // Object提供的方法: public Class getClassO;Class c3 = 对象.getClass();
            Demo021 demo021 = new Demo021();
            Class clazz3 = demo021.getClass();
            System.out.println(clazz1);
            System.out.println(clazz2);
            System.out.println(clazz3);
        }
    }

反射:获取构造器成员

  • 语法

    • Class提供了从类中获取构造器的方法。

      方法 说明
      Constructor<?>\[\] getConstructors() 获取全部构造器(只能获取public修饰的)
      Constructor<?>\[\] getDeclaredConstructors() 获取全部构造器(只要存在就能拿到)
      Constructor getConstructor(Class<?>... parameterTypes) 获取某个构造器(只能获取public修饰的)
      Constructor getDeclaredConstructor(Class<?>... parameterTypes) 获取某个构造器(只要存在就能拿到)
    • 获取类构造器的作用:依然是初始化对象返回。

      Constructor提供的方法 说明
      T newInstance(Object... initargs) 调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回
      public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
  • 暴力反射:暴力反射,指的是反射操作可以直接无视类似于private等修饰符对访问的控制,获得字节码对象的各种信息

  • 代码示例

    • 先创建一个Dog类作为素材

      java 复制代码
      package com.ita._03反射获取字节码对象里面所有成员;
      public class Dog {
          private String name;
          private int age;
      
          public void eat(){
              System.out.println("狗吃东西...");
          }
      
          private String eat(String name){
              System.out.println("狗吃" + name);
              return "谢谢!谢谢!";
          }
      
          public Dog() {
          }
      
          private Dog(String name){
              this.name = name;
          }
      
          public Dog(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "Dog{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      '}';
          }
      }
    • Demo031,解析字节码对象构造器

      java 复制代码
      import java.lang.reflect.Constructor;
      public class Demo031 {
      
          public static void main(String[] args) throws Exception  {
      
              //目标:使用反射方式动态获取指定类的构造器方法
              //作用:可以通过反射灵活执行构造器创建对象
              System.out.println("=======================解析Dog类========================");
              demo(Dog.class);
              System.out.println("=======================解析Object类========================");
              demo(Object.class);
          }
      
          //反射实现的通用方法:可以接受任意类型,创建无参构造器对象和一个String类型有参构造器对象
          public static void demo(Class clazz) throws Exception {
              //1.获取这个类所有的构造器方法
              Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
              //2.打印所有构造器方法
              System.out.println("类中的构造方法有:");
              for (Constructor declaredConstructor : declaredConstructors) {
                  System.out.println(declaredConstructor);
              }
      
              //3.获取指定的无参构造器对象
              Constructor declaredConstructor = clazz.getDeclaredConstructor();
              declaredConstructor.setAccessible(true);//如果当前构造器是私有的就需要执行这句代码,否则不允许访问私有。
              //4.执行构造器动态创建对象
              Object obj = declaredConstructor.newInstance();
              // 5.打印对象
              System.out.println("创建并获取到对象:");
              System.out.println(obj);
      
              //5.解析一个够参构造器
              try {
                  Constructor declaredConstructor1 = clazz.getDeclaredConstructor(String.class);
                  declaredConstructor1.setAccessible(true);//设置私有允许访问
                  //执行构造器
                  Object obj2 = declaredConstructor1.newInstance("黑马");
                  System.out.println(obj2);
              } catch (Exception e) {
                  System.out.println(clazz+"没有一个String类型参数的构造器");
              }
      
          }
      }
    • 运行结果

反射:获取构造器成员

  • 语法

    • Class提供了从类中获取成员变量的方法。

      方法 说明
      public Field\[\] getFields​() 获取类的全部成员变量(只能获取public修饰的)
      public Field\[\] getDeclaredFields​() 获取类的全部成员变量(只要存在就能拿到)
      public Field getField​(String name) 获取类的某个成员变量(只能获取public修饰的)
      public Field getDeclaredField​(String name) 获取类的某个成员变量(只要存在就能拿到)
    • 获取到成员变量的作用:依然是赋值、取值。

      方法 说明
      void set(Object obj, Object value): 赋值
      Object get(Object obj) 取值
      public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
  • 代码示例

    java 复制代码
    import java.lang.reflect.Field;
    public class Demo032 {
        public static void main(String[] args) throws Exception {
            //目标:使用反射解析操作成员变量
    
            // 获取字节码对象
            Class clazz = Dog.class;
            //1.解析字节码对象中所有成员变量(包括私有)
            Field[] declaredFields = clazz.getDeclaredFields();
            //打印字节码中所有的成员变量
            System.out.println("类中的成员变量有:");
            for (Field declaredField : declaredFields) {
                System.out.println(declaredField);
            }
            //3.指定无参构造器创建对象
            Object obj = clazz.getConstructor().newInstance();
            System.out.println("通过解析字节码创建对象:"+obj);
    
            //4.获取name私有成员变量
            Field nameField = clazz.getDeclaredField("name");
            nameField.setAccessible(true);// 开启暴力反射
    
            //5.将name的值设置为"旺财"
            nameField.set(obj,"旺财");// 对象名.实例成员变量 = 值
            System.out.println("修改对象的name成员后打印:"+obj);
        }
    }
    • 运行结果

反射:获取成员方法

  • 语法

    • Class提供了从类中获取成员方法的API

      方法 说明
      Method\[\] getMethods​() 获取类的全部成员方法(只能获取public修饰的)
      Method\[\] getDeclaredMethods​() 获取类的全部成员方法(只要存在就能拿到)
      Method getMethod​(String name, Class<?>... parameterTypes) 获取类的某个成员方法(只能获取public修饰的)
      Method getDeclaredMethod​(String name, Class<?>... parameterTypes) 获取类的某个成员方法(只要存在就能拿到)
    • 成员方法的作用:依然是执行

      Method提供的方法 说明
      public Object invoke​(Object obj, Object... args) 触发某个对象的该方法执行。
      public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
  • 代码示例:

    java 复制代码
    import java.lang.reflect.Method;
    public class Demo033 {
        public static void main(String[] args) throws Exception {
            //目标:使用反射解析操作成员方法
            //1.获取所有成员方法(包括私有)
            Class clazz = Dog.class;
            System.out.println("获得类:" + clazz);
            System.out.println("获取类中所有成员方法:");
            Method[] declaredMethods = clazz.getDeclaredMethods();
            //2.打印
            for (Method declaredMethod : declaredMethods) {
                System.out.println(declaredMethod);
            }
            //3.指定无参构造器创建对象
            Object obj = clazz.getConstructor().newInstance();
            System.out.println("通过解析出的构造器创建对象" + obj);
    
            //4.获取private String eat(String name) 方法
            Method declaredMethod = clazz.getDeclaredMethod("eat",String.class);// 方法名加参数类型
            declaredMethod.setAccessible(true);// 开启暴力反射
    
            //5.执行方法private String eat(String name)
            System.out.println("传入通过解析构造器创建的对象+成员方法的参数,调用解析出的方法");
            declaredMethod.invoke(obj,"酱骨架");//相当于 对象名.实例方法(参数)
        }
    }
    • 运行结果

反射案例:任意对象数据写入磁盘文件

  • 需求:对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去

  • 思路:利用反射获得对象的字段名和对应的值,利用IO缓冲流将信息写入文件中

  • 代码:

    • 创建两个实体类,StudentTeacher作为例子
    java 复制代码
    package src.reflectionExpection;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student {
    
        private String name;
        private int age;
        private char sex;
        private double height;
        private String hobby;
    }
    java 复制代码
    package src.reflectionExpection;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Teacher {
    
        private String name;
        private double salary;
    }
    • 核心解析对象类
    java 复制代码
     package src.reflectionExpection;
    
     import java.io.BufferedWriter;
     import java.io.FileWriter;
     import java.io.Writer;
     import java.lang.reflect.Field;
     public class ObjectDataToFileUtil {
         public static void  writeData(Object obj) throws Exception{
    
             try(
                     //1.创建输出流对象
                     //低级流字符输出流
                     Writer out = new FileWriter("./src/reflectionExpection/objectData.txt",true);
                     //高级流字符缓冲输出流
                     BufferedWriter bout = new BufferedWriter(out)
             ){
    
                 //2.根据obj得到字节码对象
                 Class clazz = obj.getClass();
    
                 //3.输出类名字写到文件中
                 String simpleName = clazz.getSimpleName();
                 bout.write(String.format("============%s============", simpleName));
                 //format为格式化字符串
                 bout.newLine();//在文件中输出换行
    
                 //4.获取字节码对象中所有成员变量得到Field[]
                 Field[] declaredFields = clazz.getDeclaredFields();
    
                 //5.Field[] 数组遍历
                 for (Field declaredField : declaredFields) {
                     //5.1 解析获取每个成员变量名字和值
                     declaredField.setAccessible(true);
                     String name = declaredField.getName();//成员变量的名字
                     Object value = declaredField.get(obj);
    
                     //5.2 使用输出流输出数据
                     bout.write( String.format("%s=%s%n", name, value));
                 }
    
                 //提示成功
                 System.out.printf("%s对象的数据写入磁盘文件成功!%n",simpleName);
             }
         }
     }
    • 主类运行测试
    java 复制代码
    package src.reflectionExpection;
    
    public class Main {
        public static void main(String[] args) {
            try {
                //准备学生对象数据
                Student student = new Student("播仔", 20, '男', 1.88D, "敲代码");
    
                //准备老师对象数据
                Teacher teacher = new Teacher("宇哥", 40000);
    
                //写入磁盘
                ObjectDataToFileUtil.writeData(student);
    
                ObjectDataToFileUtil.writeData(teacher);
            } catch (Exception e) {
                e.printStackTrace();//给程序员看
                System.out.println("服务器忙,请稍后重试...");//给用户看的
            }
        }
    }
    • 运行结果

注解

什么是注解

  • 就是Java代码里的特殊标记,比如:@Override、@Test等,作用是:让其他程序根据注解信息来决定怎么执行该程序。

  • 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处。

自定义注解

  • 就是自己定义注解,
    • 注解内可以有参数属性但注解的参数和类中的参数(成员变量)有不同的语法

      java 复制代码
      public @interface 注解名称 {
          public 属性类型 属性名() default 默认值 ;
      }
    • 特殊属性名: value (如果注解中只有一个value属性,使用注解时,value名称可以不写!! )

  • 代码示例
    • 创建自定义注解MyBookMyBook2

      java 复制代码
      package src.annotation;
      
      public @interface MyBook {
      
          //属性语法:public 属性类型属性名() [default 默认值];
          public String bookName();
      
          public String author() default "黑马程序员";
          //有默认值,在使用注解的时候可以不用给这个成员赋值
      
          public String price();
      
          public String value();
      }
      java 复制代码
      package src.annotation;
      
      public @interface MyBook2 {
          public String value();
      }
    • 使用两个注解(可以在类、成员变量、成员方法上使用)

      java 复制代码
      package src.annotation;
      
      @MyBook(value = "test",bookName = "java从入门到起飞",price = "100")
      @MyBook2("test")//因为这个注解只有一个属性变量value,赋值的时候value名字可以不写
      public class Demo041 {
      
          @MyBook(value = "test",bookName = "java从入门到起飞",price = "100")
          private String name;
      
          @MyBook(value = "test",bookName = "java从入门到起飞",price = "100")
          public Demo041(){}
      
          @MyBook(value = "test",bookName = "java从入门到起飞",price = "100")
          public void show(
                  @MyBook(value = "test",bookName = "java从入门到起飞",price = "100")
                  String demo){
              @MyBook(value = "test",bookName = "java从入门到起飞",price = "100") int age =20;
          }
      
          @MyBook(value = "test",bookName = "java从入门到起飞",price = "100")
          public static void show2(){
              int age =20;
          }
      }

注解概述-注解的原理

  • 注解本质是一个接口 ,Java中所有注解都是继承了Annotation接口的。如下图所示,MyTest1实际上是一个继承了Annotation接口的接口
  • @注解(...)操作:其实就是一个实现类对象,实现了该注解以及Annotation接口。如下图所示,@MyTest实际上是实现了继承Annotation接口的MyTest1接口
  • 注解的作用
    • 对Java中类、方法、成员变量做标记,然后进行特殊处理。
    • 例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行

元注解

  • 元注解是描述注解的注解,每个注解都有两个元注解分别是@Retention@Target
  • @Target:
    • 作用:声明被修饰的注解只能在哪些位置使用
    • 语法:@Target(ElementType.TYPE)
      • TYPE,类,接口
      • FIELD, 成员变量
      • PARAMETER, 方法参数
      • CONSTRUCTOR, 构造器
      • LOCAL_VARIABLE, 局部变量
    • 如果需要指定在多个位置类型使用可以写多参数如@Target({ElementType.TYPE, ElementType.METHOD}) 即在类与接口中使用,又在方法中使用。
  • @Retention:
    • 作用:声明注解的保留周期。
    • 语法:@Retention(RetentionPolicy.RUNTIME)
      • SOURCE,只作用在源码阶段,字节码文件中不存在。
      • CLASS(默认值),保留到字节码文件阶段,运行阶段不存在.
      • RUNTIME(开发常用),一直保留到运行阶段。
  • 代码示例:
    • 创建注解用于测试

      java 复制代码
      package src.annotation;
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      @Target({ElementType.METHOD,ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)//推荐使用
      public @interface MyBook3 {
      
          public String value() default "demo";
      }
    • 测试注解使用位置是否被限制,可以发现无法在成员变量上使用注解了,测试注解保留的时间可以看到注解在编译后的字节码中出现,说明至少保留到了字节码文件阶段(因为运行阶段目前还无法查看)

      java 复制代码
      package src.annotation;
      @MyBook3
      public class Demo042 {
      
          // @MyBook3  报错,不支持
          private String name;
      
          // @MyBook3  报错,不支持
          public Demo042(){}
      
          @MyBook3
          public void show(
                  // @MyBook3  报错,不支持
                  String demo){
              int age =20;
          }
      
          @MyBook3
          public static void show2(){
              int age =20;
          }
      
          public static void main(String[] args) {
              System.out.println("hello");
          }
      }
      • 从运行结果看确实是在生成字节码阶段被保留了
    • 下面更改Retention注解的参数,仅保留注解到源码阶段

      java 复制代码
      package src.annotation;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      @Target({ElementType.METHOD,ElementType.TYPE})
      @Retention(RetentionPolicy.SOURCE)
      public @interface MyBook3 {
      
          public String value() default "demo";
      }
      • 再次运行返现编译后的字节码中没有注解了

  • 元注解是什么?各自的作用是什么?
    • 注解注解的注解
    • @Target约束自定义注解可以标记的范围。@Retention用来约束自定义注解的存活范围。

注解:注解的解析

  • 就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。
  • 如何解析注解?
    • 指导思想:要解析谁上面的注解,就应该先拿到谁。
    • 比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解。(通过反射)
    • 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。
    • Class 、 Method 、 Field , Constructor、都实现了AnnotatedElement接口,它们都拥有解析注解的能力。(只要拿到各个对象就能通过这些封装好的方法进行解析) | AnnotatedElement接口提供了解析注解的方法 | 说明 | | --------------------------------------------------------------------- | --------------- | | public Annotation\[\] getDeclaredAnnotations() | 获取当前对象上面的注解。 | | public T getDeclaredAnnotation(Class annotationClass) | 获取指定的注解对象 | | public boolean isAnnotationPresent(Class annotationClass) | 判断当前对象上是否存在某个注解 |

代码示例

java 复制代码
package src.annotation;

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

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)//推荐使用
public @interface MyBook3 {

    public String value() default "demo";
}
  • 利用反射获得字节码对象,并解析类中的内容,最后解析出各个对象的注解
java 复制代码
package src.annotation;
import java.lang.reflect.Method;

@MyBook3("helloworld")
public class Demo043 {


    @MyBook3
    public void show(
            String demo){
        int age =20;
    }


    public static void main(String[] args) throws Exception {

        //目标:解析当前类和方法的注解,确保注解运行时存在
        //1. 解析类上的注解
        //1.1  获取字节码对象
        Class clazz = Demo043.class;
        //1.2  获取指定类上的注解  @MyBook3
        // public T getDeclaredAnnotation(Class<T> annotationClass)
        MyBook3 myBook3 = (MyBook3) clazz.getDeclaredAnnotation(MyBook3.class);
        //1.3  获取注解value属性的值
        String value = myBook3.value();
        System.out.println("类上注解Mybook3的value值:"+value);//helloworld


        //2.解析方法上的注解
        //2.1 获取字节码上show方法Method对象
        Method method = clazz.getMethod("show", String.class);
        //2.2 获取方法上面指定的Mybook3注解
        myBook3 = method.getDeclaredAnnotation(MyBook3.class);
        //2.3 打印value值
        System.out.println("value值:" + myBook3.value());//demo

    }
}

注解案例:简易版本Junit开发

  • 需求:定义若干方法,只要加了MyTest注解,就会触发该方法执行
  • 分析:
    • 定义一个自定义注解MyTest,只能注解方法,存活范围到运行阶段(一直都在)。
    • 定义若干方法,部分方法上面加@MyTest注解修饰,部分方法不加。
    • 模拟一个junit程序,可以触发@MyTest主机方法执行
  • 代码:
    • MyTest注解

      java 复制代码
      package src.annotation.MyJunit;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface MyTest {
      }
    • Demo044通过注解解析实现和Junit类似的功能

      java 复制代码
      package src.annotation.MyJunit;
      
      import java.lang.reflect.Method;
      
      public class Demo044 {
      
          @MyTest
          public void test1(){
              System.out.println("test1");
          }
          @MyTest
          public void test2(){
              System.out.println("test2");
          }
          public void test3(){
              System.out.println("test3");
          }
          @MyTest
          public void test4(){
              System.out.println("test4");
          }
      
      
          public static void main(String[] args) throws Exception {
      
              //模拟开发简易的Junit框架,解析当前类有@MyTest方法直接执行
      
              //1.获取当前字节码文件对象
              Class clazz = Demo044.class;
      
              //2.获取字节码里面所有成员方法 Method[]
              Method[] methods = clazz.getMethods();
      
              //3.创建对象,便于执行方法使用
              Object obj = clazz.getConstructor().newInstance();
      
              //4.遍历Method[]
              for (Method method : methods) {
      
                  //4.1 判断每个Method上是否有@MyTest注解
                  if(method.isAnnotationPresent(MyTest.class)) {
                      //4.2 有,执行方法
                      method.invoke(obj);
                  }
              }
          }
      }

代理设计模式

程序为什么需要代理?代理长什么样?

  • 为什么需要代理:对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。这样职责划分会更为清晰
    • 比如说歌手的主要能力是唱歌,无心处理开演唱会,准备演出器材,收门票钱等工作,那么歌手会找一个经纪人来处理这些琐碎且繁杂的事务,那么演唱会相关的事务会先经过经纪人,歌手再通过经纪人安排去演唱会进行唱歌

    • 在这里歌手职责通过经纪人进行拆分并将琐碎的职责转移给经纪人,经纪人将琐碎的职责处理完成后,安排歌手去唱歌,在这一套流程中经纪人就是代理,歌手就是代理目标,他们干的是同一件事,但是职责不同。

    • 除了代理还有中介公司,中介公司需要找到相对应的代理来安排演唱会,中介要如何找到合适的代理呢?通过接口(即为通过接口的抽象方法进行约束),而代理和代理目标则作为接口的实现类被接口管理就可以了。

静态代理设计模式

  • 设计模式:解决某种问题的最优解,前辈总结解决某一类问题的通用解决方案
  • 静态代理设计模式
    • 解决:职责更加清晰 (多余职责交给代理),通过代理实现方法增强, ,并且不修改目标对象职责的任何代码。(在一些企业中为了代码的稳定性,在为旧的功能做拓展、增强的时候,不允许修改原有的代码,此时就可以选在静态代理模式)
    • 特点:需要手动创建代理类
  • 体系结构
  • 代码
    • 先创建接口

      java 复制代码
      package src.proxy;
      
      public interface Star {
      
          //唱歌
          void sing();
      
          //跳舞
          void dance();
      }
    • 再创建代理目标

      java 复制代码
      package src.proxy;
      
      public class ZRNStar implements Star{
      
          @Override
          public void sing() {
              System.out.println("章若楠在唱歌。。。");
          }
      
          @Override
          public void dance() {
              System.out.println("章若楠在跳舞。。。");
          }
      }
    • 再创建代理

      java 复制代码
      package src.proxy;
      
      public class DLStar implements Star {
      
          private Star star;
          public DLStar(Star star){
              this.star = star;
          }
      
          @Override
          public void sing() {
              System.out.println("准备话筒,收钱。。。");
              //调用目标去唱歌
              star.sing();
          }
      
          @Override
          public void dance() {
              System.out.println("准备话筒,收钱。。。");
              //调用目标去跳舞
              star.dance();
          }
      }
    • 最后再主类进行测试

      java 复制代码
      package src.proxy;
      
      public class Main {
          public static void main(String[] args) {
              ZRNStar zrnStar = new ZRNStar(); //目标对象
              //创建1级代理对象
              Star proxy1 = new DLStar(zrnStar); // 代理对象
              proxy1.sing();
              proxy1.dance();
          }
      }
    • 运行结果

  • 除了一级代理,我们还可以设计二级代理 ,在原有的基础上进行功能增强,比如歌手除了经纪人,还需要法律顾问,这个时候就可以在经纪人代理上再套一个代理,要先进行法律评估再进行演唱会准备工作
    • 代码演示,创建法律代理(二级代理)

      java 复制代码
      package src.proxy;
      
      public class FWStar implements Star {
      
          private Star star;
          public FWStar(Star star){
              this.star = star;
          }
      
          @Override
          public void sing() {
              System.out.println("谈合同,符合法律要求。。。");
              //调用目标去唱歌
              star.sing();
          }
      
          @Override
          public void dance() {
              System.out.println("谈合同,符合法律要求。。。");
              //调用目标去跳舞
              star.dance();
          }
      }
    • 再在主类中测试

      java 复制代码
      package src.proxy;
      
      public class Main {
          public static void main(String[] args) {
              ZRNStar zrnStar = new ZRNStar(); //目标对象
              //创建1级代理对象
              Star proxy1 = new DLStar(zrnStar); // 代理对象
              FWStar proxy2 = new FWStar(proxy1);
              proxy2.sing();
              proxy2.dance();
          }
      }
    • 运行结果

动态代理设计模式

  • 动态代理设计模式

    • 解决:让目标对象方法增强的问题,也可以让目标方法职责更加清晰,并且不修改目标对象的职责任何代码。
    • 特点:动态创建代理类,无需手动创建,扩展性更强
  • 如何为Java对象动态创建一个代理对象?

    • java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:

    • 语法

      java 复制代码
      public static Object newProxyInstance(
          ClassLoader loader
          , Class<?>[] interfaces
          , InvocationHandler h) 
      • 参数一:用于指定用哪个类加载器,去加载生成的代理类
      • 参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法(要求接收一个接口数组,因为在java中接口是多继承的)
      • 参数三:用来指定生成的代理对象要干什么事情(需要自己实现,可以作为内部类实现)
  • 代码

    • 创建接口

      java 复制代码
      package src.dynamicProxy;
      
      public interface Star {
      
          //唱歌
          void sing();
      
          //跳舞
          void dance();
      }
    • 实现接口创建目标类

      java 复制代码
      package src.dynamicProxy;
      
      public class ZRNStar implements Star {
      
          @Override
          public void sing() {
              System.out.println("章若楠在唱歌。。。");
          }
      
          @Override
          public void dance() {
              System.out.println("章若楠在跳舞。。。");
          }
      }
    • 设计代理并调用测试

      java 复制代码
      package src.dynamicProxy;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      
      public class dynamicProxyTest {
          public static void main(String[] args) {
              //1.创建目标对象
              Star star = new ZRNStar();
              //2.创建代理对象(功能增强的对象)匿名内部类方式
              Star proxy = (Star) Proxy.newProxyInstance(
                      star.getClass().getClassLoader(),
                      //写法1:写死接口 new Class[]{Star.class},
                      //写法2:获取目标实现的接口数组
                      star.getClass().getInterfaces(),//获取目标字节码中所有实现的接口类型数组
                      new InvocationHandler(){
                          @Override
                          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                          //写增强的代码和调用目标的方法
                          //参数1:proxy 就是生成的代理对象,一般不用
                          //参数2:method 反射的方法对象(实现接口的方法)
                          //参数3:args 方法的参数数据(实参)
                              //增强的代码
                              System.out.println("准备实施,收钱");
                              //调用目标对象的方法
                              Object result = method.invoke(star, args);//返回值可能有也可能没有
                              return result;//将返回值返回给调用方
                          }
                      }
              );
              // 创建代理对象(功能增强的对象)Lambda表达式方式
              Star proxy2 = (Star) Proxy.newProxyInstance(
                      proxy.getClass().getClassLoader(),
                      proxy.getClass().getInterfaces(),//获取目标字节码中所有实现的接口类型数组
                      (proxy1, method, args1) -> {
                          //因为lambda表达式没有单独的作用域所以参数会冲突
                                  // ,proxy和一级代理冲突改为proxy1
                                  // ,args和main方法的args冲突改为args1
                          //增强的代码
                          System.out.println("签合同");
                          //调用目标对象的方法
                          //返回值可能有也可能没有
                          return method.invoke(proxy, args1);//将返回值返回给调用方
                      }
              );
              //3.执行代理的方法(增强目标功能方法)
              proxy2.sing();
              proxy2.dance();
          }
      }
  • 代码执行顺序

使用代理优化用户管理类

  • 场景:某系统有一个用户管理类,包含用户登录,删除用户,查询用户等功能,系统要求统计每个功能的执行耗时情况,以便后期观察程序性能。
  • 需求:现在,某个初级程序员已经开发好了该模块,请观察该模块的代码,找出目前存在的问题,并对其进行改造。
  • 代码
    • 新建用户服务接口

      java 复制代码
      public interface UserService {
      
          void login();
          void add();
          void delete();
      }
    • 实现接口创建用户类

      java 复制代码
      public class UserServiceImpl implements UserService{
          private Random random = new Random();
          @Override
          public void login() {
              System.out.println("执行登录操作。。。");
              try {
                  Thread.sleep(random.nextInt(1000));
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
          }
      
          @Override
          public void add() {
              System.out.println("执行添加用户操作。。。");
              try {
                  Thread.sleep(random.nextInt(1000));
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
          }
      
          @Override
          public void delete() {
              System.out.println("执行用户删除操作。。。");
              try {
                  Thread.sleep(random.nextInt(1000));
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
          }
      }
    • 设计一个测试耗时代理

      java 复制代码
      package src.ExecuteTimeProxyObjectUtil;
      import java.lang.reflect.Proxy;
      
      public class ExecuteTimeProxyObjectUtil{
      
          public static Object getProxyObject(Object target){
              return Proxy.newProxyInstance(
                      target.getClass().getClassLoader(),
                      target.getClass().getInterfaces(),
                      (proxy1, method, args1) -> {
                          //目标:给每个目标方法增强统计耗时执行时间
      
                          //1.定义开始毫秒数
                          Long start = System.currentTimeMillis();
                          //2.执行目标方法
                          Object result = method.invoke(target, args1);
                          //3.定义结束毫秒数
                          Long end = System.currentTimeMillis();
                          //4.计算打印耗时时间=结束毫秒数-开始毫米数
                          System.out.printf("方法名:%s,耗时时间:%d%n",
                                  method.getName(),end-start);
                          return result;
                      }
              );
          }
      }
    • 在主函数中运行代理

      java 复制代码
      package src.ExecuteTimeProxyObjectUtil;
      
      public class Main {
          public static void main(String[] args) {
              //1.创建目标对象
              UserService userService = new UserServiceImpl();
      
              //2.创建代理对象
              UserService proxy = (UserService) ExecuteTimeProxyObjectUtil.getProxyObject(userService);
      
              //3.执行代理方法(看增强后的效果)
              proxy.add();
              proxy.login();
              proxy.delete();
          }
      }
    • 运行结果

动态代理是怎么实现的?(底层原理简介)

  • 底层用的就是 Java 的反射(Reflection)机制。甚至可以说,JDK 动态代理就是反射技术最巅峰的代表作之一。

  • 直接去查看 Proxy 类和 InvocationHandler 接口,它们俩就堂堂正正地躺在 java.lang.reflect 这个包下面。

  • JVM会通过反射自动的拿到代理目标,如下方代码会先通过字节码对象(这里是Star类的)获得的信息,而在内部类的method参数就是通过字节码对象反射过来的

    • 当外面调用代理对象的 sing() 方法时,JVM 会把 sing 这个方法包装成一个 java.lang.reflect.Method 对象。
    • 传入 invoke 方法后,你写的那行 method.invoke(target, args),就是最典型的利用反射去动态调用目标对象的方法
    java 复制代码
    Star proxy = (Star) Proxy.newProxyInstance(
            star.getClass().getClassLoader(),
            //写法1:写死接口 new Class[]{Star.class},
            //写法2:获取目标实现的接口数组
            star.getClass().getInterfaces(),
            //获取目标字节码中所有实现的接口类型数组
            new InvocationHandler(){
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    ...
                }
            }
    );

反射、注解、动态代理在框架中的应用

  • 后续学习的Spring框架就是利用反射、注解、动态代理这几项技术来做的

  • 第一步:反射(Reflection)------ Spring 的眼睛

    • Spring 启动时,它怎么知道你的项目里有哪些类?怎么在不使用 new 的情况下把对象创建出来?
      • 全靠反射 !Spring 会动态扫描你的类,利用 Class.forName() 拿到字节码,然后用反射动态调用构造方法,把对象塞进它的 IOC 容器里。
  • 第二步:注解(Annotation)------ Spring 的暗号

    • Spring 怎么知道哪些类需要它管理?哪些方法需要加事务?
      • 全靠注解 !你在类上写个 @Component,Spring 的反射机制一读:"哦,这个我得管";你在方法上写个 @Transactional,Spring 一读:"哟,这个方法需要带事务保护"。注解就是你和 Spring 之间不言自明的暗号。
  • 第三步:动态代理(Dynamic Proxy)------ Spring 的肌肉

    • Spring 读到了你的 @Transactional 注解,它要在不修改你原本代码的前提下,帮你加上"开启事务、提交事务、回滚事务"的功能。它怎么做?
      • 就是利用我们刚才讨论的 Proxy.newProxyInstance()!Spring 在内存中为你动态生成一个代理对象 。以后别人调用你的方法时,其实是在调用这个代理对象,代理对象先帮你把事务开了,再去调用你原本的方法。这就是 Spring 最核心的王牌功能------AOP(面向切面编程)
相关推荐
算法印象派2 小时前
Rokid AI 眼镜远程协作应用"一线互联"开发实践:设备发现与 BLE 扫描
后端
basketball6162 小时前
Go 语言从入门到进阶:5. 玩转Go函数
开发语言·后端·golang
砍材农夫2 小时前
物联网实战:Spring Boot + Netty 搭建 MQTT | MQTT 设备模拟器
java·spring boot·后端·物联网·struts·spring·netty
BingoGo2 小时前
通过 CC Switch 本地路由让 Codex CLI 接入 DeepSeek 等第三方模型
后端
jay神2 小时前
基于 Python + Flask + Vue 的校内求职互助平台
前端·vue.js·后端·python·flask·毕业设计
用户298698530142 小时前
Java 开发中读取与解析 Word 文档的实践记录
java·后端
AskHarries2 小时前
如何判断市场是否拥挤
后端
日月云棠2 小时前
14 Error 与 Exception —— 异常分类与处理策略
后端