【Spring Boot】Spring Boot 中的 Starter

Spring Boot 中的 Starter

  • 1.常用 Starter
  • 2.为什么要用 Starter
  • 3.Starter 有哪些要素

我们都知道,Spring 的功能非常强大,但也有些弊端。比如:我们需要手动去配置大量的参数,没有默认值,需要我们管理大量的 jar 包和它们的依赖。为了提升 Spring 项目的开发效率,简化一些配置,Spring 官方引入了 SpringBoot。当然,引入 SpringBoot 还有其他原因,在这里就不过多描述了。

而 Spring Boot 为了简化配置,提供了非常多的 Starter。它先打包好与常用模块相关的所有 JAR 包,并完成自动配置,然后组装成 Starter(如把 Web 相关的 Spring MVC、容器等打包好后组装成 spring-boot-starter-web)。这使得在开发业务代码时不需要过多关注框架的配置,只需关注业务逻辑即可。

1.常用 Starter

Spring Boot 提供了很多开箱即用的 Starter,大概有近 50 种,常见的如下表所示。

Starter
说明
spring-boot-starter-web用于构建 Web。包含 RESTful 风格框架、SpringMVC 和默认的嵌入式容器 Tomcat
spring-boot-starter-test用于测试
spring-boot-starter-data-jpa带有 Hibernate 的 Spring Data JPA
spring-boot-starter-jdbc传统的 JDBC。轻量级应用可以使用,学习成本低,但最好使用 JPA 或 MyBatis
spring-boot-starter-thymeleaf支持 Thymeleaf 模板
spring-boot-starter-mail支持 Java Mail、Spring Email 发送邮件
spring-boot-starter-integrationSpring 框架创建的一个 API,面向企业应用集成(EAI)
spring-boot-starter-mobileSpring MVC 的扩展,用来简化手机上的 Web 应用程序开发
spring-boot-starter-data-redis通过 Spring Data Redis、Redis Client 使用 Redis
spring-boot-starter-validationBean Validation 是一个数据验证的规范,Hibernate Validator 是一个数据验证框架
spring-boot-starter-websocket相对于非持久的协议 HTTP,Websocket 是一个持久化的协议
spring-boot-starter-web-servicesSOAP Web Services
spring-boot-starter-hateoas为服务添加 HATEOAS 功能
spring-boot-starter-security用 Spring Security 进行身份验证和授权
spring-boot-starter-data-rest用 Spring Data REST 公布简单的 REST 服务

如果想使用 Spring 的 JPA 操作数据库,则需要在项目中添加 spring-boot-starter-data-jpa 依赖,即在 pom.xml 文件中的 <dependencies> 和元素之间加入依赖,具体代如下:

<dependencies>...<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>...
</dependencies>

如果依赖项没有版本号,则 Spring Boot 会根据自己的版本号自动关联。如果需要特定的版本则需要加上 version 元素。

2.为什么要用 Starter

在 SpringBoot 还没有出来之前,我们使用 Spring 开发项目。如果程序需要连接数据库,我们一般会使用 Hibernate 或 Mybatis 等 ORM 框架,这里我以 Mybatis 为例,具体的操作步骤如下:

  • 到 Maven 仓库去找需要引入的 mybatisjar 包,选取合适的版本。
  • 到 Maven 仓库去找 mybatis-spring 整合的 jar 包,选取合适的版本。
  • 在 Spring 的 applicationContext.xml 文件中配置 dataSourcemybatis 相关信息。

当然有些朋友可能会指正,不是还需要引入数据库驱动包吗?

确实需要引入,但数据库驱动有很多,比如:mysqloraclesqlserver,这不属于 mybatis 的范畴,使用者可以根据项目的实际情况单独引入。

如果程序只是需要连接数据库这一个功能还好,按上面的步骤做基本可以满足需求。但是,连接数据库可能只是庞大的项目体系中一个环节,实际项目中往往更复杂,需要引入更多的功能,比如:连接 redis、连接 mongodb、使用 rocketmq、使用 excel 功能等等。

引入这些功能的话,需要再把上面的步骤再重复一次,工作量无形当中增加了不少,而且有很多重复的工作。

另外,还是有个问题,每次到要到 Maven 中找合适的版本,如果哪次找的 mybatis.jar 包和 mybatis-spring.jar 包版本不兼容,程序不是会出现问题?

SpringBoot 为了解决以上两个问题引入了 Starter 机制。

3.Starter 有哪些要素

我们首先一起看看 mybatis-spring-boot-starter.jar 是如何定义的。

在这里插入图片描述
可以看到它的 META-INF 目录下只包含了:

  • pom.protperties:配置 Maven 所需的项目 versiongroupIdartifactId
  • pom.xml:配置所依赖的 jar 包。
  • MANIFEST.MF:这个文件描述了该 jar 文件的很多信息。
  • spring.provides:配置所依赖的 artifactId,给 IDE 使用的,没有其他的作用。

注意一下,没有一行代码。

我们重点看一下 pom.xml,因为这个 jar 包里面除了这个没有啥重要的信息。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot</artifactId><version>1.3.1</version></parent><artifactId>mybatis-spring-boot-starter</artifactId><name>mybatis-spring-boot-starter</name><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId></dependency></dependencies>
</project>

从上面可以看出,pom.xml 文件中会引入一些 jar 包,其中除了引入 spring-boot-starter,之外重点看一下:mybatis-spring-boot-autoconfigure

我们找到 mybatis-spring-boot-autoconfigure.jar 文件,打开这个文件。

在这里插入图片描述

里面包含如下文件:

  • pom.properties:配置 Maven 所需的项目 versiongroupIdartifactId
  • pom.xml:配置所依赖的 jar
  • additional-spring-configuration-metadata.json:手动添加 IDE 提示功能
  • MANIFEST.MF:这个文件描述了该 jar 文件的很多信息
  • spring.factories:SPI(Service Provider Interface)会读取的文件
  • spring-configuration-metadata.json:系统自动生成的 IDE 提示功能
  • ConfigurationCustomizer:自定义 Configuration 回调接口
  • MybatisAutoConfigurationmybatis 配置类
  • MybatisPropertiesmybatis 属性类
  • SpringBootVFS:扫描嵌套的 jar 包中的类

spring-configuration-metadata.jsonadditional-spring-configuration-metadata.json 的功能差不多,我们在 applicationContext.properties 文件中输入 spring 时,会自动出现下面的配置信息可供选择,就是这个功能了。

在这里插入图片描述

这两个文件有什么区别?

如果 pom.xml 中引入了 spring-boot-configuration-processor 包,则会自动生成 spring-configuration-metadata.json

如果需要手动修改里面的元数据,则可以在 additional-spring-configuration-metadata.json 中编辑,最终两个文件中的元数据会合并到一起。

MybatisProperties 类是 属性实体类

@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {public static final String MYBATIS_PREFIX = "mybatis";private String configLocation;private String[] mapperLocations;private String typeAliasesPackage;private String typeHandlersPackage;private boolean checkConfigLocation = false;private ExecutorType executorType;private Properties configurationProperties;@NestedConfigurationPropertyprivate Configuration configuration;public String getConfigLocation() {return this.configLocation;}public void setConfigLocation(String configLocation) {this.configLocation = configLocation;}@Deprecatedpublic String getConfig() {return this.configLocation;}@Deprecatedpublic void setConfig(String config) {this.configLocation = config;}public String[] getMapperLocations() {return this.mapperLocations;}public void setMapperLocations(String[] mapperLocations) {this.mapperLocations = mapperLocations;}public String getTypeHandlersPackage() {return this.typeHandlersPackage;}public void setTypeHandlersPackage(String typeHandlersPackage) {this.typeHandlersPackage = typeHandlersPackage;}public String getTypeAliasesPackage() {return this.typeAliasesPackage;}public void setTypeAliasesPackage(String typeAliasesPackage) {this.typeAliasesPackage = typeAliasesPackage;}public boolean isCheckConfigLocation() {return this.checkConfigLocation;}public void setCheckConfigLocation(boolean checkConfigLocation) {this.checkConfigLocation = checkConfigLocation;}public ExecutorType getExecutorType() {return this.executorType;}public void setExecutorType(ExecutorType executorType) {this.executorType = executorType;}public Properties getConfigurationProperties() {return configurationProperties;}public void setConfigurationProperties(Properties configurationProperties) {this.configurationProperties = configurationProperties;}public Configuration getConfiguration() {return configuration;}public void setConfiguration(Configuration configuration) {this.configuration = configuration;}public Resource[] resolveMapperLocations() {ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();List<Resource> resources = new ArrayList<Resource>();if (this.mapperLocations != null) {for (String mapperLocation : this.mapperLocations) {try {Resource[] mappers = resourceResolver.getResources(mapperLocation);resources.addAll(Arrays.asList(mappers));} catch (IOException e) {// ignore}}}return resources.toArray(new Resource[resources.size()]);}
}

可以看到 Mybatis 初始化所需要的很多属性都在这里,相当于一个 JavaBean。

下面重点看一下 MybatisAutoConfiguration 的代码:

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);private final MybatisProperties properties;private final Interceptor[] interceptors;private final ResourceLoader resourceLoader;private final DatabaseIdProvider databaseIdProvider;private final List<ConfigurationCustomizer> configurationCustomizers;public MybatisAutoConfiguration(MybatisProperties properties,ObjectProvider<Interceptor[]> interceptorsProvider,ResourceLoader resourceLoader,ObjectProvider<DatabaseIdProvider> databaseIdProvider,ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {this.properties = properties;this.interceptors = interceptorsProvider.getIfAvailable();this.resourceLoader = resourceLoader;this.databaseIdProvider = databaseIdProvider.getIfAvailable();this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();}@PostConstructpublic void checkConfigFileExists() {if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());Assert.state(resource.exists(), "Cannot find config location: " + resource+ " (please add config file or check your Mybatis configuration)");}}@Bean@ConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factory = new SqlSessionFactoryBean();factory.setDataSource(dataSource);factory.setVfs(SpringBootVFS.class);if (StringUtils.hasText(this.properties.getConfigLocation())) {factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));}Configuration configuration = this.properties.getConfiguration();if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {configuration = new Configuration();}if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {for (ConfigurationCustomizer customizer : this.configurationCustomizers) {customizer.customize(configuration);}}factory.setConfiguration(configuration);if (this.properties.getConfigurationProperties() != null) {factory.setConfigurationProperties(this.properties.getConfigurationProperties());}if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors);}if (this.databaseIdProvider != null) {factory.setDatabaseIdProvider(this.databaseIdProvider);}if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());}if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());}if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {factory.setMapperLocations(this.properties.resolveMapperLocations());}return factory.getObject();}@Bean@ConditionalOnMissingBeanpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {ExecutorType executorType = this.properties.getExecutorType();if (executorType != null) {return new SqlSessionTemplate(sqlSessionFactory, executorType);} else {return new SqlSessionTemplate(sqlSessionFactory);}}public static class AutoConfiguredMapperScannerRegistrarimplements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {private BeanFactory beanFactory;private ResourceLoader resourceLoader;@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);try {if (this.resourceLoader != null) {scanner.setResourceLoader(this.resourceLoader);}List<String> packages = AutoConfigurationPackages.get(this.beanFactory);if (logger.isDebugEnabled()) {for (String pkg : packages) {logger.debug("Using auto-configuration base package '{}'", pkg);}}scanner.setAnnotationClass(Mapper.class);scanner.registerFilters();scanner.doScan(StringUtils.toStringArray(packages));} catch (IllegalStateException ex) {logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);}}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}}@org.springframework.context.annotation.Configuration@Import({ AutoConfiguredMapperScannerRegistrar.class })@ConditionalOnMissingBean(MapperFactoryBean.class)public static class MapperScannerRegistrarNotFoundConfiguration {@PostConstructpublic void afterPropertiesSet() {logger.debug("No {} found.", MapperFactoryBean.class.getName());}}
}

这个类就是一个 Configuration(配置类),它里面定义很多 Bean,其中最重要的就是 SqlSessionFactory 的 Bean 实例,该实例是 Mybatis 的核心功能,用它创建 SqlSession,对数据库进行 CRUD 操作。

除此之外,MybatisAutoConfiguration 类还包含了:

  • @ConditionalOnClass 配置了只有包含 SqlSessionFactory.classSqlSessionFactoryBean.class,该配置类才生效。
  • @ConditionalOnBean 配置了只有包含 dataSource 实例时,该配置类才生效。
  • @EnableConfigurationProperties 该注解会自动填充 MybatisProperties 实例中的属性。
  • @AutoConfigureAfter 配置了该配置类在 DataSourceAutoConfiguration 类之后自动配置。

这些注解都是一些辅助功能,决定 Configuration 是否生效,当然这些注解不是必须的。

接下来,重点看看 spring.factories 文件有啥内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

里面只有一行配置,即 Key 为 EnableAutoConfiguration,Value 为 MybatisAutoConfiguration

好了,介绍了这么多东西,现在我们来总结一下,

Starter 几个要素如下图所示:

在这里插入图片描述
那么,编写 Starter 需要哪些步骤?

  • 需要定义一个名称为 xxx-spring-boot-starter 的空项目,里面不包含任何代码,可以有 pom.xmlpom.properties 文件。
  • pom.xml 文件中包含了名称为 xxx-spring-boot-autoconfigure 的项目。
  • xxx-spring-boot-autoconfigure 项目中包含了名称为 xxxAutoConfiguration 的类,该类可以定义一些 Bean 实例。当然,Configuration 类上可以打一些如:ConditionalOnClassConditionalOnBeanEnableConfigurationProperties 等注解。
  • 需要在 spring.factories 文件中增加 Key 为 EnableAutoConfiguration,Value 为 xxxAutoConfiguration

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

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

相关文章

【JS面试题】闭包

一、自由变量 在学习闭包之前&#xff0c;首先了解一下什么是自由变量&#xff1a; 一个变量在当前作用域&#xff08;比如函数内&#xff09;没有定义&#xff0c;但是被使用了&#xff0c;这个变量就是自由变量。在定义该函数的位置&#xff0c;向上级作用域&#xff0c;一…

【C++】STL — map和set的使用详细介绍

前言 本章将继续学习STL中的两个很重要的容器map和set&#xff0c;其底层实现是封装了一个红黑树&#xff0c;我们通过本节来学习和深入了解一下这两大容器。。。 序列式容器&#xff1a; string 、Vector、List 、dequeue 关联式容器&#xff1a;MAP 、SET、nordered_map、uno…

MySql软件安装

1.打开mysql官网网址 MySQL :: Download MySQL Community Server 2.本次针对版本8的图形化界面安装&#xff0c;下载成功后接下来对MySQL进行安装 3.图形化下载后有一个MSI文件 4.我们安装典型即可&#xff0c;选择第一个 5.选择数据库信息存放的路径&#xff0c;我默认放在C盘…

如何解决pycharm在HTML文件中注释快捷键出错的问题(HTML注释规则出错)

文章目录 💢 问题 💢🏡 演示环境 🏡💯 解决方案 💯⚓️ 相关链接 ⚓️💢 问题 💢 你是否在编程时遇到过这样的烦恼?当你正专注地编写HTML代码,想要快速注释掉某部分内容时,却发现PyCharm的注释快捷键失灵了(没有使用正确的注释格式)。这不仅打断了你的工作…

Spring Event--踩坑(注意事项)

原文网址&#xff1a;Spring Event--踩坑(注意事项)-CSDN博客 简介 本文介绍Spring的事件的使用注意事项。 Spring Event框架实现了基于事件的发布订阅机制。开发者可以自定义事件&#xff0c;发布事件&#xff0c;Spring 会将该事件广播给监听该事件的监听者。监听者可以实…

linux学习:视频输入+V4L2

目录 V4L2 视频采集流程 代码例子 核心命令字和结构体 VIDIOC_ENUM_FMT VIDIOC_G_FMT / VIDIOC_S_FMT / VIDIOC_TRY_FM VIDIOC_REQBUFS VIDIOC_QUERYBUF VIDIOC_QBUF /VIDIOC_DQBUF VIDIOC_STREAMON / VIDIOC_STREAMOFF V4L2 是 Linux 处理视频的最新标准代码模块&…

Java后端实现对象与文件接收数据(minio测试)

实现思路&#xff1a; 1. 两个接口实现&#xff0c;一个接对象数据(file)&#xff0c;一个接文件数据(json)。 2. json对象(base64String) 实体类信息 &#xff0c;请求体统一接收 3. file, String name ,String password ,String name &#xff0c; Controller层接收 统一…

OpenHarmony 实战开发——如何编译OpenHarmony自带APP

概述 OpenHarmony 的主干代码是开源社区的重要学习资源&#xff0c;对于想进行应用开发和熟悉 OpenHarmony 能力的同学主干代码是非常重要的资源&#xff0c;在主干代码的 applications 目录里聚集了很多原生的应用实现&#xff0c;那么如何编译这些代码就是我们这篇文章的主要…

Vue3项目打包部署到云服务器的Nginx中

文章目录 一、打包vue3项目二、dist文件夹上传到服务器三、改nginx配置文件Docker安装nginx 一、打包vue3项目 npm run build 二、dist文件夹上传到服务器 将dist文件夹放到docker安装的nginx中的html目录下 三、改nginx配置文件 然后重启nginx【改了配置文件重启nginx才能…

消息中间件Kafka(PHP版本)

小编最近需要用到消息中间件&#xff0c;有需要要复习一下以前的东西&#xff0c;有需要的自取&#xff0c;强调一点&#xff0c;如果真的想了解透彻&#xff0c;一定要动手&#xff0c;脑袋会了不代表就会写了 Kafka是由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅…

等保一体机能过三级等保吗?过等保无需再买安全设备如何做到?

等保一体机能过三级等保吗&#xff1f;过等保无需再买安全设备如何做到&#xff1f; 全云在线 2024-03-28 12:08 广东 尽管等保建设的标准是统一的&#xff0c;但由于不同行业和用户规模的差异&#xff0c;建设方案呈现出多样化的特点。 虽然重点行业过等保现象确实已经十分…

免费思维13招之八:跨行业思维

免费思维13招之八:跨行业思维 免费思维的另一大战略思维——跨行业型思维。 跨行业型思维有两种:一种是通过跨行业,把自己的产品免费掉,从而赚取其他行业的利润。另一种是通过跨行业,把别人的主流产品免费掉,从而增大自己产品的销量。 第一种,把自己的产品免费,从而赚…

Hadoop3.4.0 完全分布式集群 运行环境搭建 VMware Workstation 虚拟机 大数据系列 一

一 生产环境集群模式部署&#xff0c;需要多台主机&#xff0c;主机之间通过密钥相互访问. 1 配置如图 节点名字节点IP系统版本master11192.168.50.11centos 8.5slave12192.168.50.12centos 8.5slave13192.168.50.13centos 8.5 2 安装服务器 #先安装一台master11&#xff…

Hadoop-未授权访问-内置配合命令执行RCE

一、Hadoop常见端口及配置文件 Hadoop中有多种端口&#xff0c;这些端口用于不同的服务和通信。以下是Hadoop中常见的端口以及它们的用途&#xff1a; NameNode Web界面端口 (默认: 9870)NameNode 对客户端服务端口 (默认: 8020)Secondary NameNode Web界面端口 (默认: 9868)…

Ubuntu20.4部署Cuda12.4

准备Ubuntu20.4 VM 安装Cuda12.4 1.进入如下界面安装安装Cuda12.4版本&#xff1a; CUDA Toolkit 12.4 Update 1 Downloads | NVIDIA Developerhttps://developer.nvidia.com/cuda-downloads?target_osLinux&target_archx86_64&DistributionUbuntu&target_vers…

MySQL软件安装基于压缩包

打开mysql官网网址 MySQL :: Download MySQL Community Server 本次针对版本8的安装包方式进行安装&#xff0c;下载成功后接下来对MySQL进行安装 下载后有一个以zip后缀结尾的压缩包文件 对于安装包方式安装&#xff0c;比起可视化安装省去了许多安装步骤&#xff0c;这里直接…

Python | Leetcode Python题解之第86题分隔链表

题目&#xff1a; 题解&#xff1a; class Solution:def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:sml_dummy, big_dummy ListNode(0), ListNode(0)sml, big sml_dummy, big_dummywhile head:if head.val < x:sml.next headsml sm…

C++ | Leetcode C++题解之第86题分隔链表

题目&#xff1a; 题解&#xff1a; class Solution { public:ListNode* partition(ListNode* head, int x) {ListNode* small new ListNode(0);ListNode* smallHead small;ListNode* large new ListNode(0);ListNode* largeHead large;while (head ! nullptr) {if (head-…

安卓模拟器怎么修改ip地址

最近很多老铁玩游戏的&#xff0c;想多开模拟器一个窗口一个IP&#xff0c;若模拟器窗口开多了&#xff0c;IP一样会受到限制&#xff0c;那么怎么更换自己电脑手机模拟器IP地址呢&#xff0c;今天就教大家一个修改模拟器IP地址的方法&#xff01;废话不多说&#xff0c;直接上…

摩苏尔大坝形变监测

摩苏尔大坝&#xff0c;是伊拉克最大的大坝。它位于底格里斯河35公里&#xff0c;北距摩苏尔市&#xff0c;这是一座粘土质地的水坝&#xff0c;高113米&#xff0c;长3.2公里&#xff0c;于1986落成。 大坝建成后不久&#xff0c;大坝就遇到了由软石膏地基造成的一些结构性问题…