静态代理和动态代理的区别以及实现过程

简介: 这篇文章通过示例代码讲解了静态代理和动态代理在Java中的使用方式和区别,重点介绍了动态代理的实现原理及其在Spring框架中的应用。

前言

代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了。

在这里插入图片描述
其实代理模式的核心思想就是这么简单,在java中,代理又分静态代理动态代理2种,其中动态代理根据不同实现又区分基于接口的的动态代理和基于子类的动态代理。

其中静态代理由于比较简单,面试中也没啥问的,在代理模式一块,问的最多就是动态代理,而且动态代理也是spring aop的核心思想,spring其他很多功能也是通过动态代理来实现的,比如拦截器,事务控制等。

熟练掌握动态代理技术,能让你业务代码更加精简而优雅。如果你需要写一些中间件的话,那动态代理技术更是必不可少的技能包。

静态代理

静态代理,就是通过声明一个明确的代理类来访问源对象。

我们有1个接口,Person。这个个接口各有2个实现类,UML如下图:
在这里插入图片描述

实现

接口:person.java

package StaticProxy;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/2/15 13:29
 * @Description:
 */
public interface Person {

    /**
     * 起床
     */
    public void wakeup();

    /**
     * 睡觉
     */
    public void sleep();
}

实现类:Student .java

package StaticProxy;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/2/15 13:32
 * @Description:
 */
public class Student implements Person{
    private String name;

    public Student(){
    }

    public Student(String name){
        this.name = name;
    }

    @Override
    public void wakeup() {
        System.out.println("学生:"+name+",起床了!!!");
    }

    @Override
    public void sleep() {
        System.out.println("学生:"+name+",睡觉了!!!");
    }
}

假设我们现在要做一件事,就是在所有的实现类调用wakeup()前增加一行输出早安,调用sleep()前增加一行输出晚安。那我们只需要编写1个代理类PersonProxy

代理类:PersonProxy .java

package StaticProxy;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/2/15 13:35
 * @Description:
 */
public class PersonProxy implements Person{
    private Person person;

    public PersonProxy(Person person){
        this.person = person;
    }
    @Override
    public void wakeup() {
        System.out.println("早上好啊!!!");
        person.wakeup();
    }

    @Override
    public void sleep() {
        System.out.println("晚上好啊!!!");
        person.sleep();
    }
}

测试类

package StaticProxy;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/2/15 13:37
 * @Description:
 */
public class Test {
    public static void main(String[] args) {
        Person student1 = new Student("张三");
        PersonProxy studentProxy = new PersonProxy(student1);
        studentProxy.wakeup();
        studentProxy.sleep();
    }
}

结果

在这里插入图片描述

结论:

静态代理的代码相信已经不用多说了,代码非常简单易懂。这里用了1个代理类,代理了Person接口。

这种模式虽然好理解,但是缺点也很明显:

  • 会存在大量的冗余的代理类,这里演示了1个接口,如果有10个接口,就必须定义10个代理类。
  • 不易维护,一旦接口更改,代理类和目标类都需要更改。

动态代理

动态代理,通俗点说就是:无需声明式的创建java代理类,而是在运行过程中生成"虚拟"的代理类,被ClassLoader加载。从而避免了静态代理那样需要声明大量的代理类。

JDK从1.3版本就开始支持动态代理类的创建。主要核心类只有2个:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

还是前面那个例子,用JDK动态代理类去实现的代码如下:

创建一个JdkProxy类,用于统一代理:

package DynamicProxy;

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

/**
 * @author zyz
 * @version 1.0
 * @data 2023/2/15 13:28
 * @Description:
 */
public class JdkProxy implements InvocationHandler {

    private Object bean;

    public JdkProxy(Object bean) {
        this.bean = bean;
    }

    /**
     * 其中proxy为代理过之后的对象(并不是原对象),method为被代理的方法,args为方法的参数。
     *
     * 如果你不传原有的bean,直接用method.invoke(proxy, args)的话,那么就会陷入一个死循环。
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();

        if (methodName.equals("wakeup")) {
            System.out.println("早安~~~");
        } else if (methodName.equals("sleep")) {
            System.out.println("晚安~~~");
        }

        return method.invoke(bean, args);
    }
}

测试

package DynamicProxy;

import StaticProxy.Person;
import StaticProxy.Student;

import java.lang.reflect.Proxy;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/2/15 13:46
 * @Description:
 */
public class Test {
    public static void main(String[] args) {
        JdkProxy proxy = new JdkProxy(new Student("李四"));
        Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
        student.wakeup();
        student.sleep();
    }
}

结果

在这里插入图片描述

可以看到,相对于静态代理类来说,无论有多少接口,这里只需要一个代理类。核心代码也很简单。唯一需要注意的点有以下2点:

JDK动态代理是需要声明接口的,创建一个动态代理类必须得给这个”虚拟“的类一个接口。可以看到,这时候经动态代理类创造之后的每个bean已经不是原来那个对象了。

这里JdkProxy最核心的方法就是

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

其中proxy为代理过之后的对象(并不是原对象),method为被代理的方法,args为方法的参数。

如果你不传原有的bean,直接用method.invoke(proxy, args)的话,那么就会陷入一个死循环。

相关文章
|
SQL 监控 关系型数据库
为 MySQL/MariaDB 开启 Binlog 功能
说到 Binlog 就不得不提一下 MySQL Server 的四种类型的日志:Error Log、General Query Log、Slow Query Log 和 Binary Log 。
6365 0
|
XML Java 数据格式
Spring BeanFactory深度讲解
Spring `BeanFactory` 是 Spring 容器的基础,负责创建、配置和管理 Bean 实例,并提供对 Bean 生命周期的管理和控制。它通过读取配置文件或注解来实例化和管理 Bean,支持 XML、Java 配置和注解配置方式。与 `ApplicationContext` 相比,`BeanFactory` 更轻量级,采用延迟加载策略,适用于资源受限的环境。它实现了工厂模式,将对象的创建和管理分离,使应用程序更灵活、可扩展且易于维护。
336 0
|
人工智能 自然语言处理 PyTorch
基于openi平台免费华为昇腾910B芯片部署qwen2.5 Instruct 14B大模型
基于OpenI平台和华为昇腾910B芯片,本方案详细介绍了如何免费部署Qwen-2.5 Instruct 14B大模型。涵盖准备工作、模型适配、部署步骤及性能优化等内容,适用于NLP任务部署、本地化适配及实时服务化等多种应用场景。
4617 1
|
消息中间件 存储 Kafka
kafka是如何实现高性能高吞吐的?
以下是某网站上对该问题的总结,一共分为了以下六点,但这上面说的很浅显,我在后面加了一些自己的理解,做为解释,如有遗漏或者不对的地方欢迎大家指点,我会即时的修改,辛苦诸位老铁!
332 0
|
消息中间件 存储 缓存
为什么 Kafka 的吞吐量那么高?
为什么 Kafka 的吞吐量那么高?
522 2
|
XML Java 开发者
Spring 和 Spring Boot 的区别
【2月更文挑战第3天】
2124 2
|
人工智能 运维 监控
SLS 智能运维 AI 基础模型创新
SLS 全新发布运维场景基础模型,覆盖 Log、Metric、Trace 等可观测数据场景,模型提供开箱即用的异常检测、自动标注、分类和根因分析等能力;根因分析算法千级异常请求秒级定位,生产中准确率达95%;同时支持人工辅助微调,提供人工标注、结果打标修正,模型根据人工反馈自动微调,提升场景准确率。
91961 1
|
Java 测试技术
[笔记]Springboot入门《五》之单元测试读取配置
[笔记]Springboot入门《五》之单元测试读取配置
559 0
|
设计模式 运维 Java
Spring5深入浅出篇:Spring中静态代理与动态代理
Spring框架中的代理模式分为静态代理和动态代理。在JavaEE分层开发中,Service层最为重要,包含核心业务逻辑和额外功能。静态代理通过手动创建代理类来增加原始类的额外功能,但当代理类数量多时管理不便且不易于维护。动态代理则解决了这一问题,通过Spring的AOP(面向切面编程)实现,无需手动创建代理类,只需定义切点和增强(额外功能),在运行时动态生成代理对象,提高了代码的灵活性和可维护性。动态代理主要利用了JVM的字节码技术,在运行过程中创建代理类,执行完毕后随着虚拟机的结束而销毁,不会产生持久化的代理类。