便宜云服务器轻量应用服务器详细介绍
便宜云服务器轻量应用服务器 (Simple Application Server),是可快速搭建且易于管理的轻量级云服务器;提供基于单台服务器的应用部署,安全管理,运维监控等服务,一站式提升您的服务器使用体验和效率:
详细参考:https://www.aliyun.com/product/swas
从零开始的Spring Boot自动配置学习和starter制作教程
现在的Java后端开发中,Spring Boot早已被广泛使用,使用它,我们轻轻松松地就可以搭建起一个后端服务,发挥出你无限的创造力。
为什么Spring Boot可以这么方便呢?在Spring Boot问世之前,Spring为什么又会让人觉得繁琐呢?
这很大程度得益于Spring Boot的自动配置机制,并且在Spring Boot生态中,有着非常多的starter。
Spring Boot的Starter指的是利用Spring Boot自动配置机制,完成一些依赖的Bean的预先配置的一种依赖,在帮助我们一键引入所有所需依赖的同时,还能自动完成某些Bean的配置。
所以什么是自动配置机制?starter到底做了什么?Spring Boot到底方便在哪里?
可能刚刚开始学习后端开发,仅仅接触过Spring Boot而没有从事单纯的Spring开发的同学,脑袋里会有这些问号。
没关系,今天我们从零开始,了解一下Spring框架是如何配置各种外部依赖的,以及Spring Boot的starter到底省略了那些事情。
在这之前,大家需要先搞清楚Spring框架的一些核心概念例如依赖注入、控制反转、IoC容器是什么、Spring Bean是什么等等。
1,从编写一个外部库为例开始
无论是使用Spring框架开发,还是Spring Boot,我们都需要引入很多外部库依赖例如连接数据库的、安全框架等等。引入依赖之后,要想将依赖中需要的类作为Bean交给IoC容器托管,就需要做一些配置。
这里我们自己开发一个简单的外部库,用作简单的日志打印功能,这个外部库有以下功能:
输出info和warn类型的日志允许用户配置输出日志时是否显示时间
开发了这个外部库之后,我们来对比一下通过Spring引用并配置这个外部库,以及将其做成Starter后在Spring Boot引用,这两种情景下有什么区别。
言归正传,我们开始第一步吧。
先创建一个空的Maven项目,不需要任何依赖,编写存放日志配置的类LogConfig:
package com.gitee.swsk33.logcoredemo.config;
/**
* 日志功能的配置类
*/
public class LogConfig {
/**
* 是否显示时间
*/
private boolean showTime;
// 对应getter和setter方法
public boolean isShowTime() {
return showTime;
}
public void setShowTime(boolean showTime) {
this.showTime = showTime;
}
}
这就是一个简单的POJO类。
然后创建我们的核心逻辑功能类LogService:
package com.gitee.swsk33.logcoredemo.service;
import com.gitee.swsk33.logcoredemo.config.LogConfig;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 日志功能类
*/
public class LogService {
/**
* 日志配置字段(需要用户注入,因此这个字段要有Setter方法)
*/
private LogConfig config;
// config字段的setter方法
public void setConfig(LogConfig config) {
this.config = config;
}
/**
* 工具类:获取当前时间字符串
*
* @return 当前时间字符串
*/
private String getTimeString() {
// 自定义时间格式:年/月/日-时/分/秒
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy/MM/dd-HH:mm:ss");
// 时间对象转换成自定义的字符串形式
return LocalDateTime.now().format(format);
}
/**
* 输出告示类消息
*
* @param message 日志消息
*/
public void info(String message) {
// 根据配置判断是否输出时间
String messageString = config.isShowTime() ? "[INFO] " + getTimeString() + " " + message : "[INFO] " + message;
System.out.println(messageString);
}
/**
* 输出警告类消息
*
* @param message 日志消息
*/
public void warn(String message) {
// 根据配置判断是否输出时间
String messageString = config.isShowTime() ? "[WARN] " + getTimeString() + " " + message : "[WARN] " + message;
System.out.println(messageString);
}
}
里面的代码很简单,这里不再详细介绍了。
好的,我们的外部库就开发完成了!现在在项目目录下执行mvn clean install命令将其安装至本地Maven仓库,使得待会可以引用这个外部库。
在这里这个外部库的groupId是com.gitee.swsk33,artifactId是log-core-demo,version是1.0.0,这里大家自己在pom.xml设定好即可。
2,在Spring项目中引用并配置这个外部库
好的,假设现在有一个一个使用Spring框架的开发者(下文将这个开发者称作外部库使用者),需要使用我们的日志外部库,并将其中需要使用的服务类LogService交给Spring的IoC容器托管,这样除了引用这个外部库之外,还需要定义一些Bean的配置。
再创建一个空的Maven项目,引入Spring依赖以及我们的日志外部库依赖等等:
<!-- Spring 上下文 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.9</version>
</dependency>
<!-- 调用我们的日志核心外部库 -->
<dependency>
<groupId>com.gitee.swsk33</groupId>
<artifactId>log-core-demo</artifactId>
<version>1.0.0</version>
</dependency>
仅仅是引入依赖,依赖中的服务类并不会被Spring框架实例化为Bean并放入IoC容器,因为外部库中的类不仅没有标注@Component等等注解,也没有说包含XML文件。
所以使用Spring框架的开发者在这时还需要手动地配置一下Bean,才能在后续开发时通过IoC容器取出对应的服务类的Bean并正常使用。
这位开发者可能使用XML的方式配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义日志库中的配置类的Bean -->
<bean id="logConfig" class="com.gitee.swsk33.logcoredemo.config.LogConfig">
<!-- 配置为显示时间 -->
<property name="showTime" value="true"/>
</bean>
<!-- 定义日志库中的服务类的Bean,并注入配置 -->
<bean id="logService" class="com.gitee.swsk33.logcoredemo.service.LogService">
<!-- 将上述的配置Bean注入进来 -->
<property name="config" ref="logConfig"/>
</bean>
</beans>
也可以是通过注解的方式,创建配置类进行配置:
package com.gitee.swsk33.springannotationbased.config;
import com.gitee.swsk33.logcoredemo.config.LogConfig;
import com.gitee.swsk33.logcoredemo.service.LogService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 用于日志外部库的配置类:将日志库中需要的类注册为Bean
*/
@Configuration
public class LogServiceConfig {
/**
* 实例化一个日志配置类,并设定配置,然后注册为Bean
*/
@Bean
public LogConfig logConfig() {
LogConfig config = new LogConfig();
// 设定显示时间
config.setShowTime(true);
return config;
}
/**
* 将LogService类实例化,并注册为Bean,并注入配置对象依赖
*/
@Bean
public LogService logService(LogConfig logConfig) {
LogService logService = new LogService();
logService.setConfig(logConfig);
return logService;
}
}
无论如何,也就是说如果要将对应的对象交给Spring框架托管,那么开发者需要为外部库中的类编写Bean配置,才能够使用。
以注解的方式为例,配置完成后,才能从IoC容器中取出LogService类的Bean并使用:
package com.gitee.swsk33.springannotationbased;
import com.gitee.swsk33.logcoredemo.service.LogService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class Main {
public static void main(String[] args) {
// 创建IoC容器,基于注解的
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
// 从容器中取出日志服务对象并调用
LogService logService = context.getBean(LogService.class);
logService.info("调用日志服务!");
logService.warn("调用日志服务!");
}
}
我们这里的日志外部库中的类比较简单,实际开发中许多外部库中的类,及其依赖关系都是很复杂的,因此开发者在利用Spring框架开发并引用它们的时候,都需要为这些外部库的对应类编写Bean配置,可见这是比较麻烦的。
毕竟你不知道你所使用的外部库中的类是否都标注了@Component等注解,也不知道外部库的开发者是否编写了XML配置,所以你需要自己为外部库中的类配置Bean的定义。
3,使用Spring Boot的自动配置机制解决上述麻烦
引入依赖后还要写配置,这实在是太麻烦了!能不能引入依赖后就直接使用呢?
当然,Spring Boot的自动配置机制就实现了这一点,当然,这是借助starter依赖完成的。
(1) 自动配置机制概述
Spring Boot自动配置机制是尝试根据开发者添加的jar依赖项,自动配置Spring应用程序。
例如前面我们引入了日志外部库,那么自动配置机制就会自动地将这个外部库中的类初始化为Bean,而无需像前面一样先手动配置Bean。
自动配置是如何完成的呢?
可以说,要想自动配置一个外部库中的类,至少需要下列两个东西:
自动配置类自动配置候选类配置文件
这两样东西,通常就放在一个称作starter的依赖中,然后starter就会被打包发布,外部库的使用者引用即可。
下面,我们先单独看看starter中的这两个东西是什么。
1. 自动配置类
在上述使用Spring框架引入外部库时,要手动地给外部库中的类LogService和LogConfig编写Bean的定义配置,那么外部库的开发者能不能预先编写好这些Bean的配置呢而不是我们使用者去编写呢?
当然可以,根据这个思路,外部库开发者可以定义一个配置类,在其中通过@Bean标注的方法,创建对应的类的Bean对象,以及约定好默认配置,然后交给IoC容器托管。外部库开发者完成了Bean的定义编写,是不是就不需要我们外部库使用者去编写Bean的配置了呢?
这里所说的配置类,就是starter中的自动配置类。自动配置类就是一个普通的标注了@Configuration的类,其中使用标注了@Bean的方法完成对Bean的定义,这就是自动配置类完成的工作,自动配置类由外部库开发者编写并放在starter中。
2. 自动配置候选类配置文件
到这里又有一个问题了,开发者确实先定义好了一个自动配置类,但是我们知道Spring框架并不是会扫描所有的类的,那是不是说明我们还要通过@ComponentScan注解配置一下外部库的包路径呢?
当然不是了!不然怎么体现出自动配置中的“自动”这个特点呢?
所以外部库的开发者除了编写完成自动配置类之外,还需要编写一个自动配置候选类配置文件放在starter中,这个配置文件中就是声明哪些类是自动配置类,这样Spring Boot的自动配置机制会去先读取这些自动配置候选类的配置文件,找到所有的自动配置类后,再去加载这些自动配置类,完成自动配置。
自动配置候选类配置文件也是包含在starter中的,并且放在固定的位置。
在Spring Boot启动时,会扫描所有的外部库的classpath下所有的META-INF/spring.factories文件(Spring Boot 2.x版本),在这个文件中读取哪些类需要被读取以进行自动配置,可见这个META-INF/spring.factories文件就是我们所说的自动配置候选类配置文件。
Spring Boot应用程序默认开启了自动配置功能,因为Spring Boot的主类上通常有@SpringBootApplication这个注解,而这个注解中包含了@EnableAutoConfiguration注解,这个注解就是用于开启自动配置功能的,至于其底层原理,就不在此赘述了!
这里说明一下,Spring Boot 2.x版本和3.x版本的自动配置候选类的配置文件是不一样的:
Spring Boot 2.x启动时,是扫描所有的外部库classpath下所有的META-INF/spring.factories文件Spring Boot 3.x启动时,是扫描所有的外部库classpath下所有的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
classpath表示类路径,可以理解为Java程序编译并打包为jar文件后jar包内的路径,在Maven项目中,项目目录下的src/main/resources目录就对应着classpath的根路径/。
外部库开发者完成了自动配置类的编写以及自动配置候选类配置文件的编写,就可以将其打包为starter并发布,我们引入starter即可!这样Spring Boot启动时,扫描到starter中的自动配置候选类配置文件并读取到需要加载的配置类,就能够完成配置类加载。外部库的使用者只需要引入starter作为依赖,然后直接就可以从IoC容器中获取外部库中需要用的类的Bean了!
(2) 为我们的日志外部库制作一个starter
讲解了这么多的自动配置机制,大家可能还是不知道starter里面到底装着啥,所以我们现在就为我们上述的日志外部库编写一个starter。
1. 创建starter工程
首先创建一个新的Spring Boot依赖,并勾选Spring Configuration Processor依赖:
然后在pom.xml中,删除spring-boot-starter-test依赖,以及build部分,这些是不需要的:
然后把项目中的主类和resources目录下的配置文件也删掉,这也是用不着的:
这样,一个空的starter工程就创建完成了!
在这里,starter工程中通常有两个关键依赖大家可以看一下:
<!-- Spring Boot Starter 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot Starter 配置生成器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2. 加入日志外部库依赖
这个starter是为我们的日志外部库制作的,当然要在这个工程中加入日志外部库作为依赖了!当然主要目的是我们可以引用到外部库中的类并实例化为Bean然后交给IoC容器。
<!-- 引入我们的外部库 -->
<dependency>
<groupId>com.gitee.swsk33</groupId>
<artifactId>log-core-demo</artifactId>
<version>1.0.0</version>
</dependency>
3. 编写配置属性读取类
在上述日志外部库中,有LogConfig类专门用于存放用户的配置信息,这个类中的配置值是可以由外部库使用者自定义的。
我们也知道在Spring Boot中可以让使用者把配置写在application.properties配置文件中,然后我们读取,现在我们就创建一个这样的配置读取类:
package com.gitee.swsk33.logspringboot2starter.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 用于读取配置文件(application.properties或application.yml)中的配置的属性配置类
*/
@Data
@ConfigurationProperties(prefix = "com.gitee.swsk33.log-core-demo")
public class LogConfigProperties {
/**
* 是否显示时间(默认为false)
*/
private boolean showTime = false;
}
利用@ConfigurationProperties注解,即可将Spring Boot配置文件中的对应配置读取并赋予到这个类的对应属性中,以实现我们自定义配置值,这里就不再过多赘述这个注解的作用了!
4. 编写自动配置类
这里就是starter的核心了!创建上述所说的自动配置类,这个类就是用于在其中约定好对应的Bean对象,并交给IoC容器托管:
package com.gitee.swsk33.logspringboot2starter.autoconfigure;
import com.gitee.swsk33.logcoredemo.config.LogConfig;
import com.gitee.swsk33.logcoredemo.service.LogService;
import com.gitee.swsk33.logspringboot2starter.properties.LogConfigProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 用于自动配置日志库中的服务类的自动配置类
*/
// 该类标记为配置类
@Configuration
// 通过@EnableConfigurationProperties注解指定我们的属性配置类,才能在这个类中使用自动装配获取到属性配置类的Bean并读取配置
@EnableConfigurationProperties(LogConfigProperties.class)
public class LogServiceAutoConfiguration {
/**
* 获取属性配置类以读取配置文件中的配置值
*/
@Autowired
private LogConfigProperties logConfigProperties;
/**
* 在这里创建服务类LogService的实例,设定配置并注册为Bean
*/
@Bean
public LogService logService() {
// 以读取的配置值创建配置对象
LogConfig config = new LogConfig();
config.setShowTime(logConfigProperties.isShowTime());
// 实例化日志服务类并设定配置
LogService service = new LogService();
service.setConfig(config);
// 输出一个提示语
System.out.println("------- LogService自动配置完成!-------");
return service;
}
}
这个类并不难,我们来看一下其中的一些要点:
@EnableConfigurationProperties注解:表示加载一个配置属性读取类(标注了@ConfigurationProperties注解用于读取配置文件值的类),并将其实例化为Bean注册到IoC容器,这样就可以在该配置类中使用自动装配得到配置属性读取类,获取配置值在其中我们写了一个带有@Bean的方法,@Bean注解的作用相信大家都知道了,方法中我们完成了最开始在Spring开发中使用者的Bean的定义工作,即创建好外部库的LogService类型的Bean并放入IoC容器中去
5. 编写自动配置候选类配置文件
上述了解了自动配置过程,我们知道要想Spring Boot能够加载到上述的自动配置类LogServiceAutoConfiguration,还需要编写自动配置候选类配置文件并放在指定位置。
这个配置文件的编写和位置在Spring Boot 2.x版本和3.x版本是有区别的,下面分别来讲解。
假设你要制作Spring Boot 2.x的starter,那就在resources目录下创建META-INF/spring.factories文件:
在里面声明上述的自动配置类的全限定类名:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gitee.swsk33.logspringboot2starter.autoconfigure.LogServiceAutoConfiguration
如果你有多个自动配置类,则以逗号,隔开,例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.mycorp.libx.autoconfigure.LibXAutoConfiguration,com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
当然,这样写成一行不太美观,可以借助\换行,如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
如果你是制作Spring Boot 3.x的starter,那就在resources目录下创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件:
在其中直接声明自动配置类的全限定类名即可:
com.gitee.swsk33.logspringboot3starter.autoconfigure.LogServiceAutoConfiguration
多个自动配置类则每行一个,例如:
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
好的,到此我们的starter就制作完成了!同样地,执行mvn clean install命令将其安装至本地Maven仓库。
(3) 使用Spring Boot调用我们的starter
在Spring Boot项目中直接引入我们的starter的工件坐标作为依赖即可,然后就可以在我们需要使用的地方,直接通过@Autowired注解注入LogService类的对象即可使用!
package com.gitee.swsk33.springboot3use;
import com.gitee.swsk33.logcoredemo.service.LogService;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBoot3UseApplication {
/**
* 在需要使用的地方自动装配一下即可!
*/
@Autowired
private LogService logService;
@PostConstruct
private void init() {
// 测试调用
logService.info("调用日志服务!");
}
public static void main(String[] args) {
SpringApplication.run(SpringBoot3UseApplication.class, args);
}
}
这里直接在主类调用,然后运行试试:
可见控制台输出了对应消息,说明我们制作的starter自动配置成功!
除此之外,使用者还可以在配置文件application.properties中进行对应配置:
# 配置显示时间
com.gitee.swsk33.log-core-demo.show-time=true
相信到这里,大家就知道starter是什么了!可见starter帮我们完成了下列工作:
导入所有所需的依赖:上述starter中引入了所有需要的依赖,包括日志外部库,这样开发者只需要引入starter作为依赖即可,不需要手动配置所有依赖完成了Bean的定义:Starter中已经完成了对外部库中使用的类的Bean的定义,而不需要使用者像最开始使用Spring框架开发时自己编写外部库中的Bean定义抽离出用户可自定义的配置部分:例如上述日志的配置部分,即配置是否显示时间的部分,是可以由使用者自定义的,在starter中我们用配置属性读取类LogConfigProperties抽离出了自定义的部分,使得使用者在Spring Boot的配置文件中定义自定义的配置值即可
除此之外,我们也学习到了Spring Boot中导入starter作为依赖时,自动配置的大致过程如下:
应用程序启动,Spring Boot扫描所有依赖的classpath路径下的自动配置候选类配置文件,在里面读取到哪些类是用于自动配置的类,其中:
Spring Boot 2.x扫描的是classpath下所有的META-INF/spring.factories文件Spring Boot 3.x扫描的是classpath下所有的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
读取到所有自动配置类后,就会将这些类实例化为Bean并放入IoC容器(自动配置类要标注@Configuration注解),这个过程中,这些自动配置类中的所有@Bean方法也会被执行,这样开发者预先约定的Bean就被初始化好了并注册到IoC容器,后续开发者只需通过自动装配获取对应的类的Bean即可
上述自动配置类被加载并初始化为Bean的过程,我们就称之为完成自动配置,也可以叫做配置生效。
可见,Spring Boot的自动配置机制并不是很难,制作starter也不是一件难事,对于Spring Boot 2.x和3.x版本的区别,也就是自动配置候选类配置文件的位置和表示方式不同而已。
(4) 讨论一下@EnableConfigurationProperties
在这里我们来讨论一下为什么要在上述的自动配置类LogServiceAutoConfiguration上标注@EnableConfigurationProperties(LogConfigProperties.class)注解,我们在LogConfigProperties上面标注@Component注解,然后直接在需要的地方进行@Autowired不行吗?
这样的话,在我们自己的项目中是可以的,但是制作成starter,让别人去引用那就不行了。
这还是可以回到上述自动配置机制概述部分中,为什么需要自动配置候选类配置文件的讨论。因为starter作为依赖引入其它项目的时候,starter下的类所在的包并不在项目的扫描路径下(Spring Boot扫描Bean通常是从主类开始向下扫描其所在的包及其子包)。
而我们上述的自动配置候选类中只声明了LogServiceAutoConfiguration,也因此starter被其它项目引入时,只会将LogServiceAutoConfiguration类初始化为Bean,如果不使用@EnableConfigurationProperties,那么读取配置文件的类LogConfigProperties.class就不会被初始化为Bean,我们就无法通过自动装配获取它,毕竟它不在扫描范围内。
因此在LogServiceAutoConfiguration类上面标注@EnableConfigurationProperties(LogConfigProperties.class),这样Spring Boot将LogServiceAutoConfiguration初始化为Bean时,读取到注解@EnableConfigurationProperties(LogConfigProperties.class),就也会将LogConfigProperties初始化为Bean,完成配置的读取了!
讨论这个部分,其实是着重强调一下starter的开发和我们普通Spring Boot项目有所不同:
日常的Spring Boot项目开发,所有在主类相同层级及其子层级下标注了Bean相关注解(@Service、@Component等等)都位于扫描范围内,都会被初始化为Bean而starter的开发中,只有声明在自动配置候选类文件中的类,才会被初始化为Bean
4,条件注解
上述我们成功地制作了一个starter,不过我们也发现这个starter中的配置,是一定会被加载的,我们能不能设定条件,比如说用户不需要的时候就不加载它以节省内存呢?当然可以!Spring Boot还提供了许多条件加载Bean的注解。
(1) 类加载条件
Spring Boot提供了@ConditionalOnClass和@ConditionalOnMissingClass这两个注解,我们直接看例子:
// 省略package和import
/**
* 用于自动配置日志库中的服务类的自动配置类
*/
@Configuration
@EnableConfigurationProperties(LogConfigProperties.class)
@ConditionalOnClass(LogService.class)
public class LogServiceAutoConfiguration {
// 省略自动配置类的内容
}
我们在上述的自动配置类上面标注了@ConditionalOnClass(LogService.class)注解,表示只有当加载到LogService这个类的时候,这个自动配置类LogServiceAutoConfiguration才会被加载并初始化为Bean。
否则这个类不会被加载,其中的@Bean方法也会不生效,这个配置也就不生效了!
大家可以把上述starter中的日志外部库依赖删掉,然后加上@ConditionalOnClass(LogService.class)注解,最后在Spring Boot工程中引用这个starter,观察一下这个类会不会被加载。
那很简单,@ConditionalOnMissingClass就是和它相反,例如你标注@ConditionalOnMissingClass(LogService.class)就说明如果没有加载到LogService这个类,这个字段配置类才会被加载并初始化为Bean。
这两个注解只能用在类上面,而不能用在标注了@Bean的方法上!
(2) Bean条件
Spring Boot还可以根据是否存在或者不存在某个Bean作为条件,来初始化你的Bean,还是看下列例子:
// 省略package和import
/**
* 用于自动配置日志库中的服务类的自动配置类
*/
@Configuration
@EnableConfigurationProperties(LogConfigProperties.class)
public class LogServiceAutoConfiguration {
@Autowired
private LogConfigProperties logConfigProperties;
/**
* 在这里创建服务类LogService的实例,设定配置并注册为Bean
*/
@Bean
@ConditionalOnBean
public LogService logService() {
// 以读取的配置值创建配置对象
LogConfig config = new LogConfig();
config.setShowTime(logConfigProperties.isShowTime());
// 实例化日志服务类并设定配置
LogService service = new LogService();
service.setConfig(config);
// 输出一个提示语
System.out.println("------- LogService自动配置完成!-------");
return service;
}
}
可见上述logService方法上,标注了@ConditionalOnBean,表示在自动配置时,IoC容器中存在LogService类型的Bean的时候,就会执行这个方法以生成Bean。
同样地,如果改成:
@Bean
@ConditionalOnMissingBean
public LogService logService() {
// 省略方法内容
}
表示在自动配置时,IoC容器中不存在LogService类型的Bean的时候,才会执行这个方法以生成Bean。
可见,这两个注解直接标注(不传参)在@Bean的方法上时,是判断这个方法的返回类型的Bean是否存在/不存在。
当然,还可以这样:
@Bean
@ConditionalOnMissingBean(LogConfig.class)
public LogService logService() {
// 省略方法内容
}
上述指定了注解的value字段值,表示当IoC容器中不存在LogConfig类型的Bean的时候才会执行这个方法生成Bean。
还可以这样:
@Bean
@ConditionalOnMissingBean(name = "logService")
public LogService logService() {
// 省略方法内容
}
上述指定了注解的name字段值,表示当IoC容器中不存在名(id) 为logService的Bean的时候才会执行这个方法生成Bean。
那么@ConditionalOnBean注解同理。
事实上,@ConditionalOnMissingBean这个注解是很常用的,使用这个注解,可以允许用户是自定义这个Bean还是使用外部库开发者提供的默认的Bean。
我们来看看Redis的starter中,RedisTemplate类型的Bean:
这是Spring Boot的Redis的starter中,用于自动配置RedisTemplate类型Bean的方法,这里加上了@ConditionalOnMissingBean注解,指定当未找到名为redisTemplate的Bean的时候,就会执行这个方法将RedisTemplate类型Bean注册到IoC容器中。
这样,如果用户需要自行配置RedisTemplate,例如配置Redis的序列化方式时,用户会自己创建一个RedisTemplate类型Bean,配置好序列化方式后就注册到IoC容器,这时有了用户自己创建的RedisTemplate类型Bean,上述官方starter中的这个方法就不会被执行,就可以让用户使用自己自定义的Bean。
可见这种思路,可以使得用户去选择是使用自己自定义的Bean,还是使用官方给出的默认的Bean。
(3) 配置文件条件
官方还提供了@ConditionalOnProperty注解,表示当读取到配置文件application.properties中有特定的配置值的时候,才会实例化某个Bean,例如:
@Bean
@ConditionalOnProperty(prefix = "com.gitee.swsk33", name = "enable-log", havingValue = "true")
public LogService logService() {
// 省略方法内容
}
这表示只有配置文件中,存在配置项com.gitee.swsk33.enable-log并且其值为true时,这个方法才会被执行以生成Bean。
这个注解中,通过prefix和name属性,指定具体的配置项名称,而havingValue表示指定这个配置的值是什么才生效。
加上上述注解,用户就可以通过配置文件来启用或者禁用日志功能:
# 启用日志功能
com.gitee.swsk33.enable-log=true
反之只需把配置值改成false,上述@Bean方法就不会被执行,这个配置不生效。
可见条件注解可以根据设定的条件控制哪些自动配置类被初始化为Bean,那么在这个过程中有的自动配置类没有被初始化为Bean,就称之为配置不生效。
5,自动配置类的加载顺序
在Spring Boot中,还提供了@AutoConfigureAfter和@AutoConfigureBefore这两个注解用于控制starter中的自动配置类的加载顺序。
假设现在我有两个自动配置类First和Second(都被声明在自动配置候选类配置文件中的),并且Second中的某个方法需要调用First类中的某个成员变量,所以这就要求First类必须在Second类之前完成加载。
我们可以在Second上标注注解@AutoConfigureAfter如下:
// 省略package和import
@Configuration
@AutoConfigureAfter(First.class)
public class Second {
// 省略内容
}
这就表示Second类将在First类完成自动配置之后才会进行自动配置。
反之,还可以使用@AutoConfigureBefore如下:
// 省略package和import
@Configuration
@AutoConfigureBefore(Second.class)
public class First {
// 省略内容
}
这就表示First类将在Second类之前进行自动配置。
上述两个注解还可以传入多个类组成的数组作为参数,需要注意的是上述两个注解只能够用于控制自动配置类之间的初始化顺序,也就是只对声明在自动配置候选类配置文件中的类生效。如果将其标注在其它类上面,或者是传入不是自动配置类的类作为参数,那么这两个注解将不会生效。
除此之外,如果你的starter依赖了其它的 starter,那么上述注解还能够传入其它的starter中的类,例如:
@Configuration
@AutoConfigureAfter({DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MyAutoConfiguration {
// 自动配置相关的代码
}
这意味着MyAutoConfiguration类将在DataSourceAutoConfiguration和HibernateJpaAutoConfiguration类之后进行自动配置,传入的这两个类属于其它的starter中的自动配置类,虽然不在自己的starter中但是也是生效的。
6,总结
可见Spring Boot的自动配置,大大地方便了我们的开发,这也是为什么我们平时引入依赖例如MongoDB的starter后,就可以直接自动装配MongoTemplate并使用了,非常方便。这些,都是自动配置机制,以及各个starter帮我们简化了开发。
在最后来总结一下制作一个Starter的注意事项:
starter中通常并不包含一个库的核心功能或者业务代码,只包含自动配置类和自动配置候选类配置文件,当然有时候也可能会包含配置属性读取类。这说明starter应当是和外部库核心是分开的,例如上述日志外部库的核心功能的代码并没有包含在starter中,而是作为一个单独的项目,starter只是引用它而已,当然这也要视实际情况而定,并非一定要遵守starter的artifactId通常也是有所讲究的,我们通常将自己制作的starter命名为xxx-spring-boot-starter,例如log-spring-boot-starter,这个规范需要遵守,我们也可以发现Spring官方的starter的命名格式为spring-boot-starter-xxx,例如spring-boot-starter-web,和我们自己的starter命名“相反”,可见上述规范也是为了将第三方starter和官方的区分开来制作starter时最好选择较低的Spring Boot版本,可以自行修改pom.xml中的parent部分的版本号,当然要区分2.x和3.x的starter,毕竟两个版本的starter是不可以混用的,制作适用于2.x版本的starter可以选择2.5.0版本,制作适用于3.x版本的starter可以选择3.0.0版本,所有的Spring Boot版本可以在Maven中央仓库查看,官方仍提供支持的版本可以参考官网
本文以制作一个简单的外部库为例,比较了Spring框架直接引用外部库并配置,以及制作为starter后使用Spring Boot引用这两种情景的区别,认识Spring Boot的自动配置机制帮我们简化了哪些步骤,以及starter是由什么组成的,怎么制作。
这些对于初学者来说可能有些难以理解,希望大家能够仔细阅读完成本文的每一个部分,一步步地认识到自动配置机制解决了什么问题,以及其大致过程。
本文的参考文献:
Spring Boot自动配置机制概述:传送门Spring Boot 2.x Starter制作指引:传送门Spring Boot 3.x Starter制作指引:传送门
本文的代码仓库地址:传送门
基于安卓Android微信小程序的产后康复APP
项目介绍网络的广泛应用给生活带来了十分的便利。所以把产后康复A与现在网络相结合,利用java技术建设产后康复AAPP,实现产后康复A的信息化。则对于进一步提高产后康复A发展,丰富产后康复A经验能起到不少的促进作用。产后康复AAPP能够通过互联网得到广泛的、全面的宣传,让尽可能多的产后用户了解和熟知产后康复AAPP的便捷高效,不仅为群众提供了服务,而且也推广了自己,让更多的群众了解自己。对于产后康复A而言,若拥有自己的APP,通过系统得到更好的管理,同时提升了形象。本app设计的现状和趋势,从需求、结构、数据库等方面的设计到app的实现,分别为前后端实现。论文的内容从系统的设计、描述、实现、分析、测试方面来表明开发的过程。本app根据现实情况来选择一种可行的开发方案,借助java编程语言和MySQL数据库等实现app的全部功能,接下来对系统进行测试,测试系统是否有漏洞和测试产后用户权限来完善app,最终app完成达到相关标准。
开发说明:前端使用微信微信小程序开发工具;后端使用:springboot:VUE开发开发语言:Java开发工具:IDEA /Eclipse/微信小程序开发工具数据库:MYSQL5.7或以上应用服务:Tomcat8或以上
功能介绍
效果图
1绪论 51.1项目研究的背景 51.2开发意义 51.3项目研究内容 52开发技术介绍 62.1 B/S架构 62.2Java技术 62.3MySQL 介绍 72.4MySQL环境配置 72.5SpringBoot技术 83系统分析 93.1可行性分析 93.1.1技术可行性 93.1.2经济可行性 93.1.3操作可行性 93.2网站性能需求分析 103.3 网站现状分析 103.4网站功能分析 113.5系统流程的分析 113.5.1 用户管理的流程 123.5.2个人中心管理流程 123.5.3登录流程 134系统设计 144.1 软件功能模块设计 144.2数据库设计 144.2.1概念模型设计 144.2.2物理模型设计 155系统详细设计 225.1系统功能模块 225.2管理员功能模块 235.3用户功能模块 286系统测试 307总结与心得体会 317.1 总结 317.2 心得体会 31参考文献 32
基于安卓Android的学生作业管理系统APP
项目介绍网络的广泛应用给生活带来了十分的便利。所以把学生作业管理与现在网络相结合,利用java技术建设学生作业管理APP,实现学生作业管理的信息化。则对于进一步提高学生作业管理发展,丰富学生作业管理经验能起到不少的促进作用。学生作业管理APP能够通过互联网得到广泛的、全面的宣传,让尽可能多的用户了解和熟知学生作业管理APP的便捷高效,不仅为群众提供了服务,而且也推广了自己,让更多的群众了解自己。对于学生作业管理而言,若拥有自己的APP,通过系统得到更好的管理,同时提升了形象。本app设计的现状和趋势,从需求、结构、数据库等方面的设计到app的实现,分别为前后端实现。论文的内容从系统的设计、描述、实现、分析、测试方面来表明开发的过程。本app根据现实情况来选择一种可行的开发方案,借助java编程语言和MySQL数据库等实现app的全部功能,接下来对系统进行测试,测试系统是否有漏洞和测试用户权限来完善app,最终app完成达到相关标准。
开发环境开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9浏览器:谷歌浏览器
安卓框架:uniapp安卓开发软件:HBuilder X开发模式:混合开发
功能介绍
效果图
1绪论 51.1项目研究的背景 51.2开发意义 51.3项目研究内容 52开发技术介绍 62.1 B/S架构 62.2Java技术 62.3MySQL 介绍 72.4MySQL环境配置 72.5SpringBoot技术 83系统分析 93.1可行性分析 93.1.1技术可行性 93.1.2经济可行性 93.1.3操作可行性 93.2网站性能需求分析 103.3 网站现状分析 103.4网站功能分析 113.5系统流程的分析 113.5.1 用户管理的流程 123.5.2个人中心管理流程 123.5.3登录流程 134系统设计 144.1 软件功能模块设计 144.2数据库设计 144.2.1概念模型设计 144.2.2物理模型设计 155系统详细设计 225.1系统功能模块 225.2管理员功能模块 235.3用户功能模块 286系统测试 307总结与心得体会 317.1 总结 317.2 心得体会 31参考文献 32
Rust 笔记Rust 语言中的字符串
Rust 笔记Rust 语言中的字符串作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130876665【介绍】:本文介绍 Rust 语言中的字符和字符串的用法。上一节:《?Rust 语言中使用 vector(向量)?》?|?下一节:《 Rust 语言中应用正则表达式?》目 录1. 概述2. Rust 的 字符 类型2.1 Rust 中的 字符(char)类型2.1.1 char 类型的概念2.1.2 在 char 类型 里 使用 转义字符2.1.3 char 类型 与其它类型的转换2.1.4 在字符(char)实例上提供的一些方法char 类型上用于判断的相关方法char 类型上用于转换的相关方法2.2 从字符到字符串2.2.1 什么是字符串2.2.2 字符 和 字符串的异同3. 不可变字符串类型 str3.1 `str` 类型概述3.2 不可变字符串str的特点3.2.1 不可变字符串str的优点3.2.2 不可变字符串str的不足3.2.3 如何选用 字符串str3.3 如何判断一个值的类型为str4. 可变字符串类型 String4.1 快速入门可变字符串 String4.1.1 什么是可变字符串4.1.2 String 类型 与 str 类型的区别4.1.3 如何创建String通过使用 String::new 方法创建通过使用 String::from 方法创建通过 str 类型的 to_string 方法创建通过 format! 宏创建4.2 String 用法解析4.2.1 字符串拼接4.2.2 字符串的切片4.2.3 clone 方法拷贝字符串4.2.4 trim 方法去除两端空白字符4.2.5 chars 方法及其应用String 的 chars 方法通过 chars 方法获取 字符串首字符通过 chars 方法获取 字符串尾字符通过 chars 方法获取 字符串的字符数通过 chars 方法 反转字符串4.2.6 to_uppercase 方法将字符串统一为大写字母形式4.2.7 to_lowercase 方法将字符串统一为大写字母形式4.2.8 len 方法获取字符串的长度4.2.9 contains 方法判断字符串是否包含指定的子串4.2.10 push 方法在字符串末尾添加单个字符4.2.11 push_str 方法在字符串末尾添加字符串字面量4.2.12 pop 方法移除字符串末尾的字符4.2.13 remove 方法移除字符串指定为序处的字符4.2.14 insert 方法在字符串指定位置插入内容4.2.15 replace_range 方法替换字符串指定范围内的内容4.2.16 truncate 方法截断字符串4.2.17 clear 方法清空字符串的内容4.2.18 字符串中使用转义符、原始字符串字符串中使用转义符原始字符串表示4.3 format! 宏 与 模板字符串的使用1. 概述本文介绍 Rust 语言中的 字符 和 字符串。在 Rust 语言中,不仅 字符 和 字符串 是不同的类型,并且还存在着 str 和 String 两种字符串。Rust 中的字符类型是 char,它表示 Unicode 标量值,占用 4 个字节,而字符串类型是 str 和 String。str 类型是一种不可变的字符串类型,通常使用 &str 来表示,而 String 类型是一种可变的字符串类型,通常使用 String 来表示。2. Rust 的 字符 类型2.1 Rust 中的 字符(char)类型2.1.1 char 类型的概念Rust 的 char 类型是该语言最原始的字母类型。该类型 在 Rust 表示 Unicode 标量值,它占用 4 个字节,可以使用 单引号 来表示一个 char 类型的值,例如:let a = 'a'; // 常规的 ASCII 字符
let b = '😎'; // Unicode 表情符号
let c = '\u{1F600}'; // 同样是 Unicode 表情符号,使用 Unicode 标量值表示2.1.2 在 char 类型 里 使用 转义字符现代高级语言中,如 C、JavaScript、Python,都使用 转义符 来表示一些特殊的字符, Rust 也不例外,其中的转义字符使用 反斜杠 (\)来表示,例如:let a = '\n'; // 换行符
let b = '\t'; // 制表符
let c = '\''; // 单引号由于反斜杠被用作 转义符,这意味着只有一个反斜杠表示的不是反斜杠自身,因此如果你要使用反斜杠,则加一个反斜杠对其进行转义,也就是:let d = '\\'; // 反斜杠在这一小节的例子中,我们使用的都是 单引号,可知这里的转义符针对的是 字符 类型。实际上 Rust 中,也可以将其用于 字符串 类型(在后面介绍)。2.1.3 char 类型 与其它类型的转换Rust 语言中提供了 as 关键字,与 TypeScript 中的 as 关键字仅仅是用于 类型断言 不同,在 Rust 语言中,as 的直接将 一个类型 转换为 另一个类型,而不仅仅是视作另一个类型。因此我们可以通过 as 将 char 类型转换为 u8 类型,或者将 u8 类型转换为 char 类型:let a = 'a';
let b = a as u8; // char 类型转换为 u8 类型
let c = b as char; // u8 类型转换为 char 类型或者let d = '😀';
let e = d as u32; // char 类型转换为 u32 类型
let f = e as char; // u32 类型转换为 char 类型2.1.4 在字符(char)实例上提供的一些方法我们可以调用 char 原生支持的一些方法,实现某些功能。char 类型上用于判断的相关方法方法描述is_ascii判断是否是 ASCII 字符is_alphabetic判断是否是字母is_digit判断是否是十进制数字is_lowercase判断是否是小写字母is_uppercase判断是否是大写字母例如:let a = 'A';
let m = a.is_ascii(); // 判断是否是 ASCII 字符
let n = a.is_alphabetic(); // 判断是否是字母
let o = a.is_digit(10); // 判断是否是十进制数字
let p = a.is_lowercase(); // 判断是否是小写字母
let q = a.is_uppercase(); // 判断是否是大写字母char 类型上用于转换的相关方法方法描述to_ascii_lowercase转换为小写字母to_ascii_uppercase转换为大写字母例如:let c = 'C';
let l = c.to_ascii_lowercase(); // 转换为对应的小写字母 => clet s = 's'
let u = s.to_ascii_uppercase(); // 转换为对应的大写字母 => S2.2 从字符到字符串2.2.1 什么是字符串顾名思义,将一串字符穿在一起就形成了 字符串。字符串是非常重要的数据类型,因为它们可以用于表示文本数据,例如文件内容、用户输入、网络数据等等。现实生活中的文本、句子都需要使用字符串而不是单个字符进行表示。字符串还可以用于格式化输出,例如将数字转换为字符串,或将字符串转换为数字。在Rust语言中,str类型的 字符串是一种不可变的数据类型,用于表示文本数据。字符串是 由字符组成的序列。str类型是一种不可变的字符串类型,通常使用&str来表示。String类型是一种可变的字符串类型,通常使用String来表示。2.2.2 字符 和 字符串的异同在 Rust 语言中,字符和字符串相比:字符类型(char)是单个字符,而 字符串 (str、String)是多个字符 组成的序列;字符类型(char)是不可变的,String 类型的字符串类型可以是可变的;字符类型通常使用 单引号表示,字符串类型通常使用 双引号 或者S tring::from方法 创建;字符 和 字符串 类型 都可以 使用一些特殊的 转义字符,如换行符、制表符等,字符串类型也支持这些转义字符。字符 类型 支持的一些方法,如 is_ascii、is_alphabetic、is_digit,同样在字符串中也支持。3. 不可变字符串类型 str3.1 str 类型概述在 Rust 语言中,str 类型 是一种不可变的字符串类型,通常使用 &str 来表示。创建 str 类型 的方法有很多,比如最简单的就是使用 双引号语法,该方法可以非常方便地创建 str 字面量:let s1 = "hello world!"; // 直接使用字符串字面量创建
let s2: &str = "hello"; // 使用类型注解创建str 也可以是通过可变字符串 String转换来的:let s3 = String::from("world").as_str(); // 使用 String 类型的 as_str 方法创建或者使用可变字符串 String 类型的引用创建不可变字符串 str 类型地字面量:let s4 = &String::from("world"); // 使用 String 类型的引用创建3.2 不可变字符串str的特点3.2.1 不可变字符串str的优点不可变字符串具有以下有点安全性高:不可变字符串不会被意外修改,避免了一些潜在的错误。性能高:不可变字符串的内存布局更加紧凑,访问速度更快。3.2.2 不可变字符串str的不足不可变字符串也有一些不足,比如:不可变字符串无法修改,需要重新创建一个新的字符串来实现修改操作,会占用更多的内存空间。不可变字符串无法直接拼接,需要使用 format! 宏 或者 String::from 方法来实现。3.2.3 如何选用 字符串str一般以下,可以参考以下建议选择字符串:字符串不需要修改时,选用不可变字符串 str ,这样更加 安全 和 高效;反之,字符串需要修改时,选用可变字符串 String。3.3 如何判断一个值的类型为str判断一个值是否是 str 类型可以使用类型判断语法:fn is_str(s: &dyn std::any::Any) -> bool {
s.is::<&str>()
}例如:let s1 = "hello";
let s2 = String::from("world");
let s3 = 123;
let s4 = vec![1, 2, 3];
println!("{}", is_str(&s1)); // true
println!("{}", is_str(&s2)); // false
println!("{}", is_str(&s3)); // false
println!("{}", is_str(&s4)); // false4. 可变字符串类型 String4.1 快速入门可变字符串 String4.1.1 什么是可变字符串可变字符串的 可变 是相对于不可变字符串而言的,我们上一节使用字面量语法创建的字符串已经创建就不可以修改了,因而称之为不可变字符串。换而言之,可变字符串的可变指的是字符串的内容可以被修改。例如,使用 push_str 方法可以在字符串末尾添加字符串字面量:let mut s1 = String::from("hello");
s1.push_str(", world!"); // 在字符串末尾添加字符串字面量
println!("{}", s1); // 输出:hello, world!可以看到,通过这样的方法,使得字符串的内容发生了变化。类似的方法还有很多,请参考 4.2 String 方法解析 部分。4.1.2 String 类型 与 str 类型的区别str 类型是不可变的,而 String 类型是可变的。str 类型通常用于函数参数和返回值,而 String 类型通常用于变量和常量。str 类型通常使用 字符串字面量 创建,而 String 类型通常使用 String::from 方法 创建。关于创建 String 的方法,请参考下一小节4.1.3 如何创建String通过使用 String::new 方法创建使用 String::new 方法可以创建一个空的 String 类型的实例,例如:let s1 = String::new(); // 创建一个空的 String 类型通过使用 String::from 方法创建String::from 方法可以直接使用字符串字面量创建 String,例如:let s2 = String::from("hello");实际上,你也可以理解为将一个 str 转换为对应的 String。通过 str 类型的 to_string 方法创建不可变字符串 str 上提供了一个 to_string 方法,返回对应的 String 字符串。例如:let s3 = "world".to_string();通过 format! 宏创建你还可以使用 format! 宏 来创建 String 字符串。例如:let s4 = format!("{} {}", "hello", "world");关于 format! 宏 ,请参考 4.3 format! 宏 与 模板字符串的使用4.2 String 用法解析4.2.1 字符串拼接使用 + 操作符可以用于拼接两个字符串例如:let s1 = String::from("Hello ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 使用 + 运算符连接两个字符串
println!("{}", s3); // 输出:Hello world!4.2.2 字符串的切片所谓 切片,指的是通过指定一对起止字符在字符串中的 位序,从而获得一个子字符串的方法。和诸多编程语言一样(如Python),可以使用 [] 语法对字符串进行切片。如:let s1 = String::from("hello");
let s2 = &s1[0..2]; // 使用切片获取字符串的一部分
println!("{}", s2); // 输出:he4.2.3 clone 方法拷贝字符串String 的 clone 方法从原 String 对象实例上拷贝得到一个副本,例如:let s1 = String::from("hello");
let s2 = s1.clone(); // 使用 clone 方法复制一个字符串
println!("{}", s2); // 输出:hello4.2.4 trim 方法去除两端空白字符在字符串处理的时候经常需要处理掉字符串两端的空白字符,使用 String 的trim 方法即可轻松实现,例如:例如:let s1 = String::from("hello");
let s2 = s1.trim();
println!("{}", s9); // 输出:hello4.2.5 chars 方法及其应用String 的 chars 方法String 的chars 方法用于获取字符串的字符迭代器。例如:let s1 = String::from("hello");
let s2 = s1.chars(); // 获取字符串的字符迭代器
for c in s23 {
println!("{}", c); // 逐个输出字符串的字符
}通过 chars 方法获取 字符串首字符例如:let s1 = String::from("hello");
let s2 = s1.chars().nth(0).unwrap(); // 获取字符串的第一个字符
println!("{}", s2); // 输出:h通过 chars 方法获取 字符串尾字符例如:let s1 = String::from("hello");
let s2 = s1.chars().last().unwrap(); // 获取字符串的最后一个字符
println!("{}", s2); // 输出:o通过 chars 方法获取 字符串的字符数例如:let s1 = String::from("hello");
let s2 = s1.chars().count(); // 获取字符串的字符数
println!("{}", s2); // 输出:5通过 chars 方法 反转字符串例如:let s1 = String::from("hello");
let s2 = s1.chars().rev().collect::<String>(); // 将字符串反转
println!("{}", s2); // 输出:olleh4.2.6 to_uppercase 方法将字符串统一为大写字母形式例如:let s1 = String::from("hEllo");
let s2 = s1.to_uppercase(); // 将字符串转换为大写字母形式
println!("{}", s2); // 输出:HELLO4.2.7 to_lowercase 方法将字符串统一为大写字母形式例如:let s1 = String::from("heLLo");
let s2 = s1.to_lowercase(); // 将字符串转换为小写字母形式
println!("{}", s2); // 输出:hello4.2.8 len 方法获取字符串的长度例如:let length = String::from("world").len(); // 54.2.9 contains 方法判断字符串是否包含指定的子串例如:let isContains = String::from("Hello").contains("llo"); // true4.2.10 push 方法在字符串末尾添加单个字符例如:let mut s = String::from("Hello");
s.push('!');
println!("{}", s); // 输出:Hello!4.2.11 push_str 方法在字符串末尾添加字符串字面量例如:let mut s = String::from("hello");
s.push_str(", world!");
println!("{}", s); // 输出:hello, world!4.2.12 pop 方法移除字符串末尾的字符例如:let mut s = String::from("hello!");
s.pop(); // 移除字符串末尾的字符
println!("{}", s); // 输出:hello4.2.13 remove 方法移除字符串指定为序处的字符例如:let mut s = String::from("hello");
s.remove(2);
println!("{}", s); // 输出:helo4.2.14 insert 方法在字符串指定位置插入内容内容可以是 char 和 str。例如在字符串指定位置插入单个字符:let mut s = String::from("helo");
s.insert(2, 'l'); //
println!("{}", s); // 输出:hello又如在字符串指定位置插入字符串字面量:let mut s = String::from("heo");
s.insert(1, "ll");
println!("{}", s); // 输出:hello4.2.15 replace_range 方法替换字符串指定范围内的内容例如:let mut s = String::from("hello");
s.replace_range(1..3, "a");
println!("{}", s); // 输出:hallo4.2.16 truncate 方法截断字符串truncate 方法可以用于截断字符串,只保留指定长度的内容。例如:let mut s = String::from("hello");
s.truncate(3);
println!("{}", s); // 输出:hel4.2.17 clear 方法清空字符串的内容字符串是字符的容器,清空字符串就得到一个没有任何字符的字符串,也称空字符串。例如:let mut s = String::from("hello");
s.clear();
println!("{}", s); // 输出:4.2.18 字符串中使用转义符、原始字符串字符串中使用转义符在介绍 Rust 字符的时候我们介绍过转义符,Rust 中的字符串类型也支持转义字符,例如:let s1 = "hello\nworld"; // 换行符
let s2 = "hello\tworld"; // 制表符
let s3 = "hello\'world"; // 单引号
let s4 = "hello\\world"; // 反斜杠也可以使用转义符表示 Unicode 标量值,例如:let s1 = "\u{1F600}"; // Unicode 表情符号
let s2 = "\u{1F600}\u{1F601}\u{1F602}"; // 多个 Unicode 表情符号原始字符串表示在 Rust 中,字符串前的 r# 表示使用原始字符串表示法,不会转义字符。例如:let s1 = r#"hello\nworld"#;
println!("{}", s1); // 输出:hello\nworld原始字符串表示法还支持使用多个 # 号,例如:let s2 = r##"hello "world""##;
println!("{}", s2); // 输出:hello "world"使用原始字符串表示法可以避免一些转义字符带来的麻烦,比如在正则表达式中使用反斜杠。例如:let s3 = r"\d+";
println!("{}", s3); // 输出:\d+4.3 format! 宏 与 模板字符串的使用Rust 中的 format! 宏 用于格式化字符串,比如:let s1 = format!("{} {}", "hello", "world"); // 使用位置占位符
let s2 = format!("{name} {age}", name = "Alice", age = 18); // 使用命名占位符
let s3 = format!("{:?}", vec![1, 2, 3]); // 将 Vec 转换为字符串format! 宏 还支持一些高级特性,如:let s4 = format!("{:x}", 255); // 将数字转换为十六进制字符串
let s5 = format!("{:b}", 255); // 将数字转换为二进制字符串
let s6 = format!("{:o}", 255); // 将数字转换为八进制字符串
let s7 = format!("{:.2}", 3.1415926); // 将浮点数保留两位小数
let s8 = format!("{:+}", 123); // 将数字添加正负号
let s9 = format!("{:*>10}", "hello"); // 将字符串右对齐并填充 *
let s10 = format!("{:^10}", "hello"); // 将字符串居中对齐
带你读《云存储应用白皮书》之34:7、可观测运维解决方案
7、可观测运维解决方案?1)需求背景?企业上云后,可通过云产品的弹性能力、快速创新力,快速构建企业业务的创新迭代。云产品的使用,在企业业务系统架构中发挥着巨大的作用。随着云上企业使用云产品类型的不断增加,云产品可观测性对于构建全栈的可观测方案尤为重要。?2)解决方案?Cloud Lens基于日志服务存储分析底座,支持对接各种日志、监控指标、云产品Trace数据、Trail、关键配置计量数据、关键事件、多账号信息等数据源。您可以创建统一的云产品访问分析、用量分析、异常监测、性能监控、安全分析、数据保护的场景化洞察大盘,用于了解组织范围内存储、安全、网络、流量和数据库类云产品的使用情况和活动。?各个Cloud Lens应用提供数据接入管理模块和通用功能模块。其中,接入管理模块包括接入管理、存储目标库管理、自动化采集配置。通过日志服务中的Cloud Lens入口或云产品控制台中的入口,可以使用各个Cloud Lens应用。?方案优势:?????????统一云产品运维数据的采集:计量、指标、访问日志、审计日志等数据的跨账号、跨区域、自动采集。????????提供更全面的云产品可观测能力:用量分析、访问分析、性能分析、异常检测、安全分析和数据保护。????????提供灵活订阅的数据平台:支持消费组、API、Grafana等多种数据订阅方式。
带你读《云存储应用白皮书》之31:4. 数据湖存储解决方案
4. 数据湖存储解决方案?1)需求背景?数据湖已经不是一个新概念,在提出的初期也有不少人对数据湖和传统的数仓之间的关系感到困惑。?简单来说,数据湖中一般存储较多的原始数据,包括结构化数据(如关系型数据库中的表),半结构化数据(如CSV、JSON 、XML、日志等),非结构化数据(如电子邮件、文档、PDF等)以及二进制数据(如图形、音频、视频等)。?数据湖在写入时没有模式限制,存储到数据湖的数据在写入过程中,对数据格式没有限制,可以需要读取数据时,才开始使用各种工具对数据湖中的数据进行分析,相比数仓成本更低,有更高的灵活性。?随着各种数据处理平台和新技术的不断发展,用户对越来越认识到通过挖掘数据价值去支撑业务发展,用户希望能够将数据统一化集中管理,能够使用统一存储平台支撑各类计算平台。?2)解决方案?数据湖非常适合存储大量的结构化、非结构化和半结构化数据。如果场景中正在处理大量基于事件的数据,比如应用日志或点击流,那么以原始形式存储这些数据并根据基于场景构建特定的ETL并对接数据平台会让数据处理与分析更为便捷。?便宜云服务器对象存储OSS作为非结构化数据存储池和数据湖底座,为双十一期间淘宝、天猫、支付宝等应用提供了如丝般顺滑的图片、视频体验。????方案优势:?????????消除数据孤岛:用户的数据可以使用同一个命名空间下统一存储,同一个份数据,可对接多个数据分析平台,避免孤岛以及数据搬迁。????????不限制数据类型:支持结构化、半结构化、非结构化数据的存储。????????计算生态丰富:支持多种数据导入方式,支持对接开源系统、便宜云服务器多个数据分析平台,和数据消费框架。????????数据冷热分层:多种存储类型组合,用户可根据数据冷热,进行数据分层,优化存储成本。????????计算与存储解耦合:存储空间弹性伸缩,计算的扩缩容与存储解耦,让系统架构更加灵活,成本更节约。????????访问控制:提供更丰富的存储访问控制策略,让数据更安全。
带你读《云存储应用白皮书》之23:10、网盘与相册服务
10、网盘与相册服务?产品介绍:?网盘与相册服务(Drive&Photo Service)包含企业版和开发者版本,是为客户提供的面向企业、团队与个人的数据管理开放平台,提供一站式数据存储、分析和AI的能力。方便客户快速高效的构建可支撑海量用户的网盘与相册服务,同时针对团队及个人用户,支持免开发开箱即用。?网盘与相册服务企业版产品优势:?????????集中管理,高效协同:开箱即用,实时同步最新文件,用户在电脑、手机都可以获取最新文件。?????????????????360°权限管理:灵活的权限管理,支持团队、企业、集团公司各分支组织配置不同权限。?????????????????多重安全防护:依托于便宜云服务器高安全防护水位,符合国家信息安全等级保护制度要求。?????????????????可定制化:定制企业名称、logo,打造企业专属风格。?????????网盘与相册服务企业版产品功能:?多种文件管理功能:统一存放和管理企业重要的文件资料,提供各类文件增删改查功能,符合用户在本地电脑管理文件的使用习惯,实现本地与云上无差别管理。?????????统一存储:提供文件上传下载、新建文件及文件夹、移动、复制、删除、排序、收藏、备注等一系列的文件管理能力。?????????????????在线预览:提供文档、图片、音频、视频等十几种文件格式的预览。?????????????????文件检索:支持按照文件名称、文件类型、文件内容等搜索,也支持各种排序方式方便查找文件。?????????????????回收站:提供默认保留90天的文件回收站功能,90天内可以恢复文件,90天后文件自动清除。?????????丰富的文件传输能力:实现公网、内网等各种文件内容快速同步,给用户根据当前网络状态管理传输状态的功能,且能在不同群体之间快速共享和分享文件,无需下载和传输。?????????文件秒传:多人上传相同的文件时,只需其中一人成功上传,其他人也可自动快速完成上传,无需等待上传过程。?????????????????断点续传:可以支持因网络状态、电脑状态导致的文件传输到一半失败后,重新上传请求时无需重新等待前半段已传输成功的文件。?????????????????共享与分享:支持同组织内文件共享、外部用户公网文件分享等功能。?????????????????传输状态管理:提供批量上传、文件夹上传、传输暂停、取消等传输状态管理功能。?????????完善的安全及权限管理能力:基于便宜云服务器底层存储提供企业级安全防护,配备灵活、多维度的权限逻辑,同时也支持用户回溯历史操作,同步达标99.9%的可用性SLA和99.999999999999%(12个9)的持久性。?????????日志审计:记录用户的操作路径,支持线上查询和导出日志。?????????????????加密防护:基于便宜云服务器底层存储提供企业级多层次安全防护,支持加密传输、加密存储、多种鉴权模式。?????????????????用户角色管理:新增用户自定义角色权限组,根据企业内岗位角色分工不同,定义多重维度的权限叠加,具备新增、删除、查看及成员管理功能。?????????????????团队管理:区别于角色的团队维度权限组,根据团队组织结构定义权限,具备新增、删除、查看、团队空间分配限制及团队成员管理功能。
带你读《云存储应用白皮书》之21:8、闪电立方
8、闪电立方?产品介绍:?闪电立方(Datatransport)能够为用户提供安全、高效、便捷的数据传输服务。支持将对象存储、文件存储从不同设备、不同云服务商迁移和同步到便宜云服务器。它提供在线迁移和离线迁移(闪电立方)两种迁移方式,致力于解决大规模数据传输效率、安全问题等难题。?微型闪电立方作为闪电立方的小型化设备,具有超强的抗震、宽温能力,可运行在自动驾驶、媒体、医院、工厂、影院等多个边缘数据迁移场景,让数据迁移更简单,更高效。?产品优势:?????????易用灵活:迁移过程实时监控,在线显示迁移进度;迁移结果提供报告,完成情况一目了然;支持可视化配置页面,仅需3步设置部署任务。?????????????????安全可靠:传输时,采用HTTPS数据加密通道;支持MD5或CRC自动识别源端校验规范,进行读写双向校验。?????????????????传输性能高:支持多任务、高并发;支持个性化流控,可指定时间段内的最大流量,避免迁移数据与在线业务访问争抢网络带宽。?????????????????扩展灵活,低成本:单台设备可支持40TB\100TB\480TB的迁移数据能力,可多套同时使用,提升迁移效率;相比传统Internet或者专线接入的方式;成本大幅下降,迁移速度大幅提升。?????????????????部署方便:为数据迁移而生的专业设备,标准机架和电源,可多套同时部署提升迁移效率;支持多种的数据源类型:本地文件系统、NAS、HDFS、FastDFS等。?????????????????安全可靠:采用CRC技术对读写双向校验保障数据一致性;提供端到端的加密机制运输并上传数据;数据迁移完毕后,通过便宜云服务器官方数据擦除机制,确保数据不会被第三方获取。?????????产品功能:?支持热迁移,降低系统迁移过程中的应用停机时间。可实现不停服热迁移,存量数据迁移完成后,配合使用增量数据迁移功能,定时扫描增量将数据迁移到便宜云服务器目标数据源中,可以将系统迁移过程中的应用停机时间降低到秒级别。?完善的迁移监控可视化平台,无运维成本。?????????迁移过程监控:支持迁移进度查询,迁移流量数据监控。?????????????????迁移任务报告:支持迁移完成率统计,打印失败文件列表清单。?????????????????灵活的迁移管控:支持随时启停迁移任务,动态修改迁移限流,重试失败任务。?????????支持图形化管理。提供图形化的配置界面,用于用户登录设备,进行网络、数据迁移任务和迁移参数的配置。?从本地机房到便宜云服务器:?????????第1步:选择闪电立方机型,并下单; ?????????????????第2步:设备邮寄到用户机房,上传数据;?????????????????第3步:设备邮寄回便宜云服务器指定机房,传输数据上云。
带你读《云存储应用白皮书》之18:5. 表格存储
5. 表格存储?产品介绍:?表格存储(Tablestore)面向海量结构化数据提供 Serverless 表存储服务,同时针对物联网场景深度优化提供一站式的 IoTstore 解决方案。适用于海量账单、IM 消息、物联网、车联网、风控、推荐等场景中的结构化数据存储,提供海量数据低成本存储、毫秒级的在线数据查询和检索以及灵活的数据分析能力。?产品优势:?????????Serverless:弹性支持单表PB级存储,自动扩展服务能力,享受免运维、即开即用的使用体验。支持多级存储介质,配合冷热自动分层最大化优化存储成本。?????????????????场景化数据模型:针对场景化优化提供多种数据模型,包括宽行模型、时序模型和消息模型。简化数据模型定义,让开发更便捷,让能力更贴合。?????????????????多元化索引:可便捷的对数据进行实时索引,针对不同查询场景提供多元化索引。支持二级索引、全文索引和多维数值索引,加速查询与分析。?????????????????易集成生态丰富:全面接入开源与云原生大数据生态体系。与Maxcompute、Spark、Flink等计算引擎集成,与Kafka、数据集成等链路组件无缝打通。???????????产品功能:?Serverless:提供Serverless服务体验,零运维,低成本。?????????分布式架构体系,自动负载均衡:单表10PB级数据量、万亿条记录数以及千万级别的TPS能力。自动负载均衡及热点迁移,无需人工介入。?????????????????存储计算分离,多种存储介质:存储计算分离架构,计算层与存储层均可独立扩展,更灵活更低成本。支持多级存储介质,配合冷热自动分层最大化优化存储成本。?????????????????支持灵活计费模型:支持纯按量模式,享受0元门槛产品使用。同时支持预留模式,规划预留资源、成本可控。灵活选择满足不同场景需求。?????????企业级服务,稳定安全:多维度、多层次的安全防护与访问控制,保障数据安全。?????????企业级安全保障体系:提供表级别和API级别的权限管理机制,支持STS临时授权和自定义权限认证及主子账号功能。同时提供网络访问控制能力与数据加密能力。?????????????????数据高可靠:数据多副本,保证强一致性,按照11个9的数据可靠性的标准设计。同时支持通过HBR来做数据备份与恢复。?????????????????服务高可用:分布式存储架构,单点故障快速检测快速恢复,按照99.99%可用性设计。?????????场景化数据模型:能够满足不同场景不同类型数据存储。?????????宽行数据模型:无需结构定义,属性列能够灵活动态扩展。适用于非强事务、海量在线数据存储与查询。?????????????????时序数据模型:针对时间序列数据的特点进行设计,提供更高压缩比,支持数据查询与分析。适用于物联网设备监控、设备采集数据、机器监控数据等场景。?????????????????消息数据模型:针对消息数据场景所设计,能够满足消息数据场景对消息保序、海量消息存储、实时同步的特殊需求。可以同时应用在IM、Feed流等消息场景中。?????????多语言SDK与SQL灵活访问:支持SQL、ResultAPI、SDK、客户端等不同访问方式。?????????SQL:支持SQL查询。能够映射关联不同数据模型的表,能够自动关联索引进行查询与分析优化。?????????????????多语言SDK:Restful API接口,多语言SDK支持,满足不同语言开发者的使用需求。?????????????????开发工具:支持客户端与命令行工具,支持Windows、Linux和Mac平台。能够便捷的完成所需的运维管理需求。?????????数据检索与分析:提供多元化索引,满足不同场景数据查询与分析需求。?????????多元索引:提供二级索引、全文索引、多维数值索引等多种数据索引。提供不用场景的查询与分析加速。?????????????????查询加速:支持任意字段的组合查询、全文检索、地理空间查询、模糊查询等能力。加速数据查询。?????????????????分析加速:内置统计聚合能力,支持高并发扫描与存储侧算子下推,加速数据分析。?????????开放的计算生态对接:全面接入开源与云原生大数据生态体系以及数据链路中间件。?????????计算引擎对接:支持Maxcompute、Spark、Flink等计算引擎直接访问。?????????????????数据实时订阅:能够实时捕获表内数据的变化,提供全增量一体数据实时订阅能力。可以自定义数据实时处理或对接流计算引擎。?????????????????上下游数据集成:与Kafka、数据集成等链路组件无缝打通,便于多组件数据架构搭建。同时支持数据实时投递至OSS,搭建数据湖架构。