BlazeMaple BlazeMaple
首页
  • 基础知识

    • Java的基本数据类型
    • Java中的常用类String
    • Java中的异常
    • Java中的注解
    • Java中的反射机制
    • Java中的泛型
    • Java为什么是值传递
  • 集合框架

    • Java集合核心知识总结
    • HashMap的7种遍历方式
    • 源码分析
  • Java新特性

    • Java8新特性
  • IO流

    • Java基础IO总结
    • Java IO中的设计模式
    • Java IO模型
    • IO多路复用详解
  • 并发编程

    • 并发编程基础总结
  • JVM

    • JVM基础总结
  • MySQL

    • MySQL核心知识小结
    • MySQL 45讲
  • Redis

    • Redis核心入门知识简记
  • Spring
  • SpringCloud Alibaba
  • 开发工具

    • Git详解
    • Maven详解
    • Docker详解
    • Linux常用命令
  • 在线工具

    • json (opens new window)
    • base64编解码 (opens new window)
    • 时间戳转换 (opens new window)
    • unicode转换 (opens new window)
    • 正则表达式 (opens new window)
    • md5加密 (opens new window)
    • 二维码 (opens new window)
    • 文本比对 (opens new window)
  • 学习资源

    • 计算机经典电子书PDF
    • hot120
GitHub (opens new window)
首页
  • 基础知识

    • Java的基本数据类型
    • Java中的常用类String
    • Java中的异常
    • Java中的注解
    • Java中的反射机制
    • Java中的泛型
    • Java为什么是值传递
  • 集合框架

    • Java集合核心知识总结
    • HashMap的7种遍历方式
    • 源码分析
  • Java新特性

    • Java8新特性
  • IO流

    • Java基础IO总结
    • Java IO中的设计模式
    • Java IO模型
    • IO多路复用详解
  • 并发编程

    • 并发编程基础总结
  • JVM

    • JVM基础总结
  • MySQL

    • MySQL核心知识小结
    • MySQL 45讲
  • Redis

    • Redis核心入门知识简记
  • Spring
  • SpringCloud Alibaba
  • 开发工具

    • Git详解
    • Maven详解
    • Docker详解
    • Linux常用命令
  • 在线工具

    • json (opens new window)
    • base64编解码 (opens new window)
    • 时间戳转换 (opens new window)
    • unicode转换 (opens new window)
    • 正则表达式 (opens new window)
    • md5加密 (opens new window)
    • 二维码 (opens new window)
    • 文本比对 (opens new window)
  • 学习资源

    • 计算机经典电子书PDF
    • hot120
GitHub (opens new window)
  • 基础知识

    • Java的基本数据类型
    • 聊一聊Java中的常用类String
    • 聊一聊Java中的异常
    • 聊一聊Java中的注解
    • 聊一聊Java中的反射机制
      • 聊一聊Java中的泛型
      • 聊一聊Java为什么是值传递
    • 集合框架

      • Java集合核心知识总结
      • HashMap 的 7 种遍历方式
      • 源码分析

        • ArrayList源码分析
        • LinkedList源码分析
        • HashMap源码分析
        • ConcurrentHashMap源码分析
        • CopyOnWriteArrayList 源码分析
        • LinkedHashMap 源码分析
        • ArrayBlockingQueue 源码分析
        • PriorityQueue 源码分析
        • DelayQueue 源码分析
    • Java新特性

      • Java8新特性
    • Java基础
    • 基础知识
    BlazeMaple
    2023-10-31
    目录

    聊一聊Java中的反射机制

    # 什么是反射?反射可以做什么用?

    反射被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。Spring等框架需要获取注解上的bean名称等都会用到反射这个技术。

    # 获取Class对象的四种方式

    每一个类都只会有一个Class对象,无论何种方式获取Class对象进行比较,得到的结果都是true,JVM在加载每个类时都会为每个类通过类加载器中的defineClass 去创建一个Class对象,而且只创建一次,所以后续我们无论通过何种方式获取到的某个类的Class对象都是一样的。

    /**
     * 获取Class对象的三种方式
     * 1 Object ——> getClass();
     * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
     * 3 通过Class类的静态方法:forName(String  className)(常用)
     * 4 通过类加载器xxxClassLoader.loadClass()传入类路径获取
     */
    public class FansheConstruct {
        public static void main(String[] args) {
            //第一种方式获取Class对象
            StudentConstruct stu1 = new StudentConstruct();//这一new 产生一个Student对象,一个Class对象。
            Class stuClass = stu1.getClass();//获取Class对象
            System.out.println(stuClass.getName());
    
            //第二种方式获取Class对象
            Class stuClass2 = StudentConstruct.class;
            System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
    
            //第三种方式获取Class对象
            try {
                Class stuClass3 =Class.forName("com.blazemaple.refect.StudentConstruct");//注意此字符串必须是真实路																				径,就是带包名的类路径,包名.类名
                System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            
            //第四种方式(通过类加载器获取 Class不会进行初始化,意味着不进行包括初始化等步骤,静态代码块和静态对象不会得到执行)
            ClassLoader.getSystemClassLoader().loadClass("com.blazemaple.refect.StudentConstruct");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    # 反射的一些基本操作

    # 通过反射获取构造方法

    
    public class Student {
        //(默认的构造方法)
        Student(String str) {
            System.out.println("(默认)的构造方法 s = " + str);
        }
    
        //无参构造方法
        public Student() {
            System.out.println("调用了公有、无参构造方法执行了。。。");
        }
    
        //有一个参数的构造方法
        public Student(char name) {
            System.out.println("姓名:" + name);
        }
    
        //有多个参数的构造方法
        public Student(String name, int age) {
            System.out.println("姓名:" + name + "年龄:" + age);
        }
    
        //受保护的构造方法
        protected Student(boolean n) {
            System.out.println("受保护的构造方法 n = " + n);
        }
    
        //私有构造方法
        private Student(int age) {
            System.out.println("私有的构造方法   年龄:" + age);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    几个获取构造方法的方式介绍

    1. public Constructor[] getConstructors():所有"公有的"构造方法
    2. public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
    3. public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
    4. public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
    import java.lang.reflect.Constructor;
    
    public class Constructors {
    
        public static void main(String[] args) throws Exception {
            //1.加载Class对象
            Class clazz = Class.forName("com.blazemaple.reflect.Student");
            
            //2.获取所有公有构造方法
            System.out.println("**********************所有公有构造方法*********************************");
            Constructor[] conArray = clazz.getConstructors();
            for(Constructor c : conArray){
                System.out.println(c);
            }
    
            System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
            conArray = clazz.getDeclaredConstructors();
            for(Constructor c : conArray){
                System.out.println(c);
            }
    
            System.out.println("*****************获取公有、无参的构造方法*******************************");
            Constructor con = clazz.getConstructor(null);
            //1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
            //2>、返回的是描述这个无参构造函数的类对象。
    
            System.out.println("con = " + con);
            //调用构造方法
            Object obj = con.newInstance();
            //	System.out.println("obj = " + obj);
            //	com.blazemaple.reflect.Student stu = (com.blazemaple.reflect.Student)obj;
    
            System.out.println("******************获取私有构造方法,并调用*******************************");
            con = clazz.getDeclaredConstructor(char.class);
            System.out.println(con);
            //调用构造方法
            con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
            obj = con.newInstance('男');
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40

    输出结果:

    **********************所有公有构造方法*********************************
    public com.blazemaple.reflect.Student(java.lang.String,int)
    public com.blazemaple.reflect.Student(char)
    public com.blazemaple.reflect.Student()
    ************所有的构造方法(包括:私有、受保护、默认、公有)***************
    private com.blazemaple.reflect.Student(int)
    protected com.blazemaple.reflect.Student(boolean)
    public com.blazemaple.reflect.Student(java.lang.String,int)
    public com.blazemaple.reflect.Student(char)
    public com.blazemaple.reflect.Student()
    com.blazemaple.reflect.Student(java.lang.String)
    *****************获取公有、无参的构造方法*******************************
    con = public com.blazemaple.reflect.Student()
    调用了公有、无参构造方法执行了。。。
    ******************获取私有构造方法,并调用*******************************
    public com.blazemaple.reflect.Student(char)
    姓名:男
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    # 通过反射获取成员变量

    public class StudentField {
    
        public StudentField() {
    
        }
    
        //**********字段*************//
        public String name;
    
        protected int age;
    
        char sex;
    
        private String phoneNum;
    
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + ", sex=" + sex
                + ", phoneNum=" + phoneNum + "]";
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    用Class 对象的getFields方法获取不同修饰符的成员变量

    import java.lang.reflect.Field;
    
    public class GetFields {
    
        public static void main(String[] args) throws Exception {
            //1.获取Class对象
            Class stuClass = Class.forName("com.blazemaple.reflect.StudentField");
            //2.获取字段
            System.out.println("************获取所有公有的字段********************");
            Field[] fieldArray = stuClass.getFields();
            for (Field f : fieldArray) {
                System.out.println(f);
            }
            System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
            fieldArray = stuClass.getDeclaredFields();
            for (Field f : fieldArray) {
                System.out.println(f);
            }
            System.out.println("*************获取公有字段**并调用***********************************");
            Field f = stuClass.getField("name");
            System.out.println(f);
            //获取一个对象
            Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
            //为字段设置值
            f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
            //验证
            StudentField stu = (StudentField) obj;
            System.out.println("验证姓名:" + stu.name);
    
            System.out.println("**************获取私有字段****并调用********************************");
            f = stuClass.getDeclaredField("phoneNum");
            System.out.println(f);
            f.setAccessible(true);//暴力反射,解除私有限定
            f.set(obj, "18888889999");
            System.out.println("验证电话:" + stu);
    
            f = stuClass.getDeclaredField("age");
            System.out.println(f);
            f.setAccessible(true);//暴力反射,解除私有限定
            f.set(obj, 18);
            System.out.println("验证保护变量年龄:" + stu);
    
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    输出结果:

    ************获取所有公有的字段********************
    public java.lang.String com.blazemaple.reflect.StudentField.name
    ************获取所有的字段(包括私有、受保护、默认的)********************
    public java.lang.String com.blazemaple.reflect.StudentField.name
    protected int com.blazemaple.reflect.StudentField.age
    char com.blazemaple.reflect.StudentField.sex
    private java.lang.String com.blazemaple.reflect.StudentField.phoneNum
    *************获取公有字段**并调用***********************************
    public java.lang.String com.blazemaple.reflect.StudentField.name
    验证姓名:刘德华
    **************获取私有字段****并调用********************************
    private java.lang.String com.blazemaple.reflect.StudentField.phoneNum
    验证电话:Student [name=刘德华, age=0, sex= , phoneNum=18888889999]
    protected int com.blazemaple.reflect.StudentField.age
    验证保护变量年龄:Student [name=刘德华, age=18, sex= , phoneNum=18888889999]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 通过反射获取方法

    public class StudentMethod {
    
        //**************成员方法***************//
        public void show1(String s) {
            System.out.println("调用了:公有的,String参数的show1(): s = " + s);
        }
    
        protected void show2() {
            System.out.println("调用了:受保护的,无参的show2()");
        }
    
        void show3() {
            System.out.println("调用了:默认的,无参的show3()");
        }
    
        private String show4(int age) {
            System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
            return "abcd";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    通过Class 对象的getMethod获取到对应方法

    import java.lang.reflect.Method;
    
    public class GetMethod {
    
        public static void main(String[] args) throws Exception {
            //1.获取Class对象
            Class stuClass = Class.forName("com.blazemaple.reflect.StudentMethod");
            //2.获取所有公有方法
            System.out.println("***************获取所有的”公有“方法*******************");
            Method[] methodArray = stuClass.getMethods();
            for(Method m : methodArray){
                System.out.println(m);
            }
            System.out.println("***************获取所有的方法,包括私有的*******************");
            methodArray = stuClass.getDeclaredMethods();
            for(Method m : methodArray){
                System.out.println(m);
            }
            System.out.println("***************获取公有的show1()方法*******************");
            Method m = stuClass.getMethod("show1", String.class);
            System.out.println(m);
            //实例化一个Student对象
            Object obj = stuClass.getConstructor().newInstance();
            m.invoke(obj, "刘德华");
    
            System.out.println("***************获取私有的show4()方法******************");
            m = stuClass.getDeclaredMethod("show4", int.class);
            System.out.println(m);
            m.setAccessible(true);//解除私有限定
            Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
            System.out.println("返回值:" + result);
    
            System.out.println("***************获取私有的show3()方法******************");
            m = stuClass.getDeclaredMethod("show3");
            System.out.println(m);
            m.setAccessible(true);//解除私有限定
            Object result2 = m.invoke(obj);
            System.out.println("返回值:" + result2);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40

    输出结果:

    ***************获取所有的”公有“方法*******************
    public void com.blazemaple.reflect.StudentMethod.show1(java.lang.String)
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public java.lang.String java.lang.Object.toString()
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()
    ***************获取所有的方法,包括私有的*******************
    public void com.blazemaple.reflect.StudentMethod.show1(java.lang.String)
    protected void com.blazemaple.reflect.StudentMethod.show2()
    private java.lang.String com.blazemaple.reflect.StudentMethod.show4(int)
    void com.blazemaple.reflect.StudentMethod.show3()
    ***************获取公有的show1()方法*******************
    public void com.blazemaple.reflect.StudentMethod.show1(java.lang.String)
    调用了:公有的,String参数的show1(): s = 刘德华
    ***************获取私有的show4()方法******************
    private java.lang.String com.blazemaple.reflect.StudentMethod.show4(int)
    调用了,私有的,并且有返回值的,int参数的show4(): age = 20
    返回值:abcd
    ***************获取私有的show3()方法******************
    void com.blazemaple.reflect.StudentMethod.show3()
    调用了:默认的,无参的show3()
    返回值:null
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    # 通过反射完成泛型擦除

    java是一门伪泛型语言,泛型仅仅是在编译器会进行检查,一旦过了编译泛型就会被擦除,所以假如某些情况下我们不能修改某个类的泛型,且需要为这个为这个指定泛型的变量赋一个非泛型的值时,我们就可以使用反射技术,如下所示:

    public class ModifyGenerics {
        public static void main(String[] args) throws Exception {
    
            ArrayList<String> strList = new ArrayList<>();
            strList.add("aaa");
            strList.add("bbb");
    
            //	strList.add(100);
            //获取ArrayList的Class对象,反向的调用add()方法,添加数据
            Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
            //获取add()方法
            Method m = listClass.getMethod("add", Object.class);
            //调用add()方法
            m.invoke(strList, 100);
    
            //遍历集合
            for (Object obj : strList) {
                System.out.println(obj);
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 反射的工作原理

    new的过程很简单,大体来说就是看看方法区有没有关于class解析的对象的信息,如果没有则先去加载,完成后拿着这些信息完成对象创建。 而反射则是通过方法区在堆区创建一个对应类的唯一一个Class对象,通过这个对象获取类中各种信息并封装成一个个Method、Field等对象使用。

    在这里插入图片描述

    # 反射机制的优缺点

    • 优点:可以让代码更加灵活、为各种框架提供开箱即用的功能提供了便利
    • 缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点。

    # 基于反射的两种代理模式

    # 代理模式

    代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。代理模式有静态代理和动态代理两种实现方式。

    # 静态代理

    从应用层面来说,静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活且麻烦(需要对每个目标类都单独写一个代理类)。从 JVM 层面来说,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

    静态代理实现步骤:

    1. 定义一个接口及其实现类;
    2. 创建一个代理类同样实现这个接口
    3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
    /**
     * 通过接口抽象代理类和被代理类的动作
     */
    public interface SmsService {
        String send(String message);
    }
    /**
     * 被代理类
     */
    public class SmsServiceImpl implements SmsService {
        public String send(String message) {
            System.out.println("send message:" + message);
            return message;
        }
    }
    /**
     * 代理类
     */
    public class SmsProxy implements SmsService {
    
        /**
         * 通过关联关系将被代理对象以成员变量形式组合到代理类中
         */
        private final SmsService smsService;
    
        public SmsProxy(SmsService smsService) {
            this.smsService = smsService;
        }
    
        @Override
        public String send(String message) {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("before method send()");
            smsService.send(message);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method send()");
            return null;
        }
    }
    /**
     * 运行代码
     */
    public class StaticMain {
    
        public static void main(String[] args) {
            SmsService smsService = new SmsServiceImpl();
            SmsProxy smsProxy = new SmsProxy(smsService);
            smsProxy.send("java");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50

    # 动态代理

    相比与静态代理,动态代理则相对灵活一些,它可以在运行时候动态的生成被代理类的字节码并加载到JVM中,而且与静态代理相比它无需创建接口类去规范代理类的方法(如果被代理类没有实现接口完全可以使用CGLIB来实现)。

    # 基于jdk实现的动态代理

    在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            ......
        }
    
    1
    2
    3
    4
    5
    6
    7

    这个方法一共有 3 个参数:

    1. loader :类加载器,用于加载代理对象。
    2. interfaces : 被代理类实现的一些接口;
    3. h : 实现了 InvocationHandler 接口的对象;

    要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

    public interface InvocationHandler {
    
        /**
         * 当你使用代理对象调用方法的时候实际会调用到这个方法
         */
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    invoke() 方法有下面三个参数:

    1. proxy :动态生成的代理类
    2. method : 与代理类对象调用的方法相对应
    3. args : 当前 method 方法的参数

    也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

    实现步骤:

    1. 定义一个接口及其实现类;
    2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
    3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象

    示例:

    定义接口

    public interface SmsService {
        String send(String message);
    }
    
    1
    2
    3

    实现类

    public class SmsServiceImpl implements SmsService {
        public String send(String message) {
            System.out.println("send message:" + message);
            return message;
        }
    }
    
    1
    2
    3
    4
    5
    6

    增强器代码(自定义 InvocationHandler 并重写invoke方法)

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * 通过jdk代理增强被代理方法
     */
    public class DebugInvocationHandler implements InvocationHandler {
        /**
         * 代理类中的真实对象
         */
        private final Object target;
    
        public DebugInvocationHandler(Object target) {
            this.target = target;
        }
    
    
        public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("before method " + method.getName());
            Object result = method.invoke(target, args);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method " + method.getName());
            return result;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    代理类代码(获取代理对象的工厂类)

    import java.lang.reflect.Proxy;
    
    /**
     * jdk代理类 可以看到Obj对象类型任意,真正实现了代码解耦
     */
    public class JdkProxyFactory {
        public static Object getProxy(Object target) {
            return Proxy.newProxyInstance(
                    target.getClass().getClassLoader(), // 目标类的类加载
                    target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                    new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler
            );
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    测试类代码

    public class Main {
        public static void main(String[] args) {
            SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
            smsService.send("java");
        }
    }
    
    1
    2
    3
    4
    5
    6

    结果

    before method send
    send message:java
    after method send
    
    1
    2
    3
    # 基于CGLIB实现动态代理

    JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

    **在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。**需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

    public interface MethodInterceptor extends Callback{
        // 拦截被代理类中的方法
        public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
    }
    
    1
    2
    3
    4
    1. obj : 被代理的对象(需要增强的对象)
    2. method : 被拦截的方法(需要增强的方法)
    3. args : 方法入参
    4. proxy : 用于调用原始方法

    你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

    实现步骤:

    1. 定义一个类;
    2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
    3. 通过 Enhancer 类的 create()创建代理类;

    示例:

    不同于 JDK 动态代理不需要额外的依赖。CGLIBopen in new window (opens new window)(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。

    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>
    
    1
    2
    3
    4
    5

    定义被代理的类

    /**
     * 需要被代理的类
     */
    public class AliSmsService {
        public String send(String message) {
            System.out.println("send message:" + message);
            return message;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    自定义 MethodInterceptor(方法拦截增强器)

    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * 自定义MethodInterceptor
     */
    public class DebugMethodInterceptor implements MethodInterceptor {
    
    
        /**
         * @param o           代理对象(增强的对象)
         * @param method      被拦截的方法(需要增强的方法)
         * @param args        方法入参
         * @param methodProxy 用于调用原始方法
         */
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("before method " + method.getName());
            Object object = methodProxy.invokeSuper(o, args);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method " + method.getName());
            return object;
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    代理类

    import net.sf.cglib.proxy.Enhancer;
    
    /**
     * 代理类
     */
    public class CglibProxyFactory {
    
        public static Object getProxy(Class<?> clazz) {
            // 创建动态代理增强类
            Enhancer enhancer = new Enhancer();
            // 设置类加载器
            enhancer.setClassLoader(clazz.getClassLoader());
            // 设置被代理类
            enhancer.setSuperclass(clazz);
            // 设置方法拦截器
            enhancer.setCallback(new DebugMethodInterceptor());
            // 创建代理类
            return enhancer.create();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    测试类

    public class Main {
        public static void main(String[] args) {
            AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
            aliSmsService.send("java");
        }
    }
    
    1
    2
    3
    4
    5
    6

    输出

    before method send
    send message:java
    after method send
    
    1
    2
    3
    # JDK 动态代理和 CGLIB 动态代理对比
    1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
    2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
    # 静态代理和动态代理的对比
    1. 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改。

    2. JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

    # Java中实现回调的几种方式

    1. 反射,性能较差,不太推荐
    /**
     * 基于反射实现回调1
     */
    public class Demo1 {
    
        public static void main(String[] args) {
            Request request=new Request();
            new Thread(()->{
                try {
                    request.send(CallBackClass.class,CallBackClass.class.getMethod("process"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    
    
    class Request {
    
        private static Logger logger = LoggerFactory.getLogger(Request.class);
    
        public void send(Class clazz, Method method) throws Exception {
            // 模拟等待响应
            Thread.sleep(3000);
            logger.info("收到用户的请求,处理业务逻辑后发起回调");
            method.invoke(clazz.newInstance());
            logger.info("回调结束");
        }
    }
    
    
    class CallBackClass {
        private static Logger logger = LoggerFactory.getLogger(CallBackClass.class);
    
        public void process() {
            logger.info("这个是回调方法哦");
    
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    1. 直接将回调方法封装成一个对象传入
    /**
     * 直接调用,耦合度比较高
     */
    public class Demo2 {
        public static void main(String[] args) {
            Request1 request=new Request1();
            new Thread(()->{
                try {
                    request.send(new CallBackClass1());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    
    
    }
    
    
    class Request1 {
    
        private static Logger logger = LoggerFactory.getLogger(Request.class);
    
        public void send(CallBackClass1 callBackClass) throws Exception {
            // 模拟等待响应
            Thread.sleep(3000);
            logger.info("收到用户的请求,处理业务逻辑后发起回调");
            callBackClass.process();
            logger.info("回调结束");
        }
    }
    
    
    class CallBackClass1 {
        private static Logger logger = LoggerFactory.getLogger(CallBackClass.class);
    
        public void process() {
            logger.info("这个是回调方法哦");
    
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    1. 将回调行为抽成一个接口作为参数,真正的回调继承接口实现就好了
    /**
     * 接口调用
     */
    public class Demo3 {
        public static void main(String[] args) {
    
            CallBackInterface callBackInterface=new CallBackImpl();
            Request2 request2=new Request2();
            new Thread(()->{
                try {
                    request2.send(callBackInterface);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    
    }
    
    
    class Request2 {
    
        private static Logger logger = LoggerFactory.getLogger(Request.class);
    
        public void send(CallBackInterface CallBackInterface) throws Exception {
            // 模拟等待响应
            Thread.sleep(3000);
            logger.info("收到用户的请求,处理业务逻辑后发起回调");
            CallBackInterface.process();
            logger.info("回调结束");
        }
    }
    
    interface CallBackInterface{
        void process();
    }
    
    class CallBackImpl implements CallBackInterface{
    
        private static Logger logger = LoggerFactory.getLogger(CallBackImpl.class);
        @Override
        public void process() {
            logger.info("处理回调");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    1. 准确来说并不是第4种方法,这种就是基于方法3的接口用lambda实现而已
    /**
     * lambda处理回调
     */
    public class Demo4 {
        private static Logger logger = LoggerFactory.getLogger(Demo4.class);
        public static void main(String[] args) {
            CallBackInterface callBackInterface=new CallBackImpl();
            Request2 request2=new Request2();
            new Thread(()->{
                try {
                    request2.send(()->{logger.info("lambda处理回调");});
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    帮助我们改善此页面! (opens new window)
    上次更新: 2024/08/13, 09:07:12
    聊一聊Java中的注解
    聊一聊Java中的泛型

    ← 聊一聊Java中的注解 聊一聊Java中的泛型→

    最近更新
    01
    SpringCloud Alibaba实战
    08-22
    02
    SpringCloud Alibaba核心知识
    08-22
    03
    两数之和
    08-08
    更多文章>
    Theme by Vdoing | Copyright © 2023-2024 BlazeMaple
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式