首页 > 其他分享 >springboot自动配置原理

springboot自动配置原理

时间:2024-08-17 19:49:07浏览次数:6  
标签:依赖 springboot 配置 class bean 自动 注解 原理 public

springboot自动配置原理

springboot的出现就是为了简化Spring框架的开发,快速构建java项目。

springboot的两个核心特性就是起步依赖和自动配置。

起步依赖原理:maven的依赖传递

自动配置原理:条件注入、SPI机制

起步依赖

假如我们没有使用SpringBoot,用的是Spring框架进行web程序的开发,此时我们就需要引入web程序开发所需要的一些依赖。

image-20230114173645101

spring-webmvc依赖:这是Spring框架进行web程序开发所需要的依赖

servlet-api依赖:Servlet基础依赖

jackson-databind依赖:JSON处理工具包

如果要使用AOP,还需要引入aop依赖、aspect依赖

项目中所引入的这些依赖,还需要保证版本匹配,否则就可能会出现版本冲突问题。

如果我们使用了SpringBoot,就不需要像上面这么繁琐的引入依赖了。我们只需要引入一个依赖就可以了,那就是web开发的起步依赖:spring-boot-starter-web。

image-20230114174805852

为什么我们只需要引入一个web开发的起步依赖,web开发所需要的所有的依赖都有了呢?

  • 因为Maven的依赖传递。
  • 在SpringBoot给我们提供的这些起步依赖当中,已提供了当前程序开发所需要的所有的常见依赖(官网地址:https://docs.spring.io/spring-boot/docs/2.7.7/reference/htmlsingle/#using.build-systems.starters)。

  • 比如:spring-boot-starter-web,这是web开发的起步依赖,在web开发的起步依赖当中,就集成了web开发中常见的依赖:json、web、webmvc、tomcat等。我们只需要引入这一个起步依赖,其他的依赖都会自动的通过Maven的依赖传递进来。

结论:起步依赖的原理就是Maven的依赖传递。

自动配置

自动配置即根据开发者添加的jar包依赖,会自动将一些配置类的bean注册到IOC容器内。使用时只需方便地添加@Autowire或者@Resource等注解即可使用。

要了解SpringBoot的自动配置原理,我们必须先来了解SPI机制和条件注入是什么。

SPI机制

1、什么是SPI

SPI又分为JDK SPI和Spring SPI

SPI全称为Service Provider Interface,是JDK内置的一种服务提供发现机制,一种解耦非常优秀的思想,SPI可以很灵活的让接口和实现分离,让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。

在 Java 平台上,SPI 通常是通过 java.util.ServiceLoader 类实现的,但 Spring Boot 对这一概念进行了扩展,以支持其自动配置和模块化架构。JDK中一个经典的SPI就是jdbc接口,

JDK SPI

SPI可以很灵活的让接口和实现分离,让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展

举个例子:

创建一个PhoneService接口:

public interface PhoneService {
    void brand();
}

分别创建两个实现类:

public class HuaweiPhoneService implements PhoneService {
    @Override
    public void brand() {
        System.out.println("华为手机");
    }
}
public class XiaomiPhoneService implements PhoneService {
    @Override
    public void brand() {
        System.out.println("小米手机");
    }
}

在resources下新建META-INF/services/目录,然后新建接口全限定名的文件:com.my.demo.service.PhoneService,里面加上我们需要用到的实现类

image-20240817180147977

在 Java 平台上,SPI 通常是通过 java.util.ServiceLoader 类实现的,我们来写个测试类测试一下:

public class DemoApplication {
    public static void main(String[] args) {
        ServiceLoader<PhoneService> loaders = ServiceLoader.load(PhoneService.class, null);
        for (PhoneService phoneService : loaders) {
            phoneService.brand();
        }
    }
}

测试:

image-20240817181829619

原理:

Java中的 SPI 机制就是在每次类加载的时候会先去找到classpath下的 META-INF 文件夹下的 services 文件夹下的文件

将这个文件夹下面的所有文件先加载到内存中,然后根据这些文件的文件名和里面的文件内容找到相应接口的具体实现类

找到实现类后就可以通过反射去生成对应的对象,保存在一个 list 列表里面,然后可以通过迭代或者遍历的方式拿到对应的实例对象,生成不同的实现。

应用场景

JDBC DriverManager

在JDBC4.0之前,连接数据库的时候,通常会用Class.forName("com.mysql.jdbc.Driver")先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要用Class.forName("com.mysql.jdbc.Driver")来加载驱动,直接获取连接就可以了,现在这种方式就是使用了Java的SPI扩展机制来实现

JDBC接口定义

首先在java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商来提供的。

mysql实现

在mysql的jar包中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。

现在使用SPI扩展来加载具体的驱动,我们在Java中写连接数据库的代码的时候,不需要再使用Class.forName("com.mysql.jdbc.cj.Driver")来加载驱动了,而是直接使用如下代码:

String url = "jdbc:xxxx://xxxx:xxxx/xxxx";
Connection conn = DriverManager.getConnection(url, username, password);
...

Java使用SPI扩展机制来查找驱动相关的东西,关于驱动的查找其实都在DriverManager中,DriverManager是Java中的实现,用来获取数据库连接,在DriverManager中的相关代码如下:

image-20240817182312178

image-20240817182541166

Java通过SPI机制,会搜索classpath下以及jar包中所有的META-INF/services目录下的java.sql.Driver文件,并找到文件中的实现类的名字,根据驱动名字实例化各个实现类,完成驱动的注册

Spring Boot SPI

介绍

Spring Boot 对SPI机制这一概念进行了扩展,以支持其自动配置和模块化架构。

Spring Boot 利用 spring.factories (注意:从 SpringBoot 2.7 起自动配置不推荐使用 /META-INF/spring.factories 文件,而是在/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)文件,这个文件列出了与自动配置相关的接口及其实现类,Spring Boot 启动时会加载这些配置。

spring.factories 文件

这个文件使用键值对的格式列出了多种服务类型及其对应的实现类,常见的服务类型包括:

  • org.springframework.boot.autoconfigure.EnableAutoConfiguration:用于自动配置。

  • org.springframework.context.ApplicationListener:用于应用事件监听器。

  • org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider:用于模板引擎的可用性判断。

以下是 spring.factories 文件的一个典型例子:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.JpaAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

org.springframework.context.ApplicationListener=\
com.example.MyApplicationListener

Spring Boot 3 则要在 resources/META_INFO/spring/ 目录下新建

org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,并填入需要自动配置的

类的全路径:

com.example.JpaAutoConfiguration
不使用SPI机制

我们先来看一下不使用SPI机制怎么来实现bean的配置:

新建一个模块myhello,写一个MyHelloDemo类,放入IOC容器:

image-20240817183601739

@Component
public class MyHelloDemo {
    public void myHello() {
        System.out.println(" hello, you are using MyHelloDemo ");
    }
}

创建一个测试模块,引入myhello模块的依赖,创建引导类,和测试类:

image-20240817183711924

@SpringBootApplication
public class TestDemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(TestDemoApplication.class, args);
    }
}
@SpringBootTest
public class MyTest {

    // IOC容器对象
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void test() {
        try {
            MyHelloDemo bean = applicationContext.getBean(MyHelloDemo.class);
            bean.myHello();
        } catch (Exception e) {
            System.err.println(" MyHelloDemo.class not found in IOC");
        }
    }
}

这里我们通过ApplicationContext来拿到IOC容器里面的bean

此时运行测试类会发现找不到bean对象:

image-20240817184202721

引入进来的第三方依赖当中的bean以及配置类为什么没有生效?

  • 原因是因为,在类上添加@Component注解来声明bean对象时,还需要保证@Component注解能被Spring的组件扫描到。
  • SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启动类所在的当前包以及子包。
  • 当前包:com.test.demo, 第三方依赖中提供的包:com.myhello.demo(扫描不到)

image-20240817184338375

那么如何解决以上问题的呢?

  • 方案1:@ComponentScan 组件扫描
  • 方案2:@Import 导入(使用@Import导入的类会被Spring加载到IOC容器中)

方案一

@ComponentScan 组件扫描

@SpringBootApplication
@ComponentScan(basePackages = {"com.test","com.myhello"})
public class TestDemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(TestDemoApplication.class, args);
    }
}

重新测试,会发现可以输出了:

image-20240817184537845

缺点:当需要引入大量的第三方依赖,就需要在启动类上配置大量要扫描的包,这种方式会很繁琐

方案二

@Import 导入(使用@Import导入的类会被Spring加载到IOC容器中)

导入形式主要有以下几种:

  1. 导入普通类
  2. 导入配置类
  3. 导入ImportSelector接口实现类

1、导入普通类:

导入的类会被Spring加载到IOC容器中

@Import(MyHelloDemo.class)
@SpringBootApplication
public class TestDemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(TestDemoApplication.class, args);
    }
}

测试:

image-20240817185024038

2、导入配置类

  • 配置类

    去掉MyHelloDemo类上的@Component注解,新建一个配置类,自定义bean

@Configuration
public class MyHelloConfig {
    @Bean
    public MyHelloDemo myHelloDemo() {
        return new MyHelloDemo();
    }
}
  • 启动类
@SpringBootApplication
@Import(MyHelloConfig.class)
public class TestDemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(TestDemoApplication.class, args);
    }
}

测试:

image-20240817185527207

3、导入ImportSelector接口实现类

  • ImportSelector接口实现类
public class MyHelloImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.myhello.demo.MyHelloDemo"};
    }
}
  • 启动类
@Import(MyHelloImportSelector.class)
@SpringBootApplication
public class TestDemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(TestDemoApplication.class, args);
    }
}

测试:

image-20240817185754477

我们使用@Import注解通过这三种方式都可以导入第三方依赖中所提供的bean或者是配置类。

问题:如果基于以上方式完成自动配置,当要引入一个第三方依赖时,是不是还要知道第三方依赖中有哪些配置类和哪些Bean对象?相当麻烦!

当我们要使用第三方依赖,依赖中到底有哪些bean和配置类,谁最清楚?

  • 答案:第三方依赖自身最清楚。

结论:我们不用自己指定要导入哪些bean对象和配置类了,让第三方依赖它自己来指定。

怎么让第三方依赖自己指定bean对象和配置类?

  • 比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以@EnableXxx开头的注解,注解中封装的就是@Import注解

4、使用第三方依赖提供的 @EnableXxx注解

  • 第三方依赖中提供的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
//指定要导入哪些bean对象或配置类
@Import(MyHelloImportSelector.class)
public @interface EnableMyHelloConfig {
}
  • 在使用时只需在启动类上加上@EnableXxxxx注解即可
// 使用第三方依赖提供的Enable开头的注解
@EnableMyHelloConfig
@SpringBootApplication
public class TestDemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(TestDemoApplication.class, args);
    }
}

测试:

image-20240817190445637

以上四种方式都可以完成导入操作,但是第4种方式会更方便更优雅,而这种方式也是SpringBoot当中所采用的方式。

使用SPI机制

ok,这种手动配置bean是不是相当麻烦,我用你的第三方框架,最好不要让我配置,我就像引入依赖后,直接DI注入使用,这才方便呀。

那下面来看看使用SpringBoot SPI机制实现自动配置:

再新建一个模块mydemo,创建一个MyDemoService接口,再定义一个实现类:

public interface MyDemoService {
    void sayHello();
}
public class MyDemoServiceImpl implements MyDemoService {
    @Override
    public void sayHello() {
        System.out.println(" hello, MyDemoService... ");
    }
}

定义一个配置类;

@Configuration
public class MyDemoConfig {

    @Bean
    public MyDemoService myDemoService() {
        return new MyDemoServiceImpl();
    }
}

关键点:再resources目录下创建META-INF/spring文件夹,新建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,里面写上MyDemoConfig的全路径,这是实现自动配置的一个关键步骤

image-20240817191710408

在testdemo测试模块引入mydemo依赖,新建一个测试方法:

@Test
    public void test2() {
        try {
            MyDemoService bean = applicationContext.getBean(MyDemoService.class);
            System.out.println(" found MyDemoService.class in IOC ");
            bean.sayHello();
        } catch (Exception e) {
            System.err.println(" MyDemoService.class not found in IOC... ");
        }
    }

测试:可以成功执行

image-20240817191934644

此时会发现,我们做的操作只是引入了相关依赖,无需再手动配置,就可以通过IOC容器或DI依赖拿到bean对象,操作方法,这就是SpringBoot自动配置的强大之处。

条件注入

Spring Boot的自动配置还支持条件注入,在自动配置类声明bean的时候,除了在方法上加了一个@Bean注解以外,还会经常用到一个注解,就是以Conditional开头的这一类的注解。以Conditional开头的这些注解都是条件装配的注解。

@ConditionalXxx注解:

  • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。
  • 位置:方法、类
  • @ConditionalXxx本身是一个父注解,派生出大量的子注解:
    • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。
    • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。
    • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

下面来尝试一下:

@ConditionalOnClass(name = "com.test.demo.Hello")

环境中有对应字节码文件,才注册bean到IOC容器

@Configuration
@ConditionalOnClass(name = "com.test.demo.Hello")
public class MyDemoConfig {

    @Bean
	@ConditionalOnMissingBean
    public MyDemoService myDemoService() {
        return new MyDemoServiceImpl();
    }
}

测试:此时会发现IOC容器中没有MyDemoService的实例对象了

image-20240817192250383

在com.test.demo包下创建Hello类,再次运行就可以找到了:

image-20240817192440251

image-20240817192420605

再来尝试一个@ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

修改MyDemoConfig类上的注解,此时配置文件中存在mydemo.config.enable属性,并且对应的值为true时才装配这个bean到IOC容器:

@Configuration
//@ConditionalOnClass(name = "com.test.demo.Hello")
@ConditionalOnProperty(prefix = "mydemo.config", name = "enable",havingValue = "true")
public class MyDemoConfig {
    @Bean
    @ConditionalOnMissingBean
    public MyDemoService myDemoService() {
        return new MyDemoServiceImpl();
    }
}

运行测试类:会发现找不到bean了

image-20240817192713615

在配置文件中配置相关属性:

image-20240817192748980

此时再去运行测试类,就可以找到bean了:

image-20240817192820222

这就是

自动配置源码分析

要搞清楚SpringBoot的自动配置原理,要从SpringBoot启动类上使用的核心注解

@SpringBootApplication开始分析:

image-20230115001439110

在@SpringBootApplication注解中包含了:

  • 元注解(不再解释)
  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

我们先来看第一个注解:@SpringBootConfiguration

image-20230115001950076

@SpringBootConfiguration注解上使用了@Configuration,表明SpringBoot启动类就是一个配置类。

@Indexed注解,是用来加速应用启动的(不用关心)。

接下来再先看@ComponentScan注解:

image-20230115002450993

@ComponentScan注解是用来进行组件扫描的,扫描启动类所在的包及其子包下所有被@Component及其衍生注解声明的类。

SpringBoot启动类,之所以具备扫描包功能,就是因为包含了@ComponentScan注解。

最后我们来看看@EnableAutoConfiguration注解(自动配置核心注解):

image-20230115002743115

使用@Import注解,导入了实现ImportSelector接口的实现类。

AutoConfigurationImportSelector类是ImportSelector接口的实现类。

image-20230115003242549

AutoConfigurationImportSelector类中重写了ImportSelector接口的selectImports()方法:

image-20230115003348288

selectImports()方法底层调用getAutoConfigurationEntry()方法,获取可自动配置的配置类信息集合

image-20230115003704385

getAutoConfigurationEntry()方法通过调用getCandidateConfigurations(annotationMetadata, attributes)方法获取在配置文件中配置的所有自动配置类的集合

image-20230115003903302

getCandidateConfigurations方法的功能:

获取所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件、META-INF/spring.factories文件中配置类的集合

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件和META-INF/spring.factories文件这两个文件在哪里呢?

  • 通常在引入的起步依赖中,都有包含以上两个文件

image-20230129090835964

image-20230115064329460

自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是:

  • @SpringBootConfiguration
    • 声明当前类是一个配置类
  • @ComponentScan
    • 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包)
  • @EnableAutoConfiguration
    • 封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类)
      • 在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中META-INF/spring.factories、META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。

当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。

但是不是所有的bean都会被注册到IOC容器中

最后呢给大家抛出一个问题:在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中定义的配置类非常多,而且每个配置类中又可以定义很多的bean,那这些bean都会注册到Spring的IOC容器中吗?

答案:并不是。 在声明bean对象时,上面有加一个以@Conditional开头的注解,这种注解的作用就是按照条件进行装配,只有满足条件之后,才会将bean注册到Spring的IOC容器中

总结

spring boot的两个核心特性:起步依赖和自动配置

起步依赖的原理:maven的依赖传递

自动配置的原理:条件注入、SPI机制

spring boot应用启动时,会加载主配置类,也就是被@SpringBootApplication注解修饰的引导类,该注解组合了三个注解@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。

@EnableAutoConfiguration这个注解才是自动配置的核心。

  • 它组合了一个@Import注解,Import注解里面指定了一个ImportSelector接口的实现类。
  • 在这个实现类中,重写了ImportSelector接口中的selectImports()方法。
  • 而selectImports()方法中会去读取所有jar包中的一个配置文件,并将该配置文件中定义的配置类做为selectImports()方法的返回值返回,返回值代表的就是需要将哪些类交给Spring的IOC容器进行管理。

SpringBoot 2.7 之前自动配置使用 /META-INF/spring.factories 文件

SpringBoot 2.7之后的版本使用/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件

  • 所有自动配置类的中声明的bean并不是都会加载到Spring的IOC容器中,这些配置类中在声明bean时,通常都会添加@Conditional开头的注解,这个注解就是进行条件装配。而Spring会根据Conditional注解有选择性的进行bean的创建。

核心:

bean是怎么装配的

  • SPI机制:把要自动装配的类放在了什么地方:要自动装配的类放到了文件里

    • springboot 3的存放位置

      META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

    • springboot 2的存放位置

      META-INF/spring.factories

  • 文件里放了要自动装配的类的全限定名,通过IO流把这些类的全限定名读出来,通过反射获取类的构造器创建Bean对象,再往IOC容器里面放对应的bean

  • 条件装配,不是所有的bean都装配

标签:依赖,springboot,配置,class,bean,自动,注解,原理,public
From: https://www.cnblogs.com/kk-koala/p/18364883

相关文章

  • 任意文件读取与下载的原理及修复
     原文链接:https://cloud.tencent.com/developer/article/1597942原理没有对读取下载的文件做限制漏洞利用方式由于我们不知道敏感文件的路径,我们可以利用../../(返回上次目录)依次猜解,让漏洞利用变的猥琐。例如漏洞的危害:通过任意文件下载,可以下载服务器的任意文件,web业......
  • netdom 和 PowerShell 的 Add-Computer 命令可以将计算机加入特定的组织单位(OU)。如果
    netdom和PowerShell的Add-Computer命令可以将计算机加入特定的组织单位(OU)。使用 netdom:netdom是一个用于管理Windows域的命令行工具。要将计算机加入到特定的OU,使用以下命令:bashCopyCodenetdomjoin<ComputerName>/domain:<DomainName>/ou:<OUPath>/userd:<Use......
  • application.yml文件配置springboot项目
    基本用法#注意空格都不能省#配置端口号server:port:8080address:127.0.0.1#配置数据库spring:datasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/tliasusername:rootpassword:root#定义对象/Ma......
  • 编译原理
    引论语言处理器编译器:编译器将整个程序一次性翻译成机器可执行的目标代码(如二进制代码),然后再执行这个目标代码。解释器:解释器逐行或逐块地将源代码直接翻译成机器可执行的形式,然后立即执行。预处理器:把源程序聚合在一起的任务有时会由一个被称为预处理器(preprocess......
  • 【计算机毕设项目推荐】基于SpringBoot的springboot单位考勤系统的管理设计与实现
    前言:我是天码编程,从事计算机开发行业数年,专注Java程序设计开发、源码分享、技术指导和毕业设计,欢迎各位前来交流讨论......
  • 【计算机毕设选题推荐】基于SpringBoot的springboot博物馆展览与服务一体化平台的设计
    前言:我是天码编程,从事计算机开发行业数年,专注Java程序设计开发、源码分享、技术指导和毕业设计,欢迎各位前来交流讨论......
  • 【Java毕设选题推荐】基于SpringBoot的springboot基于Android的房屋租赁App
    前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务......
  • HCL学习——IP路由原理
    路由器连接的是一个个网段,交换机把多个pc连起来行程一个网段,路由器把多个网段连接起来形成更大的网络空间。路由器不负责把数据包交给具体的一个pc,他负责把数据包从一个网段交给另一个网段。交给具体的pc是交换机的活。 沿途每一台路由器、目的网段(而不是途径路段)、往返双向。......
  • springboot+vue宠物领养系统【程序+论文+开题】-计算机毕业设计
    系统程序文件列表开题报告内容研究背景在当今社会,随着人们生活水平的提高和情感需求的日益增长,宠物已成为许多家庭不可或缺的一员。然而,宠物领养过程中存在信息不对称、领养渠道不畅、宠物健康信息不透明等问题,给宠物爱好者带来了诸多困扰。同时,流浪动物数量的不断增加也引......
  • springboot+vue宠物领养系统【程序+论文+开题】-计算机毕业设计
    系统程序文件列表开题报告内容研究背景随着现代生活节奏的加快和人们情感需求的日益增长,宠物已成为许多家庭不可或缺的一员,它们不仅提供了陪伴与慰藉,还促进了人与自然的和谐共处。然而,宠物数量的激增也带来了诸如流浪动物增多、宠物领养信息不对称等问题。传统的宠物领养方......