0101代理模式详解-设计模式-spring

1 概述

代理模式是一种结构型设计模式,它通过提供一个代理对象来控制对另一个对象的访问。在代理模式中,代理对象充当原始对象的接口,客户端可以通过代理对象来访问原始对象,代理对象则可以控制对原始对象的访问,并在必要时进行一些额外的处理。

  • 分类

代理模式根据代理对象(应用场景)有以下几种类型:

  1. 远程代理:代理对象控制对远程对象的访问。
  2. 虚拟代理:代理对象控制对大对象的访问,只有在需要时才会实例化大对象。
  3. 保护代理:代理对象控制对敏感对象的访问,可以对访问进行授权和认证。
  4. 缓存代理:代理对象缓存对原始对象的访问结果,以提高访问效率。

代理模式根据实现方式有以下2种实现:

  1. 静态代理:见#3

  2. 动态代理:见#4

  • 功能

代理模式主要提供以下功能:

  1. 功能增强:下面单独说明。
  2. 控制访问:代理对象可以控制对原始对象的访问,从而可以提供一些额外的安全性和保护性。
  3. 增加性能:代理对象可以缓存原始对象的访问结果,以提高访问效率。
  4. 简化客户端:代理对象可以隐藏原始对象的复杂性,从而简化客户端的操作。
  • 功能增强

代理模式可以用来增强被代理对象的功能,实现功能的动态扩展和修改。代理对象可以在不修改被代理对象的前提下,对其方法进行增强,包括但不限于以下几种方式:

  1. 记录日志:在代理对象的方法执行前后记录日志,方便问题排查和系统监控。
  2. 缓存数据:在代理对象的方法执行前先查询缓存,如果缓存中已经存在所需数据,则直接返回缓存数据,避免重复查询数据库等资源。
  3. 增强安全性:在代理对象的方法执行前进行权限验证,确保只有有权限的用户才能调用特定的方法。
  4. 远程调用:通过代理对象实现远程调用,将需要执行的方法序列化传输给远程服务器执行,并将结果反序列化返回。
  5. 延迟加载:在代理对象的方法执行前先判断是否需要加载数据,如果不需要则不进行数据加载,从而提高系统性能。

总之,代理模式可以通过动态生成代理对象来对被代理对象的方法进行增强,从而实现各种各样的功能扩展和修改。

但代理模式也可能带来一些缺点,例如:

  1. 增加复杂性:代理模式可能增加系统的复杂性,因为需要引入额外的代理对象来控制访问。
  2. 增加开销:代理对象可能会带来额外的开销,例如网络通信、对象的实例化等。

2 对象之间的关系

在通过UML设计类图的时候,常见关系,参考地址在文章最后3,4,这里不在赘述。

3 静态代理

静态代理是指在编译期就已经确定代理类和被代理类的关系。静态代理需要手动创建代理类,实现被代理接口,并在代理类中调用被代理类的方法。静态代理的优点是简单易懂,缺点是代理类数量增多,维护成本高。

在静态代理中,代理类和被代理类实现同一个接口,代理类持有一个被代理类的引用,在代理类的方法中调用被代理类的方法,并可以在调用前后做一些额外的操作,比如记录日志、统计时间、校验参数等。静态代理的优点是代码结构清晰,易于理解和维护,缺点是当被代理类的方法发生改变时,代理类的代码也需要相应修改。

下面我们通过简单的订单服务,(静态代理)UML类图如下:

在这里插入图片描述

订单接口OrderService源代码2-1如下:

package com.gaogzhen.proxy.service;/*** 订单服务接口* @author gaogzhen*/
public interface OrderService {/*** 生成订单*/void generate();/*** 修改订单*/void modify();/*** 检索订单*/void retrieve();
}

订单服务实现类OrderServiceImpl类代码2-2如下所示:

package com.gaogzhen.proxy.service.impl;import com.gaogzhen.proxy.service.OrderService;import java.util.concurrent.TimeUnit;/*** 订单服务* @author gaogzhen*/
public class OrderServiceImpl implements OrderService {@Overridepublic void generate() {// 模拟生成订单耗时try {TimeUnit.MILLISECONDS.sleep(1234);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("生成订单");}@Overridepublic void modify() {// 模拟修改订单耗时try {TimeUnit.MILLISECONDS.sleep(556);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("修改订单");}@Overridepublic void retrieve() {// 模拟检索订单耗时try {TimeUnit.MILLISECONDS.sleep(224);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("检索订单");}
}

应用场景:假设系统运行一段时间,甲方感觉服务运行太慢,想要我们优化。现在我们需要统计每个业务接口的业务方法的耗时,有以下可选方案:

  • 解决方案一:硬编码,在业务方法中直接添加统计耗时的程序。

    • 缺点
      • 违背OCP(开闭原则)原则;
      • 大量冗余,开发效率低,代码不能复用。
  • 解决方案二:编写业务类的子类,重写业务方法。

    • 子类源代码2-3如下

      package com.gaogzhen.proxy.service.impl;/*** 业务类子类* @author gaogzhen*/
      public class OrderServiceImplSub extends OrderServiceImpl{@Overridepublic void generate() {long start = System.currentTimeMillis();super.generate();long end = System.currentTimeMillis();System.out.println("generate 耗时:" + (end - start));}@Overridepublic void modify() {long start = System.currentTimeMillis();super.modify();long end = System.currentTimeMillis();System.out.println("generate 耗时:" + (end - start));}@Overridepublic void retrieve() {long start = System.currentTimeMillis();super.retrieve();long end = System.currentTimeMillis();System.out.println("generate 耗时:" + (end - start));}
      }
    • 缺点

      • 耦合度高
      • 大量冗余,代码不能复用
  • 解决方案三:静态代理

    • 代理类代码2-4如下所示:

      package com.gaogzhen.proxy.service.proxy;import com.gaogzhen.proxy.service.OrderService;/*** OrderService代理类* @author gaogzhen*/
      public class OrderServiceProxy implements OrderService {private OrderService orderService;public OrderServiceProxy(OrderService orderService) {this.orderService = orderService;}@Overridepublic void generate() {long start = System.currentTimeMillis();orderService.generate();long end = System.currentTimeMillis();System.out.println("generate 耗时:" + (end - start));}@Overridepublic void modify() {long start = System.currentTimeMillis();orderService.modify();long end = System.currentTimeMillis();System.out.println("modify 耗时:" + (end - start));}@Overridepublic void retrieve() {long start = System.currentTimeMillis();orderService.retrieve();long end = System.currentTimeMillis();System.out.println("retrieve 耗时:" + (end - start));}
      }// 测试类
      package com.gaogzhen.proxy.client;import com.gaogzhen.proxy.service.OrderService;
      import com.gaogzhen.proxy.service.impl.OrderServiceImpl;
      import com.gaogzhen.proxy.service.proxy.OrderServiceProxy;/*** 模拟服务客户端* @author gaogzhen*/
      public class ServiceClient {public static void main(String[] args) {OrderService orderServiceImpl = new OrderServiceImpl();OrderService orderServiceProxy = new OrderServiceProxy(orderServiceImpl);orderServiceProxy.generate();orderServiceProxy.modify();orderServiceProxy.retrieve();}
      }
      
    • 优点

      • 通过关联关系解耦合
    • 缺点

      • 代码冗余,不能复用
      • 一个目标对象需要对应的一个代理类对象,如果目标对象很多,程序运行需要消耗大量内存。

4 动态代理

动态代理是指在运行时根据接口动态生成代理类。Java中提供了两种方式实现动态代理:JDK动态代理、CGLIB动态代理和Javassist动态代理。

  • Javassist是一种Java字节码编辑库,可以在运行时动态地修改字节码,包括修改已有的类、创建新的类和接口等。通过Javassist,我们可以实现动态代理。相比于Java原生的动态代理,Javassist动态代理具有更好的性能和更灵活的功能。

我们主要讲解JDK动态代理和CGLIB动态代理。

3.1 JDK动态代理

JDK动态代理是基于接口的代理模式。被代理类必须实现接口,代理类动态生成的过程由Java API实现。使用动态代理的方式,客户端调用代理对象的方法时,实际执行的是被代理对象的方法。JDK动态代理的优点是不需要手动创建代理类,缺点是只能代理实现了接口的类。

3.1.1 测试用例

测试用例同静态代理,根据测试需要随时添加代码:

  • OrderService接口
  • OrderServiceImpl目标类

3.1.1 JDK动态代理实现步骤

  • 创建目标对象

  • 创建代理对象:核心类java.lang.reflect.Proxy

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
    
    • ClassLoader loader:类加载器,spring会在内存中动态生成代理类的字节码,通过类加载器加载。JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
    • Class<?>[]interfaces:代理类和目标类要实现同一个接口或者一些接口,生成代理类的时候,需要我们告诉它实现那些接口。
    • InvocationHandler h:InvocationHandler是Java中动态代理机制中的一个接口,它定义了一个方法invoke,该方法在代理对象方法被调用时被调用。在invoke方法中,我们可以实现对被代理对象方法的增强,或者在代理对象方法被调用前后执行一些其他操作。
  • 使用代理对象的代理方法

  • 问题

    • 我们需要手写InvocationHandler接口的实现类,会不会形成大量内存占用呢?

    • 调用处理器的实现类我们只需要写一次,不会造成内存的大量占用。

3.1.2 InvocationHandler#invoke()

3.1.2.1 概述
  • 为什么强行要求我们必须实现InvocationHandler接口的invoke()方法?
    • 因为JDK底层调用invoke()方法的程序已经提前写好,所以这个方法必须是invoke。invoke()方法不是由我们程序员负责调用,而是JDK负责调用。
  • invoke方法什么时候被调用?
    • 当代理对象调用代理方法的时候,注册在InvocationHandler中的invoke()方法被调用。

invoke()方法测试,现在我们给测试用例添加计算时间的代理功能TimerInvocationHandler代码如下3.1.3-1所示:

package com.gaogzhen.proxy.service.handler;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** OrderService代理类* @author gaogzhen*/
public class TimerInvocationHandler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("invoke 被执行");return null;}
}

测试方法代码如下3.1.3-2所示:

package com.gaogzhen.proxy.client;import com.gaogzhen.proxy.service.OrderService;
import com.gaogzhen.proxy.service.handler.TimerInvocationHandler;
import com.gaogzhen.proxy.service.impl.OrderServiceImpl;import java.lang.reflect.Proxy;/*** 模拟服务客户端* @author gaogzhen*/
public class ServiceClient {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderServiceImpl();// 创建代理对象Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler());}
}

测试结果:没有输出

结论:

  • 不执行代理对象的代理方法,invoke()方法不会被调用。

下面我们来执行代理对象的代理方法,添加代码如下:

// 创建代理对象
OrderService proxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler());// 调用代理对象的代理方法
proxy.generate();
proxy.modify();
proxy.retrieve();

输出:

invoke 被执行
invoke 被执行
invoke 被执行

结论:

  • 没调用一次代理方法,invoke()相应的执行一次。
  • 因为我们代理类实现了和目标类相同的接口,所以可以转型为接口类型。
3.1.2.2 invoke()方法详解

invoke(Object proxy, Method method, Object[] args)方法的三个参数

  • invoke()方法由JDK负责调用,会自动传递给我们这3个参数。
  • Object proxy:代理对象,参数较少使用。
  • Method method:目标对象上的目标方法,要执行的目标方法。
  • Object[] args:目标方法上的参数。

方法执行四要素:

  • 对象
  • 方法
  • 参数
  • 返回结果

继续测试invoke()方法,添加增强代码逻辑,添加代码如下:

package com.gaogzhen.proxy.service.handler;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** OrderService代理类* @author gaogzhen*/
public class TimerInvocationHandler implements InvocationHandler {private Object target;public TimerInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("===方法执行前增强===");// 目标方法执行Object val = method.invoke(target, args);System.out.println("====方法执行====");System.out.println("====方法执行后增强====");return val;}
}// 接口添加带返回值的方法/*** 根据订单id获取订单名称* @param id* @return*/String getById(String id);
// 目标类实现@Overridepublic String getById(String id) {return "学习JDK动态代理";}
// 测试类输出返回结果// 调用代理对象的代理方法System.out.println(proxy.getById("xxx"));

测试结果:

===方法执行前增强===
====方法执行====
====方法执行后增强====
学习JDK动态代理
3.1.2.3 JDK动态代理工具类封装

在以后使用中,需要经常使用Proxy#newProxyInstance()生成代理对象。为了简化开发,我们把该方法简单封装,工具类代码3.1.2.3-1如下所示:

package com.gaogzhen.proxy.utils;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;/*** 动态代理工具类* @author gaogzhen*/
public class ProxyUtils {/*** JDK动态代理生成代理对象* @param target    目标对象* @return  代理对象*/public static Object newProxyInstance(Object target, InvocationHandler h) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), h);}
}

3.2 CGLIB动态代理

CGLIB动态代理是基于继承的代理模式。被代理类不需要实现接口,代理类动态生成的过程由第三方库实现。使用动态代理的方式,客户端调用代理对象的方法时,实际执行的是被代理对象的方法。CGLIB动态代理的优点是可以代理没有实现接口的类,缺点是生成代理类的过程比较耗时,会影响性能。

  • 被代理目标类不能使用final修饰

测试用例UserService用户服务类,模拟登陆退出,代码3.2-1如下所示:

package com.gaogzhen.proxy.service;/*** 用户服务类* @author gaogzhen*/
public class UserService {/*** 模拟登陆* @param username 用户名* @param password 密码* @return  {@code true} 登陆成功;{@code false} 否则*/public boolean login(String username, String password) {System.out.println("身份验证");if ("admin".equals(username) && "123".equals(password)) {System.out.println("====登陆成功====");return true;}System.out.println("登陆失败,用户名或者密码错误");return false;}/*** 退出登陆*/public void logout() {System.out.println("===退出登陆====");}
}

TimerMethodInterceptor计算耗时功能增强,代码3.2-2如下所示:

package com.gaogzhen.proxy.interceptor;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** 时间处理* @author gaogzhen*/
public class TimerMethodInterceptor implements MethodInterceptor {/*** 代理方法* @param o 目标对象* @param method* @param objects   目标方法参数* @param methodProxy   目标方法* @return 执行结果* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("====目标方法调用之前增强====");long start = System.currentTimeMillis();// 调用目标方法Object val = methodProxy.invokeSuper(o, objects);System.out.println("====目标方法调用之后增强====");long end = System.currentTimeMillis();System.out.println("耗时:" + (end - start));return val;}
}

测试类代码3.2-3如下所示:

package com.gaogzhen.proxy.client;import com.gaogzhen.proxy.interceptor.TimerMethodInterceptor;
import com.gaogzhen.proxy.service.UserService;
import net.sf.cglib.proxy.Enhancer;/*** 模拟客户端* @author gaogzhen*/
public class Client {public static void main(String[] args) {// 1创建字节码增强对象//  该对象上CGLIB库中的核心对象,需要该类生成代理类Enhancer enhancer = new Enhancer();// 1.1 指定父类,即目标类enhancer.setSuperclass(UserService.class);// 1.2 设置回调enhancer.setCallback(new TimerMethodInterceptor());// 2 创建代理对象// 2.1 在内存中生成目标类的子类,实际上代理类的字节码// 2.2 创建代理对象UserService userServiceProxy = (UserService) enhancer.create();System.out.println(userServiceProxy);// 3 调用代理对象的代理方法boolean success = userServiceProxy.login("admin", "123");userServiceProxy.logout();}
}

CGLIB在JDK版本1.8之后,想要正常运行,需要在启动项添加两个启动参数

  • VM options添加:--add-opens java.base/java.lang=ALL-UNNAMED
  • 程序运行参数添加:--add-opens java.base/sun.net.util=ALL-UNNAMED

如下图3.2-1所示:

在这里插入图片描述

测试结果:

====目标方法调用之前增强====
====目标方法调用之前增强====
====目标方法调用之后增强====
耗时:0
====目标方法调用之后增强====
耗时:6
com.gaogzhen.proxy.service.UserService$$EnhancerByCGLIB$$6f97ca11@65ae6ba4
====目标方法调用之前增强====
身份验证
====登陆成功====
====目标方法调用之后增强====
耗时:1
====目标方法调用之前增强====
===退出登陆====
====目标方法调用之后增强====
耗时:0
  • UserService$$EnhancerByCGLIB$$6f97ca11为生成的代理子类,有点印象,目标类名$$EnhancerByCGLIB$$xxxx这种的底层就是使用CGLIB动态代理生成的。

结语

如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/spring6-study

参考:

[1]Spring框架视频教程[CP/OL].P69-75.

[2]ChatGPT

[3]UML一一 类图关系 (泛化、实现、依赖、关联、聚合、组合)

[4]终于明白六大类UML类图关系了

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

chatgpt赋能python:Python中的按位取反

Python中的按位取反 Python中的按位取反是一种常见的操作&#xff0c;它可以让我们快速地对二进制的数字进行取反操作。在本文中&#xff0c;我们将介绍Python中的按位取反操作&#xff0c;并探讨它的用途和示例。 什么是按位取反 按位取反是一种将二进制数中的每一位进行反…

HDBits刷题2: Circuit

1.combinational logic: 组合逻辑 1.1 basic gates: 基本逻辑门 wire 解答: module top_module (input in,output out);assign out in; endmodule GND 解答: module top_module (output out);assign out 1b0; endmodule NOR 解答: module top_module (input in1,input in2,ou…

stm32f103rct6使用内部晶振作为时钟源

目录 正点原子库函数1.void SystemInit(void)2.FLASH3.宏定义4.查看5.延时6.最终结果7.精准延时尝试&#xff08;失败&#xff09; HAL库函数1 宏定义2 时钟配置3 main函数中调用4 例子代码 寄存器版本&#xff08;跑通串口&#xff09;代码示波器查看波特率 正点原子库函数 s…

Esight | 类比ChatGPT的AI助理

很多行业内的小伙伴都在使用我们的低功耗分析设备mPower1203&#xff0c;它为大家在产品功耗的分析评估和优化上提供了很大的帮助&#xff0c;也为产品的工厂自动化提供了便捷的应用。为了更好的服务于研发工程师&#xff0c;配套的上位机工具Esight集成了ChatGPT【AI助理】的功…

0101壳-手写springboot-springboot系列

文章目录 1 前言1 创建我们自己的pringboot模块1.1 引入相关依赖1.1 启动类注解1.2 启动类 2 测试模块3 启动测试结语 1 前言 springboot有以下作用&#xff1a; 简化配置&#xff1a;Spring Boot提供了一组预定义的自动配置选项&#xff0c;可以快速地配置应用程序&#xff…

网络:chrome抓包

Network面板 按F12或者CTRLSHIFTI就可以召唤出这个面板 控制器&#xff1a;控制面板的外观和功能过滤器&#xff1a;过滤请求列表中显示的资源概览&#xff1a;显示HTTP请求、响应的时间轴请求列表&#xff1a;默认按照请求的先后时间排序&#xff0c;每选择一个请求还会跳出…

用ChatGPT高效学习:7天入门Python网络爬虫

以前不懂编程&#xff0c;但经常要从互联网上批量下载一些文件图片视频、收集整理数据等&#xff0c;手工操作耗时耗力。用ChatGPT入门了Python编程后&#xff0c;就寻思着可以再利用ChatGPT入门网络爬虫。 先让ChatGPT给我列出一个学习计划&#xff1a; 我有一些Python编程基…

Oracle 发力 MySQL,MariaDB 成功上市,大规模融资锐减 | 解读数据库的 2022

又一年过去了&#xff0c;生活还在继续&#xff0c;现在是反思去年数据库世界所发生事件的绝佳时机。 链接&#xff1a;https://ottertune.com/blog/2022-databases-retrospective/ 声明&#xff1a;本文为 CSDN 翻译&#xff0c;未经允许禁止转载。 作者 | Andy Pavlo 译者 | …

【GPT-4 ChatGPT】第 2 章 :深入了解GPT-4 和 ChatGPT API

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

Python基于Oxford-IIIT Pet Dataset实现宠物识别系统

先看效果&#xff1a; Oxford-IIIT Pet Dataset是一个不错的数据集&#xff0c;牛津官方整理出来的一个关于各种猫和狗的数据集&#xff0c;可以很方便被我们用来做图像识别或者是图像分割类型的任务&#xff0c;这里我们主要是做图像识别的应用。 官方介绍如下所示&#xff1a…

Python用户管理系统,宠物管理系统

用户管理系统 surface """ #三引号是Python的注释符号&#xff0c;但也可以作为字符串输出 **************************************** 用户管理系统 **************************************** 1、注册新用户 2、用户登录 3、用户注销 4、用户信息显示 5、退…

基于涂鸦智能的宠物喂食器

基于涂鸦智能的宠物喂食器 一、开发计划二、涂鸦三明治开发套件涂鸦三明治 Wi-Fi MCU 通信板喇叭涂鸦三明治H桥直流电机驱动功能板涂鸦三明治直流供电电源板MCU主控板 三、产品开发1、产品创建进入涂鸦IoT平台创建产品选择对应的功能点和设备面板下载SDK 2、MCU SDK移植对串口寄…

宠物领养平台的分析与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 文末获取源码 项目编号:BS-PT-052 运行环…

智能宠物项圈app开发解决方案

智能宠物项圈app开发解决方案&#xff0c;今天主要介绍的就是智能宠物项圈app开发方案中的功能。它的功能主要有多重定位&#xff0c;实时定位、出入范围提醒&#xff0c;踪迹随时可寻、远程呼唤、电子围栏、活动监测等&#xff0c;接下来我就来全面的介绍一下。 智能宠物项圈a…

宠物店会员管理系统| 宠物店小程序

国内养宠家庭非常多&#xff0c;推动着国内宠物市场发展&#xff0c;而围绕宠物的细分行业&#xff0c;如宠物食品、宠物用品/医疗/美容/婚介/殡葬等&#xff0c;2019年我国宠物市场规模达2024亿元&#xff0c;预计2023年&#xff0c;市场规模将突破4000亿元左右。 未来的宠物市…

智能宠物饲养系统设计

word完整版可点击如下下载>>>>>>>> 智能宠物饲养系统设计.rar-其它文档类资源-CSDN下载1、资源内容&#xff1a;毕业设计lun-wenword版10000字&#xff1b;开题报告&#xff0c;任务书2、学习目标&#xff1a;快速更多下载资源、学习资料请访问CSDN下…

宠物服务App功能简介

随着时代的变革与发展人们的生活变得越来越好&#xff0c;也变的越来越多样化。物质生活的满足后&#xff0c;人们开始找寻其他的一些兴趣爱好&#xff0c;让自己的生活变的更加多彩&#xff0c;有人种花、有人养鸟、有人养猫、有人养狗等等。不管是养什么都是需要细心照顾才能…

线上宠物领养系统

实现功能 客户端&#xff1a;客户可以查询数据库的宠物信息并根据查询的宠物信息选择自己喜欢的宠物进行领养。 服务器&#xff1a;服务器实现了对管理员相关信息的保存&#xff0c;管理员必须输入正确的用户名和密码才能对数据库信息进行增删改查等操作。服务器也可以直接对数…

软件官网页面模板

此项目由Htmlcss结构搭建而成 里面自适应移动端而做出调整 上代码: 使用了该模板的请将出处表明 项目结构 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" conte…

(学习笔记)使用CHATGPT写的前端页面模板

一、学生管理系统的登录页面 写一个好看的学生管理系统的登录页面&#xff0c;学生使用用户名和密码进行登录 <!DOCTYPE html> <html> <head><title>Student Management System - Login</title><style>body {font-family: Arial, sans-s…