Java从入门到精通(十二)~ 动态代理

 晚上好,愿这深深的夜色给你带来安宁,让温馨的夜晚抚平你一天的疲惫,美好的梦想在这个寂静的夜晚悄悄成长。

目录

前言

主要作用和功能:

应用场景:

二、代理概念

1.静态代理

2.动态代理

2.1 概念介绍

代理对象真实的样子

2.2 代码实现

接口规范:

 真实对象

 代理对象:

测试类: 

测试结果:​编辑 

2.3.  流程分析 

1.main方法中调用createProxy(new ArrayList())将真实对象传给代理工具类进行代理。

2.代理工具类接收到真实对象,会调用newProxyInstance()通过反射创建对应的代理对象。

3. 底层根据三个参数创建完,代理对象后,会将其返回。

4. 执行listProxy.containsAll(targetList); 会跳转到代理对象,通过透传调用InvocationHandler接口的invoke()方法,并将接口反射到对应的containsAll()方法的信息传递传递过去。


前言

无反射无Java,无动态代理无框架。

  1. 动态代理可以帮助在不修改原始类代码的情况下,对原始类的方法进行增强、添加额外的处理逻辑或者拦截某些操作。 
  2. 动态代理实现的关键在于利用反射动态生成代理类或者代理对象,这些代理对象可以拦截对真实对象方法的调用,并在调用前后执行额外的逻辑。
  3. 动态代理是一种在运行时动态生成代理类的技术,它允许在不事先创建实际实现类的情况下,创建一个实现了特定接口或一组接口的代理类。


一、什么是类加载器?

类加载器(Class Loader)在Java中是一个重要的运行时系统组件,负责将字节码文件加载到内存中,并生成对应的类对象。在Java虚拟机(JVM)中,每个类都需要在运行时动态加载到内存中才能被使用,而类加载器就是完成这个任务的核心。

主要作用和功能:

  1. 加载类文件: 类加载器负责从文件系统、JAR文件、网络中加载.class文件,并将其转换为一个Class对象。

  2. 类的唯一性: 类加载器确保每个类只被加载一次,即使多次请求加载同一个类,也只会得到同一个Class对象的引用。

  3. 双亲委派模型: Java的类加载器采用双亲委派模型(Parent Delegation Model),即除了顶层的启动类加载器外,其余类加载器都有父类加载器。当一个类加载器收到类加载请求时,它会先委托给父类加载器进行加载,只有在父类加载器无法加载时,才会自己尝试加载。

  4. 安全性: 类加载器也参与了Java安全模型的实现,例如沙箱安全性。

应用场景:

类加载器的灵活性使得Java具有了丰富的动态性和扩展性,例如在Web容器中,每个Web应用程序都有独立的类加载器实例,可以隔离不同应用程序的类加载,避免冲突。在框架和插件化系统中,类加载器的使用也十分普遍,可以动态加载和卸载模块。

二、代理概念

在代理模式中,有三个主要角色:

  • 抽象对象(Subject):定义了真实对象和代理对象的公共接口,客户端通过这个接口访问真实对象或代理对象。
  • 真实对象(Real Subject):实现了抽象角色定义的具体业务逻辑,是代理模式中被代理的对象。
  • 代理对象(Proxy):持有对真实对象的引用,并实现了抽象角色定义的接口,可以在调用真实对象之前或之后执行额外的操作。

1.静态代理

继承实际上就是静态代理,通过父类应用调用子类重写的方法,子类对父类进行了代理,这种代理是在运行前就生成的字节码,但这种存在问题,真实代码如果进行增加删除修改,子类也需要,维护成本就比较高。

// 定义接口 Person
interface Person {
    void doWork();
}

// 实现接口的学生类
class Student implements Person {
    @Override
    public void doWork() {
        System.out.println("学习");
    }
}

// 静态代理类实现接口 Person
class StaticProxy implements Person {
    private Person person;

    public StaticProxy(Person person) {
        this.person = person;
    }

    @Override
    public void doWork() {
        System.out.println("doSomething ----- start");
        person.doWork();
        System.out.println("doSomething ----- end");
    }
}

// 测试类
public class ProxyDemo {
    public static void main(String[] args) {
        // 创建一个学生对象
        Person student = new Student();
        
        // 创建静态代理对象,将学生对象作为参数传入
        StaticProxy staticProxy = new StaticProxy(student);
        
        // 调用静态代理对象的 doWork 方法
        staticProxy.doWork();
    }
}

其实静态代理也是很繁琐的,我们需要为每一个类,每一个方法添加相应的操作。 

上面的代码我们不难发现 静态代理 存在一个问题,代理主题类与真实主题类之间的 耦合程度太高 ,当真实主题类中增加、删除、修改方法后,那么代理主题类中的也必须增加、删除、修改相应的方法,提高了代码的维护成本。另一个问题就是当代理对象代理多个 Subject 的实现类时,多个实现类中必然出在不同的方法,由于代理对象要实现与目标对象一致的接口(其实是包含关系),必然需要编写众多的方法,极其容易造成臃肿且难以维护的代码。

2.动态代理

2.1 概念介绍

动态代理是在运行时动态生成的类或者对象,而不是编译时静态确定的类。这与传统的继承关系有所不同,传统继承是在编译时确定的静态关系。

  1. 代理类:代理对象和真实对象实现同一个接口,其中代理对象包含真实对象的所有方法,然后对真实对象的所有方法的调用的时候,通通先去调用代理类的Invocation handler。所有方法都走我的代理类,那我就可以在我的代理类去做任何我想做的逻辑了。
  2. Invocation handler是一个增强器,proxy是一个调度器:你去找的时候总会找一个为你服务。
  3. 那其实这是最核心的呢就是通过我们的Porxy.newProxyInstance方法去生成我们的动态代理类以及访问它的实例。 

当客户端调用代理对象的方法时,实际上是调用了 InvocationHandler 的 invoke 方法,传递了代理对象、方法和参数等信息。当代理对象调用任何方法时,都会回调这个方法,在动态代理模式中,代理对象会在运行时将方法调用转发给实现了InvocationHandler接口的处理器对象。代理对象在调用方法时,实际上是在"回调"(调用)InvocationHandler接口的方法,将控制权交给处理器对象。时机:在调用被代理对象的方法的时候,触发:会自动回调invoke()方法。

代理对象真实的样子

设置生成的动态代理的代码生成到指定路径,方便我们查看。

其中super.h:表示父类Proxy 类的成员变量InvocationHandler在newProxyInstance创建代理对象的时候,将参数三匿名内部类的地址值传递给h,因此调用super.h也就是你传的参数,然后将m3通过反射获取的接口中对应类的save()方法的信息。

2.2 代码实现

接口规范:
package proxy;

/**
 * @author windStop
 * @version 1.0
 * @description user业务层
 * @date 2024年07月27日23:37:55
 */
public interface UserService {
    boolean login(String name,String password) throws InterruptedException;

    String selectUsers(int id) throws InterruptedException;

    boolean deleteUsers(int id) throws InterruptedException;
}
 真实对象
package proxy;

/**
 * @author windStop
 * @version 1.0
 * @description User业务层实现类
 * @date 2024年07月27日23:40:01
 */
public class UserServiceImpl implements UserService{

    @Override
    public boolean login(String name, String password) throws InterruptedException {
        boolean flag = false;
        if (name.equals("windStop") && password.equals("123456")){
            flag = true;
        }
        Thread.sleep(3000);
        return flag;
    }

    @Override
    public String selectUsers(int id) throws InterruptedException {
        System.out.println("查询成功");
        Thread.sleep(200);
        return "查询成功";
    }

    @Override
    public boolean deleteUsers(int id) throws InterruptedException {
        System.out.println("删除成功");
        Thread.sleep(200);
        return true;
    }
}
 代理对象:
package proxy;

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

/**
 * @author windStop
 * @version 1.0
 * @description 代理工具类
 * @date 2024年07月27日23:37:01
 */
public class ProxyUtil {
    //ClassLoader: 用于定义代理类的类加载器。动态代理需要在运行时生成代理类的字节码,
    //因此需要指定一个类加载器来加载这些类。通常使用当前类的类加载器来加载。
    public static UserService createProxy(UserService userService){
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),
                new Class[]{UserService.class},
                //将方法调用分派给的调用处理程序
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (method.getName().equals("login") || method.getName().equals("selectUsers")
                        || method.getName().equals("deleteUsers")){
                            long startTime = System.currentTimeMillis();
                            Object rs = method.invoke(userService, args);
                            long endTime = System.currentTimeMillis();
                            System.out.println(method.getName() + "方法执行了" + (endTime - startTime) + "ms");
                            return rs;
                        }else{
                            return method.invoke(userService,args);
                        }
                    }
                }
        );
        return userServiceProxy;
    }
}
测试类: 
package proxy;

/**
 * @author windStop
 * @version 1.0
 * @description 测试动态代理
 * @date 2024年07月27日23:31:58
 */
public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        UserService userServiceProxy = ProxyUtil.createProxy(new UserServiceImpl());
        userServiceProxy.deleteUsers(10);
        userServiceProxy.selectUsers(20);
        userServiceProxy.login("xiaoming","123");
    }
}
测试结果:

2.3.  流程分析 

List listProxy = ProxyUtil.createProxy(new ArrayList<>());
List<String> targetList = new ArrayList<>();
targetList.add("item1");
targetList.add("item2");
listProxy.containsAll(targetList);

通过上述代码,我们可以看出:我们并没有在真实对象中进行时间计算,但结果会给我返回该流程所执行的时间,这就是动态代理的核心作用,在没有惊动我真实对象的代码前提下,动态的给其进行添加功能。

1.main方法中调用createProxy(new ArrayList())将真实对象传给代理工具类进行代理。
2.代理工具类接收到真实对象,会调用newProxyInstance()通过反射创建对应的代理对象。

参数一:用于定义代理类的类加载器。这里需要类加载器的原因是: 

动态代理需要显式提供类加载器的原因是因为它们生成的代理类不是预先存在的,而是在运行时动态生成的。为了确保这些动态生成的代理类能够被正确加载和使用,需要通过 Proxy.newProxyInstance 方法显式指定一个合适的类加载器来加载这些类。通常使用当前类的类加载器来加载。

参数二:提供定义规范的接口。

为什么要定义抽象对象(接口)呢?

  1. 类型匹配和约束

    • 接口定义了被代理对象和代理对象必须遵循的契约。动态代理根据接口来生成代理类,确保代理对象可以替代真实对象。这种约束保证了代理对象可以正确地提供和被代理对象相同的方法和行为。
  2. 实现多态

    • 接口使得代理对象在运行时可以动态替代被代理对象,实现了多态性。客户端代码通过统一的接口调用代理对象和被代理对象的方法,而不需要关心具体是哪个对象在处理请求。这种特性使得代码更加灵活和可扩展。
  3. 动态生成代理类

    • 动态代理技术通常在运行时生成代理类,这与静态代理不同,静态代理是在编译期间就已经确定了代理类的实现。基于接口的代理可以根据接口定义灵活地生成代理对象的代码,这种动态生成的能力使得代理对象的行为可以在运行时根据需要进行适配和修改。
  4. 解耦合

    • 接口定义了被代理对象的行为和代理对象提供的功能,从而实现了解耦合。代理对象可以在不影响客户端代码的情况下替换被代理对象,这种灵活性和解耦合的设计符合面向对象设计的依赖倒置原则和单一职责原则。客户端只需要依赖于接口,而不需要关心具体的实现类,从而提高了代码的灵活性和可维护性。
  5. 面向接口编程:可以利用动态代理,获取出接口的方法,然后通过代理对象调用该方法,就是多态的情景了,子类对象调用父类方法(调用的实际是被虚方法表覆盖的方法)。如果代理对象没有继承该接口,就无法调用接口中反射的方法,就会抛出IllegalAccessException 异常。

总之,接口在动态代理中的应用,不仅限于定义契约和约束,还包括实现多态、动态生成代理类以及实现解耦合等重要作用,这些特性使得动态代理在实际应用中具有广泛的适用性和灵活性。

 参数三:调用处理器,当用户使用代理对象调用了方法后,通通先去调用我的InvocationHandler去做任何我想做的逻辑了 。

3. 底层根据三个参数创建完,代理对象后,会将其返回。

创建出对象的样子:

继承抽取出的接口规范接口,生成该接口的所有方法的属性。

通过反射对这些(方法类型)的成员变量赋值。

4. 执行listProxy.containsAll(targetList); 会跳转到代理对象,通过透传调用InvocationHandler接口的invoke()方法,并将接口反射到对应的containsAll()方法的信息传递过去。

注意:其中super.h:表示父类Proxy 类的成员变量InvocationHandler

最后执行invoke()代码的逻辑

  1. proxy:用来调用代理对象自身的其他方法;
  2. method:用来获取被调用方法的详细信息;
  3. args:则是被调用方法的具体参数值。
  4. 返回值:invoke 方法的返回值是被代理方法的返回值。这里返回的对象是实际被代理方法执行后的结果。

  • 36
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风止￴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值