Java设计模式:代理模式的静态和动态之分(八)

在这里插入图片描述

码到三十五 : 个人主页

心中有诗画,指尖舞代码,目光览世界,步履越千山,人间尽值得 !


在软件设计中,代理模式是一种常用的设计模式,它为我们提供了一种方式来控制对原始对象的访问。在Java中,代理模式尤其重要,因为它不仅增加了代码的灵活性,还提高了系统的可扩展性。本文将深入探讨Java中的代理模式,包括其定义、分类、实现方式以及实际应用场景。

[参见]:

Java设计模式:核心概述(一)

Java设计模式:单例模式之六种实现方式详解(二)

Java设计模式:工厂模式之简单工厂、工厂方法、抽象工厂(三)

Java设计模式:建造者模式之经典与流式的三种实现(四)

Java设计模式:适配器模式的三种形式(五)

Java设计模式:深入装饰器模式的三种写法(六)

Java设计模式:组合模式之透明与安全的两种实现(七)

目录

    • 一、代理模式的概念
    • 二、代理模式的分类
    • 三、代理模式的组成
    • 四、代理模式的优缺点
      • 4.1 优点
      • 4.2 缺点
    • 五、代理模式的使用场景
      • 1. 日志的采集
      • 2. 权限控制
      • 3. 实现AOP(面向切面编程)
      • 4. MyBatis Mapper
      • 5. Spring的事务管理
      • 6. 全局捕获异常
      • 7. RPC远程调用接口
      • 8. 分布式事务原理代理数据源
    • 六、代理模式的三种实现
      • 6.1 静态代理模式
      • 6.2 JDK动态代理的实现
      • 6.3 Cglib代理
      • 6.4 三种实现的区别和优缺点
        • 6.4.1 JDK动态代理
        • 6.4.2 cglib动态代理
    • 七、注意事项
    • 总结

一、代理模式的概念

代理模式(Proxy Pattern)是一种结构型设计模式,它提供了一种将类的功能委托给另一个类的方法。代理类作为原始类的代表,可以在调用原始类的方法之前或之后添加一些额外的逻辑。通过这种方式,代理模式可以控制对原始对象的访问,隐藏其复杂性或增加额外的功能。

二、代理模式的分类

在Java中,代理模式主要分为静态代理和动态代理两种。

  1. 静态代理:静态代理是在编译时就确定了代理类和被代理类的关系。代理类和被代理类通常会实现相同的接口或继承自相同的父类。静态代理的实现相对简单,但缺点是当需要代理的类增多时,会导致代理类数量剧增,增加了系统的复杂性。
  2. 动态代理:动态代理是在运行时创建代理类和对象。Java提供了java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来支持动态代理。动态代理可以灵活地创建代理对象,而无需为每个被代理类编写具体的代理类。这种灵活性使得动态代理在处理大量代理需求时更加高效。

三、代理模式的组成

在这里插入图片描述

  1. 抽象角色(Subject):这是一个接口或抽象类,定义了代理和真实对象需要实现的方法。它充当了客户端与代理/真实对象之间的契约。

  2. 代理角色(Proxy):代理类实现了抽象角色,并在需要时调用真实对象的方法。代理可以在调用真实对象之前或之后执行一些附加操作,如日志记录、访问控制、性能优化等。

  3. 真实角色(RealSubject):这是代理所代表的实际对象。它实现了抽象角色,并定义了实际的业务逻辑。客户端通常不直接与真实对象交互,而是通过代理来访问它。

四、代理模式的优缺点

4.1 优点

  • 增加额外的操作:代理可以在不修改真实对象的情况下增加额外的操作,如日志记录、安全性检查等。这提供了更好的灵活性和可扩展性。
  • 控制访问:代理可以控制对真实对象的访问,例如通过懒加载来延迟创建对象,或者在需要时限制对敏感数据的访问。
  • 保护真实对象:代理可以保护真实对象免受不必要的或恶意的访问,从而提高系统的安全性和稳定性。
  • 增强功能:代理可以通过装饰器模式的方式增强真实对象的功能,提供额外的服务或修改现有行为。

4.2 缺点

  • 性能开销:由于代理增加了额外的层级和调用,可能会导致性能下降。特别是当代理执行复杂操作或网络通信时,这种开销可能更加明显。
  • 代码复杂性:实现代理模式可能需要编写额外的代码和类,这可能会增加系统的复杂性和维护成本。需要仔细设计和测试代理类以确保其正确性和可靠性。
  • 可能引入错误:如果代理的实现不正确或存在缺陷,可能会导致意外的行为或错误。需要仔细测试和验证代理的逻辑以确保其按预期工作。

五、代理模式的使用场景

Java代理模式在以下场景中的应用:

1. 日志的采集

通过代理模式,我们可以在方法调用前后添加日志记录的逻辑,而无需修改原始类的代码。这有助于跟踪方法的执行情况、性能分析等。

2. 权限控制

在访问敏感资源或执行关键操作时,可以使用代理模式来拦截方法调用并进行权限检查。如果权限不足,则拒绝执行原方法,并返回相应的错误信息。

3. 实现AOP(面向切面编程)

AOP是代理模式的一种高级应用。通过动态代理,我们可以在不修改原始类代码的情况下,为方法调用添加横切关注点,如日志记录、事务管理、安全检查等。Spring AOP就是基于代理模式实现的。

4. MyBatis Mapper

MyBatis使用动态代理生成Mapper接口的实现类。当你调用Mapper接口的方法时,实际上是调用了一个动态生成的代理对象,该对象会根据方法签名和配置信息执行相应的SQL操作。

5. Spring的事务管理

Spring通过AOP和代理模式实现了声明式事务管理。当我们在方法上添加@Transactional注解时,Spring会为该方法创建一个代理对象,并在代理对象中添加事务管理的逻辑。这样,我们就可以通过调用代理对象来自动管理事务,而无需在代码中显式编写事务管理的代码。

6. 全局捕获异常

通过代理模式,我们可以在方法调用前后添加异常处理的逻辑。当方法抛出异常时,代理对象可以捕获该异常并进行相应的处理,如记录日志、返回统一的错误信息等。这有助于实现全局的异常处理策略。

7. RPC远程调用接口

在RPC框架中,客户端通常不会直接调用远程服务的方法,而是通过调用一个本地代理对象来实现远程调用。这个代理对象负责将方法调用转换为网络请求,并发送给远程服务。远程服务执行完毕后,将结果返回给代理对象,代理对象再将结果返回给客户端。这种方式隐藏了远程调用的复杂性,使得客户端可以像调用本地方法一样调用远程服务。

8. 分布式事务原理代理数据源

在分布式系统中,为了实现跨多个服务或数据库的事务一致性,我们可以使用分布式事务解决方案(如XA事务、TCC事务等)。这些解决方案通常会提供一个代理数据源或代理连接池,用于拦截和管理数据库操作。当应用程序访问数据库时,实际上是访问了这个代理数据源。代理数据源会根据分布式事务的配置和执行情况来决定是否提交或回滚事务操作。这种方式可以在不修改应用程序代码的情况下实现分布式事务的管理和协调。

六、代理模式的三种实现

6.1 静态代理模式

Java中的静态代理模式是一种相对简单的设计模式,它要求代理类和被代理类实现相同的接口或继承自相同的父类。代理类在内部持有被代理类的引用,并在需要时调用被代理类的方法,同时可以在调用前后添加额外的逻辑。

在这里插入图片描述

下面是一个简单的静态代理模式的实现:

首先,定义一个接口:

public interface Service {
    void performTask();
}

然后,创建被代理类,实现该接口:

public class RealService implements Service {
    @Override
    public void performTask() {
        System.out.println("RealService is performing the task.");
    }
}

接下来,创建代理类,同样实现该接口,并在构造函数中接收一个被代理类的实例:

public class ProxyService implements Service {
    private final Service realService;

    public ProxyService(Service realService) {
        this.realService = realService;
    }

    @Override
    public void performTask() {
        System.out.println("ProxyService: Before task.");
        realService.performTask(); // 调用被代理类的方法
        System.out.println("ProxyService: After task.");
    }
}

最后,在客户端代码中使用代理类:

public class Client {
    public static void main(String[] args) {
        // 创建被代理对象
        Service realService = new RealService();
        
        // 创建代理对象,将被代理对象作为参数传入
        Service proxyService = new ProxyService(realService);
        
        // 通过代理对象执行方法,会触发代理类中的额外逻辑
        proxyService.performTask();
    }
}

当运行客户端代码时,输出将会是:

ProxyService: Before task.
RealService is performing the task.
ProxyService: After task.

在这个例子中,ProxyService是代理类,它增强了RealService的功能,即在执行任务前后输出了额外的日志信息。客户端代码通过代理类ProxyService来调用performTask方法,而不需要直接与被代理类RealService交互。这种方式允许在不修改原始类的情况下增加新的行为或控制访问。

6.2 JDK动态代理的实现

在这里插入图片描述

JDK动态代理是Java提供的一种在运行时创建代理类和对象的方式。它主要利用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。下面是一个使用JDK动态代理模式的实现:

首先,定义一个接口:

public interface Service {
    void performTask();
}

然后,创建被代理类,实现该接口:

public class RealService implements Service {
    @Override
    public void performTask() {
        System.out.println("RealService is performing the task.");
    }
}

接下来,创建一个实现了InvocationHandler接口的类。这个类的invoke方法会在代理对象的方法被调用时被执行:

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

public class ServiceInvocationHandler implements InvocationHandler {
    private final Object target;

    public ServiceInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoking method: " + method.getName());
        Object result = method.invoke(target, args); // 调用被代理对象的方法
        System.out.println("After invoking method: " + method.getName());
        return result;
    }
}

最后,在客户端代码中使用Proxy.newProxyInstance方法创建代理对象,并通过该代理对象调用方法:

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 创建被代理对象
        Service realService = new RealService();
        
        // 创建InvocationHandler实例,将被代理对象传入
        InvocationHandler handler = new ServiceInvocationHandler(realService);
        
        // 使用Proxy.newProxyInstance创建代理对象
        Service proxyService = (Service) Proxy.newProxyInstance(
                Service.class.getClassLoader(), // 类加载器
                new Class<?>[] { Service.class }, // 代理类要实现的接口列表
                handler // 关联调用处理器
        );
        
        // 通过代理对象执行方法,会触发InvocationHandler中的invoke方法
        proxyService.performTask();
    }
}

当运行客户端代码时,输出将会是:

Before invoking method: performTask
RealService is performing the task.
After invoking method: performTask

在这个例子中,ServiceInvocationHandler是实现了InvocationHandler接口的调用处理器。当通过代理对象proxyService调用performTask方法时,实际上会调用ServiceInvocationHandler中的invoke方法。在invoke方法内部,我们可以添加任何额外的逻辑,比如方法调用前后的日志输出、权限检查等。通过method.invoke(target, args)语句,我们实际上是在调用被代理对象realServiceperformTask方法。

6.3 Cglib代理

cglib 是一个强大的、高性能的、高质量的代码生成库,它可以在运行时为 Java 类或接口生成子类或代理类。在 Java 代理模式中,当需要代理的类没有实现接口时,可以使用 cglib 来创建一个该类的子类作为代理。

要使用 cglib,首先需要将其添加到项目的依赖中。如果你使用 Maven,可以在 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version> <!-- 请检查是否有更新的版本 -->
</dependency>

下面是一个使用 cglib 创建代理类的示例:

首先,定义一个普通的类(注意,这个类没有实现任何接口):

public class RealService {
    public void performTask() {
        System.out.println("RealService is performing the task.");
    }
}

然后,创建一个实现了 MethodInterceptor 接口的类,该类将用于拦截对代理类方法的调用:

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ServiceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before invoking method: " + method.getName());
        Object result = proxy.invokeSuper(obj, args); // 调用父类(被代理类)的方法
        System.out.println("After invoking method: " + method.getName());
        return result;
    }
}

最后,在客户端代码中使用 Enhancer 类来创建代理对象:

import net.sf.cglib.proxy.Enhancer;

public class Client {
    public static void main(String[] args) {
        // 创建被代理对象,实际上这里并不需要直接创建,因为cglib会创建它的子类
        // RealService realService = new RealService();
        
        // 创建Enhancer实例,并设置父类为RealService
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealService.class);
        
        // 设置回调,即方法拦截器
        enhancer.setCallback(new ServiceMethodInterceptor());
        
        // 创建代理对象
        RealService proxyService = (RealService) enhancer.create();
        
        // 通过代理对象执行方法,会触发ServiceMethodInterceptor中的intercept方法
        proxyService.performTask();
    }
}

当运行客户端代码时,输出将会是:

Before invoking method: performTask
RealService is performing the task.
After invoking method: performTask

在这个例子中,ServiceMethodInterceptor 实现了 MethodInterceptor 接口,用于拦截对代理类方法的调用。在 intercept 方法中,我们可以添加任何额外的逻辑,比如方法调用前后的日志输出。通过 proxy.invokeSuper(obj, args) 语句,我们实际上是在调用被代理类 RealServiceperformTask 方法。Enhancer 类用于创建代理类,并通过 create 方法实例化代理对象。

6.4 三种实现的区别和优缺点

代理模式主要有三种实现方式(静态代理、JDK动态代理和cglib动态代理)中由于静态代理通常针对每个具体类编写,不具有通用性,因此这里主要讨论JDK动态代理和cglib动态代理的区别和优缺点。

6.4.1 JDK动态代理

实现方式
JDK动态代理要求目标类必须实现至少一个接口。代理类是在运行时动态生成的,实现了目标类所实现的所有接口,并通过反射调用目标类的方法。

优点

  • 无需为每个目标类编写具体的代理类,提高了代码的复用性和可维护性。
  • 由于代理类实现了接口,因此系统的耦合度较低,更加灵活。

缺点

  • 目标类必须实现至少一个接口,否则无法使用JDK动态代理。
  • 在方法调用频繁的情况下,由于使用了反射机制,性能可能较低。
6.4.2 cglib动态代理

实现方式
cglib动态代理是通过生成目标类的子类来实现的。它不需要目标类实现任何接口,而是通过继承目标类并重写其方法来创建代理类。在方法调用时,通过方法拦截器来增强方法的调用。

优点

  • 目标类无需实现任何接口,更加灵活。
  • 在某些场景下,由于直接调用子类方法而无需经过反射,性能可能优于JDK动态代理。

缺点

  • 由于代理类是通过继承目标类来实现的,因此目标类和方法不能声明为final类型。
  • 引入了额外的cglib库依赖,增加了项目的复杂性。
  • 系统的耦合度可能较高,因为代理类与目标类是继承关系。

JDK动态代理和cglib动态代理各有优缺点,选择哪种方式取决于具体的需求和场景。如果目标类已经实现了接口,那么JDK动态代理是一个不错的选择。如果目标类没有实现接口,或者需要更高的性能,那么可以考虑使用cglib动态代理。不过,在使用cglib时需要注意目标类和方法不能声明为final类型,以及引入额外依赖的问题。

七、注意事项

  • 选择合适的代理类型:根据具体需求选择合适的代理类型(如远程代理、虚拟代理等),并确保代理的实现与抽象角色一致。
  • 控制代理的复杂性:尽量保持代理类的简洁和清晰,避免引入不必要的复杂性。只在需要时添加额外的功能或操作。
  • 注意线程安全问题:如果代理在多线程环境中使用,需要确保它是线程安全的。考虑使用同步机制或线程本地存储来避免竞态条件和数据不一致问题。
  • 测试代理的逻辑:仔细测试和验证代理的逻辑以确保其按预期工作。特别关注代理与真实对象之间的交互和边界情况处理。

总结

Java代理模式是一种强大的设计模式,它允许我们通过代理类来控制对原始对象的访问。无论是静态代理还是动态代理,它们都为我们提供了增加额外逻辑、隐藏复杂性以及提高系统可扩展性的能力。在实际开发中,合理地运用代理模式可以使我们的代码更加灵活、可维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/511832.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python快速入门系列-9(Python项目实战)

第九章:Python项目实战 9.1 开发一个简单的Web应用9.1.1 项目概述9.1.2 环境准备9.1.3 项目结构9.1.4 代码实现9.1.4.1 创建数据库模型9.1.4.2 创建视图9.1.4.3 实用工具函数9.1.4.4 运行应用9.1.5 模板设计9.2 数据分析与可视化项目9.2.1 项目概述9.2.2 环境准备9.2.3 数据分…

vulnhub之devguru靶场提权过程(vulnhub打靶日记)

一、环境搭建 VM版本&#xff1a;17.5.1 build-23298084 攻击机&#xff1a;Kali2024&#xff08;下载地址&#xff1a;https://www.kali.org/&#xff09; 靶机&#xff1a;vulnhub靶场Devguru&#xff08;下载地址&#xff1a;https://www.vulnhub.com/entry/devguru-1,62…

探索网红系统功能菜单架构的设计与优化

随着社交媒体和数字化内容的普及&#xff0c;网红经济正在成为新兴的产业。在网红经济体系中&#xff0c;网红系统的功能菜单架构对于平台的用户体验和运营效率至关重要。本文将深入探讨网红系统功能菜单架构的设计与优化&#xff0c;为网红经济的发展提供新的思路和方法。 --…

【Web】记录Polar靶场<困难>难度题一遍过

目录 上传 PHP是世界上最好的语言 非常好绕的命令执行 这又是一个上传 网站被黑 flask_pin veryphp 毒鸡汤 upload tutu Unserialize_Escape 自由的文件上传系统​​​​​​​ ezjava 苦海 你想逃也逃不掉 safe_include CB链 phar PHP_Deserializatio…

Stable Diffusion WebUI 图片信息(PNG Info)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 大家好&#xff0c;我是水滴~~ 本文主要讲解 Stable Diffusion WebUI 的图片信息功能&#xff0c;主要包括&#xff1a;获取生成参数、将图片发送到其…

Spark实战:词频统计

文章目录 一、Spark实战&#xff1a;词频统计&#xff08;一&#xff09;Scala版1、分步完成词频统计2、一步搞定词频统计 &#xff08;二&#xff09;Python版1、分步完成词频统计2、一步搞定词频统计 二、实战总结 一、Spark实战&#xff1a;词频统计 &#xff08;一&#x…

【黑马头条】-day05延迟队列文章发布审核-Redis-zSet实现延迟队列-Feign远程调用

文章目录 昨日回顾今日内容1 延迟任务1.1 概述1.2 技术对比1.2.1 DelayQueue1.2.2 RabbitMQ1.2.3 Redis实现1.2.4 总结 2 redis实现延迟任务2.0 实现思路2.1 思考2.2 初步配置实现2.2.1 导入heima-leadnews-schedule模块2.2.2 在Nacos注册配置管理leadnews-schedule2.2.3 导入表…

STM32应用开发——使用PWM+DMA驱动WS2812

STM32应用开发——使用PWMDMA驱动WS2812 目录 STM32应用开发——使用PWMDMA驱动WS2812前言1 硬件介绍1.1 WS2812介绍1.1.1 芯片简介1.1.2 引脚描述1.1.3 工作原理1.1.4 时序1.1.5 传输协议 1.2 电路设计 2 软件编程2.1 软件原理2.2 测试代码2.2.1 底层驱动2.2.2 灯效应用 2.3 运…

css实现更改checkbox的样式;更改checkbox选中后的背景色;更改checkbox选中后的icon

<input class"check-input" type"checkbox"> .check-input {width: 16px;height: 16px;} /* 设置默认的checkbox样式 */input.check-input[type"checkbox"] {-webkit-appearance: none; /* 移除默认样式 */border: 1px solid #999;outl…

go连接数据库(原生)

根据官网文档 Go Wiki: SQL Database Drivers - The Go Programming Language 可以看到go可以连接的关系型数据库 ​ 常用的关系型数据库基本上都支持&#xff0c;下面以mysql为例 下载mysql驱动 打开上面的mysql链接 GitHub - go-sql-driver/mysql: Go MySQL Driver i…

【已解决】Error: error:0308010C:digital envelope routines::unsupported

前言 场景&#x1f3ac; 使用 Ant Design &#xff0c; 执行 npm run dev 出现异常。 文章目录 前言场景&#x1f3ac; 异常信息解决方案方案一(推荐)MAC | Linux 电脑成功⬇️ Windows 电脑 方案2&#xff1a; 不懂留言 JavaPub 异常信息 我直接异常信息&#xff0c;你可以…

Python快速入门系列-8(Python数据分析与可视化)

第八章:Python数据分析与可视化 8.1 数据处理与清洗8.1.1 数据加载与查看8.1.2 数据清洗与处理8.1.3 数据转换与整理8.2 数据可视化工具介绍8.2.1 Matplotlib8.2.2 Seaborn8.2.3 Plotly8.3 数据挖掘与机器学习简介8.3.1 Scikit-learn8.3.2 TensorFlow总结在本章中,我们将探讨…

【嵌入式智能产品开发实战】(十五)—— 政安晨:通过ARM-Linux掌握基本技能【GNU C标准与编译器】

目录 GNU C 什么是C语言标准 C语言标准的内容 C语言标准的发展过程 1.K&R C 2.ANSI C 3.C99标准 4.C11标准 编译器对C语言标准的支持 编译器对C语言标准的扩展 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 嵌入式智能产品…

QA测试开发工程师面试题满分问答6: 如何判断接口功能正常?从QA的角度设计测试用例

判断接口功能是否正常的方法之一是设计并执行相关的测试用例。下面是从测试QA的角度设计接口测试用例的一些建议&#xff0c;包括功能、边界、异常、链路、上下游和并发等方面&#xff1a; 通过综合考虑这些测试维度&#xff0c;并设计相应的测试用例&#xff0c;可以更全面地评…

【机器学习】“强化机器学习模型:Bagging与Boosting详解“

1. 引言 在当今数据驱动的世界里&#xff0c;机器学习技术已成为解决复杂问题和提升决策制定效率的关键工具。随着数据的增长和计算能力的提升&#xff0c;传统的单一模型方法已逐渐无法满足高精度和泛化能力的双重要求。集成学习&#xff0c;作为一种结合多个学习算法以获得比…

大数据实验二-HDFS编程实践

一&#xff0e;实验内容 HDFS编程实践&#xff1a; 1&#xff09;使用HDFS文件操作的常用Shell命令&#xff1b; 2&#xff09;利用Hadoop提供的Java API进行基本的文件操作。 二&#xff0e;实验目的 1、理解HDFS在Hadoop体系结构中的角色。 2、熟练使用HDFS操作常用的Sh…

【测试篇】接口测试

接口测试&#xff0c;可以用可视化工具 postman。 如何做接口测试&#xff1f;&#xff1f; 我们可以先在浏览器中随机进入一个网页&#xff0c;打开开发者工具&#xff08;F12&#xff09;。 随便找一个接口Copy–>Copy as cURL(bash) 打开postman 复制地址 进行发送。 …

CF1717 D. Madoka and The Corruption Scheme [思维题?]

传送门:CF [前题提要]:近期在集中刷1900的题,原本感觉这类题的思维难度对自己来说似乎没什么大问题,拿到手之后就开始乱贪心,然后就Wa4了,狠狠地被这道题给教育了,故记录一下 看了题解之后感觉这种做法之前在某道题中碰到过类似的,但是想不起来了… 我个人认为这道题的关键点…

时间管理系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)大学生

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读300套最新项目持续更新中..... 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含ja…

打印日志(JAVA)

1、通过导入包的形式 package com.example.demo;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; RequestMapping("/log&q…