网上科普有关“springboot解析文件?”话题很是火热,小编也是针对springboot解析文件?寻找了一些与之相关的一些信息进行分析,如果能碰巧解决你现在面临的问题,希望能够帮助到您。
springbootstarter原理解析及实践
starter是springBoot的一个重要部分。通过starter,我们能够快速的引入一个功能,而无需额外的配置。同时starter一般还会给我提供预留的自定配置选项,我们只需要在application.properties中设置相关参数,就可以实现配置的个性化。
那么这些方便的操作是怎么实现的呢?通过了解其原理,我们也可以做一个自己的starter,来让别人快速使用我们的功能。
按个人理解,我认为springBootStarter就是一个智能化的配置类@Configuration。
接下来介绍内容包括:
1、创建module,首先我们自定义一个starter的module,根据你的starter实现复杂度,引入相关spring组件。最基本的,我们只需引入spring-boot-autoconfigure模块。
2、业务bean实现实现我们的业务bean,案例中我们实现最简单的sayHello服务,输入msg,返回“hello,{msg}”。
3、然后就是Configuration类的创建,这个类是starter自动初始化的核心类,负责把业务相关的bean智能的加载进来。
4、配置spring.factories,通过该配置,才能让springboot来自动加载我们的Configuration类。具体原理我们稍后深入了解。
具体的,是在模块的resources/META-INF目录下,新建spring.factories文件。内容如下:
最后我们把上述模块单独执行以下install或者deploy,一个starter就做好了。
其他项目使用我们的starter就非常简单了:(1)引入starter依赖;(2)注入需要的service。
done!
回头再看上边的开发流程,有两个地方需要我们了解一下:
(1)如何让starter被自动识别加载:spring.factories里的EnableAutoConfiguration原理。
(2)如何实现自动加载的智能化、可配置化:@Configuration配置类里注解。
这里我们只简单的说一下大致的原理和流程,执行细节大家可以按照文章给出的思路自己去研读。
在SpringBoot的启动类,我们都会加上@SpringBootApplication注解。这个注解默认会引入@EnableAutoConfiguration注解。然后@EnableAutoConfiguration会@Import(AutoConfigurationImportSelector.class)。
AutoConfigurationImportSelector.class的selectImports方法最终会通过SpringFactoriesLoader.loadFactoryNames,加载META-INF/spring.factories里的EnableAutoConfiguration配置值,也就是我们上文中设置的资源文件。
实际使用中,我们并不总是希望使用默认配置。比如有时候我想自己配置相关功能,有时候我想更改一下默认的服务参数。这些常见的场景Starter都想到了,并提供了如下的解决方案:
springbootstarter提供了一系列的@Conditional*注解,代表什么时候启用对应的配置,具体的可以去查看一下springboot的官方文档。
比如我们案例中的「@ConditionalOnClass(DemoHelloService.class)」,代表如果存在DemoHelloService类时,配置类才会生效;又比如「@ConditionalOnMissingBean(DemoHelloService.class)」,代表着如果项目中没有DemoHelloService类型的bean,那么该配置类会自动创建出starter默认的DemoHelloService类型bean。
这个注解主要是为了解决如下场景:我想要使用starter的默认配置类,但是又想对配置中的某些参数进行自定义配置。@ConfigurationProperties类就是做这个工作的。例如上述例子中,我想对默认的defaultMsg做些个性化的设置。就可以按如下方式来实现:
starter新增ConfigurationProperties类bean
启用property
在实际项目中自定义默认msg
SpringBootStarter运行原理代码解析
springboot-boot-starter:就是springboot的场景启动器。springboot将所有的功能场景都抽取出来,做成一个个的starter,只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来,根据公司业务需求决定导入什么启动器即可。
查看@SpringBootApplication
springboot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置的工作:spring.factories文件位于springboot-autoconfigure.jar包中。
所以真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中对应org.springframework.boot.autoconfigure.包下的配置项通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总称为一个实例并加载到IOC容器中。
Springboot自定义xml文件解析有时候,要通过自定义XML配置文件来实现一些特定的功能。这里通过例子来说明。
首先,看部分spring加载bean文件的源码:
spring-beans-5.0.6.RELEASE.jar!/org/springframework/beans/factory/xml/PluggableSchemaResolver.class:
spring-beans-5.0.6.RELEASE.jar!/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.class:
可以看出,spring在加载xml文件的时候,会默认读取配置文件META-INF/spring.schemas和META-INF/spring.handlers。这样,我们就可以在这两个文件添加我们自定义的xml文件格式和xml文件解析处理器。
新建一个Springboot工程,pom如下。
SelfDefineXmlTrial/pom.xml:
然后,新建一个用于测试controller。
com.lfqy.springboot.selfdefxml.controller.SelfDefXmlController:
最后,创建一个Springboot的启动类。
com.lfqy.springboot.selfdefxml.SelfDefXmlApplication:
运行启动之后,浏览器访问效果如下:
修改前面提到的配置文件META-INF/spring.schemas、META-INF/spring.handlers,添加xml格式说明。
META-INF/spring.schemas:
META-INF/spring.handlers:
添加xml格式说明配置文件。
META-INF/selfdef.xsd:
添加自定义xml格式处理器类。
com.lfqy.springboot.selfdefxml.selxmlparse.UserNamespaceHandler:
新增xml格式解析类。
com.lfqy.springboot.selfdefxml.selxmlparse.UserBeanDefinitionParser:
新增自定义xml对应的bean类。
com.lfqy.springboot.selfdefxml.beans.User:
添加自定义xml配置文件读取的相关逻辑。
com.lfqy.springboot.selfdefxml.SelfDefXmlApplication:
到这里,编码就完成了,工程的目录结构如下。
运行之后,控制台输出如下:
这里,通过实现一个启动时自动初始化的一个servlet来实现。
com.lfqy.springboot.selfdefxml.servlet.StartupServlet:
在启动时加载servlet,为了方便区分,这里新写一个启动类。
com.lfqy.springboot.selfdefxml.SelfDefXmlLoadOnStartupApplication
到这里,编码已经完成,工程的目录结构如下:
运行之后,控制台输出如下:
Springboot初始化流程解析以上是一个最简单的Springboot程序(2.0.3版本)示例,也是我们最通用的写法,但其中其实封装这一系列复杂的功能操作,让我们开始逐步进行分析。
首先这里最重要的必然是注解@SpringBootApplication
@SpringBootApplication注解由几个注解复合组成,其中最主要的就是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan这三个。
其中的@ComponentScan是spring的原生注解,@SpringBootConfiguration虽然是springboot中的注解,但其实质就是包装后的@Configuration,仍然是spring中的注解,用于代替xml的方式管理配置bean
@EnableAutoConfiguration的定义如上,这里最重要的注解是@Import(@AutoConfigurationPackage注解的实现也是基于@Import),借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器中。关于@EnableAutoConfiguration注解后续涉及到时会再详细说明。这里我们先回到启动类的run方法从头分析初始化流程。
可以看到'run'方法最终调用的是newSpringApplication(primarySources).run(args),这里首先创建了SpringApplication对象,然后调用其run方法
这里主要是为SpringApplication对象进行初始化,这里要专门提一下的是webApplicationType和getSpringFactoriesInstances。
它用来标识我们的应用是什么类型的应用,来看一下deduceWebApplicationType()方法的实现
其返回值是WebApplicationType类型的枚举类,其值有NONE、SERVLET、REACTIVE三种,分别对应非WEB应用,基于servlet的WEB应用和基于reactive的WEB应用。
这里的核心是SpringFactoriesLoader.loadFactoryNames(type,classLoader)方法,来看一下
重点关注一下loadSpringFactories(classLoader)做了什么
这里的FACTORIES_RESOURCE_LOCATION定义为META-INF/spring.factories,因此该方法会扫描所有包下的该文件,将其解析成map对象并缓存到cache中以避免重复加载,springboot包下该文件的部分片段如下
从这里可以看出,setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class))和setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));分别对应设置的是上述这些类。
解析完成后调用createSpringFactoriesInstances(type,parameterTypes,classLoader,args,names)处理解析结果,生成对应的实例,源码如下
这里的核心是通过ClassUtils.forName(name,classLoader)方法,以反射的方式生成类实例instanceClass。由此可以看出SpringFactoriesLoader.loadFactoryNames(type,classLoader)的作用就是将META-INF/spring.factories中配置的内容进行实例化的工厂方法类,具备很强的扩展性,与SPI机制有异曲同工
的效果。
看完SpringApplication的初始化,接着跳回run方法继续分析
这里挑其中比较重要的几个方法进行分析
通过getOrCreateEnvironment()方法创建容器环境
可以看到environment存在则不会重复创建,当应用类型为servlet时创建的是StandardServletEnvironment对象,否则创建StandardEnvironment对象。
接着来看configureEnvironment(environment,applicationArguments.getSourceArgs())
configurePropertySources(environment,args)加载启动命令行的配置属性,来看一下实现
这里的MutablePropertySources对象用于存储配置集合,其内部维护了一个CopyOnWriteArrayList类型的list对象,当默认配置存在时,会向该list的尾部插入一个newMapPropertySource("defaultProperties",this.defaultProperties)对象。
接着来看configureProfiles(environment,args)
这里主要做的事情就是获取environment.getActiveProfiles()的参数设置到environment中,即spring.profiles.active对应的环境变量。
最后来看一下listeners.environmentPrepared(environment)
这里的listeners就是之前通过META-INF/spring.factories注册的所有listeners,后面我们先以其中最重要的ConfigFileApplicationListener做为例子进行分析,接着来看listener.environmentPrepared(environment)
可以看到这里创建了一个ApplicationEnvironmentPreparedEvent类型的事件,并且调用了multicastEvent方法,通过该方法最终会调用到listener的onApplicationEvent方法,触发事件监听器的执行。
接下来具体看一下ConfigFileApplicationListener的onApplicationEvent方法做了什么
可以看到当监听到ApplicationEnvironmentPreparedEvent类型的事件时,调用onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event)方法
可以看到这里通过loadPostProcessors()方法加载了META-INF/spring.factories中的所有EnvironmentPostProcessor类到list中,同时把ConfigFileApplicationListener自己也添加进去了。接着遍历list中所有对象,并执行postProcessEnvironment方法,于是接着来看该方法
这里的核心是newLoader(environment,resourceLoader).load(),这里的Loader是一个内部类,用于处理配置文件的加载,首先看一下其构造方法
可以看到这里的resourceLoader又是通过SpringFactoriesLoader进行加载,那么来看看META-INF/spring.factories中定义了哪些resourceLoader
从名字就可以看出来,PropertiesPropertySourceLoader和YamlPropertySourceLoader分别用于处理.properties和.yml类型的配置文件。
接着来看看load()方法做了什么
initializeProfiles()进行了profiles的初始化,默认会添加null和default到profiles中,null对应配置文件application.properties和application.yml,default对应配置文件application-default.yml和application-default.properties,这里的null会被优先处理,由于后处理的会覆盖先处理的,因此其优先级最低。
接着来看load(profile,this::getPositiveProfileFilter,addToLoaded(MutablePropertySources::addLast,false))方法
这里重点是通过getSearchLocations()获取配置文件的路径,默认会获得4个路径
接着会遍历这些路径,拼接配置文件名称,选择合适的yml或者properties解析器进行解析,最后将结果添加到environment的propertySources中。
可以看到这里也是根据webApplicationType的取值,分别创建不同的返回类型。
这里的sources装的就是我们的启动类,然后通过load(context,sources.toArray(newObject[0]))方法进行加载
来看一下loader是如何被加载的
经过一系列调用之后最终由load(Class?source)方法执行,这里比较有趣的是当Groovy存在时居然是优先调用Groovy的方式进行加载,否则才走this.annotatedReader.register(source)方法将启动类注册到beanDefinitionMap中。
这个refresh()方法相当重要,尤其是invokeBeanFactoryPostProcessors(beanFactory),这是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键部分,后续再详细讲解。
至此Springboot的启动流程已经大体分析完了,也了解了配置文件和启动类分别是是如何被加载的,但仍有两个问题待解,一是Springboot的核心思想约定大于配置是如何做到的,二是Springboot的各种spring-boot-starter-*是如何发挥作用的,这两个问题留待后续文章继续分析。
Springboot读取配置文件原理Springboot读取配置文件(application.yaml,application.properties)的过程发生在SpringApplication#prepareEnvironment()阶段,而prepareEnvironment又属于整个Springboot应用启动的非常前置阶段,因为Environment的准备是后续bean创建的基础。让我们来一探启动是的详细code。除去StopWatch这些code,可以发现prepareEnvironment发生在SpringApplication#run这在整个应用启动的多步实质性操作中几乎是第一步。
而prepareEnvironment中最重要的是通过触发listener(EventPublishingRunListener)来通过SimpleApplicationEventMulticaster#multicastEvent发出ApplicationEnvironmentPreparedEvent。
而SimpleApplicationEventMulticaster#multicastEvent的实现其实也很简单,找到相关的监听ApplicationEnvironmentPreparedEvent的listener,然后一个个的调用他们的Listener#onApplicationEvent(event)方法,而这其中就包括了处理configuration文件的listener。
在Springboot2.4.0之前这个处理configuration文件的lister是ConfigFileApplicationListener,在2.4.0之后,处理configuration文件的lister是EnvironmentPostProcessorApplicationListener,并且对configuration文件的加载做了较大的改变,导致一些行为可能出现了变化,这也就是下面要详细讲的内容。
Springboot2.4.0之后,configuration文件的load顺序按照优先级是如下顺序(序号大的会被小的覆盖):
和之前版本比较,整体的属性加载顺序并无调整,只有Applicationproperties(14,15)这里有顺序的调整,具体调整为:
如果存在多个active的profiles,例如[Test,Dev],那么对于同时存在两个profile配置文件中的配置,后面的profile里的配置(Dev)会覆盖前面profile(Test)里配置的值。
前面讲了这么多,终于要引出Springboot2.4之后配置文件加载的行为变化了。
考虑这样的情况,如果我想在跑Springboottest的时候指定特定的profile
TypeScript入门指南
新系列深入浅出TypeScript来了,本系列至少20+篇。本文为第一篇,来介绍一下TypeScript以及常见的类型。
TypeScript是一门由微软推出的开源的、跨平台的编程语言。它是JavaScript的超集,扩展了JavaScript的语法,最终会被编译为JavaScript代码。
TypeScript的主要特性:
TypeScript主要是为了实现以下两个目标:
下面就来看看这两个目标是如何实现的。
为什么要给JavaScript加上类型呢?
我们知道,JavaScript是一种轻量级的解释性脚本语言。也是弱类型、动态类型语言,允许隐式转换,只有运行时才能确定变量的类型。正是因为在运行时才能确定变量的类型,JavaScript代码很多错误在运行时才能发现。TypeScript在JavaScript的基础上,包装了类型机制,使其变身成为静态类型语言。在TypeScript中,不仅可以轻易复用JavaScript的代码、最新特性,还能使用可选的静态类型进行检查报错,使得编写的代码更健壮、更易于维护。
下面是JavaScript项目中最常见的十大错误,如果使用TypeScript,那么在编写阶段就可以发现并解决很多JavaScript错误了:
类型系统能够提高代码的质量和可维护性,经过不断的实践,以下两点尤其需要注意:
可以认为,在所有操作符之前,TypeScript都能检测到接收的类型(在代码运行时,操作符接收的是实际数据;在静态检测时,操作符接收的则是类型)是否被当前操作符所支持。当TypeScript类型检测能力覆盖到所有代码后,任意破坏约定的改动都能被自动检测出来,并提出类型错误。因此,可以放心地修改、重构业务逻辑,而不用担忧因为考虑不周而犯下低级错误。
在一些语言中,类型总是有一些不必要的复杂的存在方式,而TypeScript尽可能地降低了使用门槛,它是通过如下方式来实现的。
TypeScript与JavaScript本质并无区别,我们可以将TypeScipt理解为是一个添加了类型注解的JavaScript,为JavaScript代码提供了编译时的类型安全。
实际上,TypeScript是一门“中间语言”,因为它最终会转化为JavaScript,再交给浏览器解释、执行。不过TypeScript并不会破坏JavaScript原有的体系,只是在JavaScript的基础上进行了扩展。
准确的说,TypeScript只是将JavaScript中的方法进行了标准化处理:
这段代码在TypeScript中就会报错,因为TS会知道a是一个数字类型,不能将其他类型的值赋值给a,这种类型的推断是很有必要的。
上面说了,TypeScript会尽可能安全的推断类型。我们也可以使用类型注释,以实现以下两件事:
在一些语言中,类型总是有一些不必要的复杂的存在方式,而TypeScript的类型是结构化的。比如下面的例子中,函数会接受它所期望的参数:
为了便于把JavaScript代码迁移至TypeScript,即使存在编译错误,在默认的情况下,TypeScript也会尽可能的被编译为JavaScript代码。因此,我们可以将JavaScript代码逐步迁移至TypeScript。
虽然TypeScript是JavaScript的超集,但它始终紧跟ECMAScript标准,所以是支持ES6/7/8/9等新语法标准的。并且,在语法层面上对一些语法进行了扩展。TypeScript团队也正在积极的添加新功能的支持,这些功能会随着时间的推移而越来越多,越来越全面。
虽然TypeScript比较严谨,但是它并没有让JavaScript失去其灵活性。TypeScript由于兼容JavaScript所以灵活度可以媲美JavaScript,比如可以在任何地方将类型定义为any(当然,并不推荐这样使用),毕竟TypeScript对类型的检查严格程度是可以通过tsconfig.json来配置的。
在搭建TypeScript环境之前,先来看看适合TypeScript的IDE,这里主要介绍VisualStudioCode,笔者就一直使用这款编辑器。
VSCode可以说是微软的亲儿子了,其具有以下优势:
因为VSCode中内置了特定版本的TypeScript语言服务,所以它天然支持TypeScript语法解析和类型检测,且这个内置的服务与手动安装的TypeScript完全隔离。因此,VSCode支持在内置和手动安装版本之间动态切换语言服务,从而实现对不同版本的TypeScript的支持。
如果当前应用目录中安装了与内置服务不同版本的TypeScript,我们就可以点击VSCode底部工具栏的版本号信息,从而实现“useVSCode'sVersion”和“useWorkspace'sVersion”两者之间的随意切换。
除此之外,VSCode也基于TypeScript语言服务提供了准确的代码自动补全功能,并显示详细的类型定义信息,大大的提升了我们的开发效率。
1)全局安装TypeScript:
2)初始化配置文件:
执行之后,项目根目录会出现一个tsconfig.json文件,里面包含ts的配置项(可能因为版本不同而配置略有不同)。
可以在package.json中加入script命令:
3)编译ts代码:
TSLint是一个通过tslint.json进行配置的插件,在编写TypeScript代码时,可以对代码风格进行检查和提示。如果对代码风格有要求,就需要用到TSLint了。其使用步骤如下:(1)在全局安装TSLint:
(2)使用TSLint初始化配置文件:
执行之后,项目根目录下多了一个tslint.json文件,这就是TSLint的配置文件了,它会根据这个文件对代码进行检查,生成的tslint.json文件有下面几个字段:
这些字段的含义如下;
在说TypeScript数据类型之前,先来看看在TypeScript中定义数据类型的基本语法。
在语法层面,缺省类型注解的TypeScript与JavaScript完全一致。因此,可以把TypeScript代码的编写看作是为JavaScript代码添加类型注解。
在TypeScript语法中,类型的标注主要通过类型后置语法来实现:“变量:类型”
在JavaScript中,原始类型指的是非对象且没有方法的数据类型,包括:number、boolean、string、null、undefined、symbol、bigInt。
它们对应的TypeScript类型如下:
JavaScript原始基础类型TypeScript类型numbernumberbooleanbooleanstringstringnullnullundefinedundefinedsymbolsymbolbigIntbigInt
需要注意number和Number的区别:TypeScript中指定类型的时候要用number,这是TypeScript的类型关键字。而Number是JavaScript的原生构造函数,用它来创建数值类型的值,这两个是不一样的。包括string、boolean等都是TypeScript的类型关键字,而不是JavaScript语法。
TypeScript和JavaScript一样,所有数字都是浮点数,所以只有一个number类型。
TypeScript还支持ES6中新增的二进制和八进制字面量,所以TypeScript中共支持2、8、10和16这四种进制的数值:
字符串类型可以使用单引号和双引号来包裹内容,但是如果使用Tslint规则,会对引号进行检测,使用单引号还是双引号可以在Tslint规则中进行配置。除此之外,还可以使用ES6中的模板字符串来拼接变量和字符串会更为方便。
类型为布尔值类型的变量的值只能是true或者false。除此之外,赋值给布尔值的值也可以是一个计算之后结果为布尔值的表达式:
在JavaScript中,undefined和null是两个基本数据类型。在TypeScript中,这两者都有各自的类型,即undefined和null,也就是说它们既是实际的值,也是类型。这两种类型的实际用处不是很大。
注意,第一行代码可能会报一个tslint的错误:Unnecessaryinitializationto'undefined',就是不能给一个变量赋值为undefined。但实际上给变量赋值为undefined是完全可以的,所以如果想让代码合理化,可以配置tslint,将"no-unnecessary-initializer"设置为false即可。
默认情况下,undefined和null是所有类型的子类型,可以赋值给任意类型的值,也就是说可以把undefined赋值给void类型,也可以赋值给number类型。当在tsconfig.json的"compilerOptions"里设置为"strictNullChecks":true时,就必须严格对待了。这时undefined和null将只能赋值给它们自身或者void类型。这样也可以规避一些错误。
BigInt是ES6中新引入的数据类型,它是一种内置对象,它提供了一种方法来表示大于2-1的整数,BigInt可以表示任意大的整数。
使用BigInt可以安全地存储和操作大整数,即使这个数已经超出了JavaScript构造函数Number能够表示的安全整数范围。
我们知道,在JavaScript中采用双精度浮点数,这导致精度有限,比如Number.MAX_SAFE_INTEGER给出了可以安全递增的最大可能整数,即2-1,来看一个例子:
可以看到,最终返回了true,这就是超过精读范围造成的问题,而BigInt正是解决这类问题而生的:
这里需要用BigInt(number)把Number转化为BigInt,同时如果类型是BigInt,那么数字后面需要加n。
在TypeScript中,number类型虽然和BigInt都表示数字,但是实际上两者类型是完全不同的:
symbol我们平时用的比较少,所以可能了解也不是很多,这里就详细来说说symbol。
symbol是ES6新增的一种基本数据类型,它用来表示独一无二的值,可以通过Symbol构造函数生成。
注意:Symbol前面不能加new关键字,直接调用即可创建一个独一无二的symbol类型的值。
可以在使用Symbol方法创建symbol类型值的时候传入一个参数,这个参数需要是一个字符串。如果传入的参数不是字符串,会先自动调用传入参数的toString方法转为字符串:
上面代码的第三行可能会报一个错误:Thisconditionwillalwaysreturn'false'sincethetypes'uniquesymbol'and'uniquesymbol'havenooverlap.这是因为编译器检测到这里的s1===s2始终是false,所以编译器提醒这代码写的多余,建议进行优化。
上面使用Symbol创建了两个symbol对象,方法中都传入了相同的字符串,但是两个symbol值仍然是false,这就说明了Symbol方法会返回一个独一无二的值。Symbol方法传入的这个字符串,就是方便我们区分symbol值的。可以调用symbol值的toString方法将它转为字符串:
在TypeScript中使用symbol就是指定一个值的类型为symbol类型:
在ES6中,对象的属性是支持表达式的,可以使用于一个变量来作为属性名,这对于代码的简化有很多用处,表达式必须放在大括号内:
symbol也可以作为属性名,因为symbol的值是独一无二的,所以当它作为属性名时,不会与其他任何属性名重复。当需要访问这个属性时,只能使用这个symbol值来访问(必须使用方括号形式来访问):
在使用obj.name访问时,实际上是字符串name,这和访问普通字符串类型的属性名是一样的,要想访问属性名为symbol类型的属性时,必须使用方括号。方括号中的name才是我们定义的symbol类型的变量name。
使用Symbol类型值作为属性名,这个属性是不会被for…in遍历到的,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()等方法获取到:
虽然这些方法都不能访问到Symbol类型的属性名,但是Symbol类型的属性并不是私有属性,可以使用Object.getOwnPropertySymbols方法获取对象的所有symbol类型的属性名:
除了这个方法,还可以使用ES6提供的Reflect对象的静态方法Reflect.ownKeys,它可以返回所有类型的属性名,Symbol类型的也会返回:
Symbol包含两个静态方法,for和keyFor。1)Symbol.for()
用Symbol创建的symbol类型的值都是独一无二的。使用Symbol.for方法传入字符串,会先检查有没有使用该字符串调用Symbol.for方法创建的symbol值。如果有,返回该值;如果没有,则使用该字符串新创建一个。使用该方法创建symbol值后会在全局范围进行注册。
上面代码中,创建了一个iframe节点并把它放在body中,通过这个iframe对象的contentWindow拿到这个iframe的window对象,在iframe.contentWindow上添加一个值就相当于在当前页面定义一个全局变量一样。可以看到,在iframe中定义的键为TypeScript的symbol值在和在当前页面定义的键为'TypeScript'的symbol值相等,说明它们是同一个值。
2)Symbol.keyFor()该方法传入一个symbol值,返回该值在全局注册的键名:
看完简单的数据类型,下面就来看看比较复杂的数据类型,包括JavaScript中的数组和对象,以及TypeScript中新增的元组、枚举、Any、void、never、unknown。
在TypeScript中有两种定义数组的方式:
以上两种定义数组类型的方式虽然本质上没有任何区别,但是更推荐使用第一种形式来定义。一方面可以避免与JSX语法冲突,另一方面可以减少代码量。
注意,这两种写法中的number指定的是数组元素的类型,也可以在这里将数组的元素指定为其他任意类型。如果要指定一个数组里的元素既可以是数值也可以是字符串,那么可以使用这种方式:number|string[]。
在JavaScript中,object是引用类型,它存储的是值的引用。在TypeScript中,当想让一个变量或者函数的参数的类型是一个对象的形式时,可以使用这个类型:
可以看到,当给一个对象类型的变量赋值一个对象时,就会报错。对象类型更适合以下场景:
在JavaScript中并没有元组的概念,作为一门动态类型语言,它的优势是支持多类型元素数组。但是出于较好的扩展性、可读性和稳定性考虑,我们通常会把不同类型的值通过键值对的形式塞到一个对象中,再返回这个对象,而不是使用没有任何限制的数组。TypeScript的元组类型正好弥补了这个不足,使得定义包含固定个数元素、每个元素类型未必相同的数组成为可能。
元组可以看做是数组的扩展,它表示已知元素数量和类型的数组,它特别适合用来实现多值返回。确切的说,就是已知数组中每一个位置上的元素的类型,可以通过元组的索引为元素赋值::
可以看到,定义的arr元组中,元素个数和元素类型都是确定的,当为arr赋值时,各个位置上的元素类型都要对应,元素个数也要一致。
当访问元组元素时,TypeScript也会对元素做类型检查,如果元素是一个字符串,那么它只能使用字符串方法,如果使用别的类型的方法,就会报错。
在TypeScript新的版本中,TypeScript会对元组做越界判断。超出规定个数的元素称作越界元素,元素赋值必须类型和个数都对应,不能超出定义的元素个数。
这里定义了接口Tuple,它继承数组类型,并且数组元素的类型是number和string构成的联合类型,这样接口Tuple就拥有了数组类型所有的特性。并且指定索引为0的值为string类型,索引为1的值为number类型,同时指定length属性的类型字面量为2,这样在指定一个类型为这个接口Tuple时,这个值必须是数组,而且如果元素个数超过2个时,它的length就不是2是大于2的数了,就不满足这个接口定义了,所以就会报错;当然,如果元素个数不够2个也会报错,因为索引为0或1的值缺失。
TypeScript在ES原有类型基础上加入枚举类型,使得在TypeScript中也可以给一组数值赋予名字,这样对开发者比较友好。枚举类型使用enum来定义:
上面定义的枚举类型的Roles,它有三个值,TypeScript会为它们每个值分配编号,默认从0开始,在使用时,就可以使用名字而不需要记数字和名称的对应关系了:
除此之外,还可以修改这个数值,让SUPER_ADMIN=1,这样后面的值就分别是2和3。当然还可以给每个值赋予不同的、不按顺序排列的值:
我们可以将一个值定义为any类型,也可以在定义数组类型时使用any来指定数组中的元素类型为任意类型:
any类型会在对象的调用链中进行传导,即any类型对象的任意属性的类型都是any,如下代码所示:
需要注意:不要滥用any类型,如果代码中充满了any,那TypeScript和JavaScript就毫无区别了,所以除非有充足的理由,否则应该尽量避免使用any,并且开启禁用隐式any的设置。
void和any相反,any是表示任意类型,而void是表示没有类型,就是什么类型都不是。这在定义函数,并且函数没有返回值时会用到:
需要注意:void类型的变量只能赋值为undefined和null,其他类型不能赋值给void类型的变量。
never类型指永远不存在值的类型,它是那些总会抛出异常或根本不会有返回值的函数表达式的返回值类型,当变量被永不为真的类型保护所约束时,该变量也是never类型。
下面的函数,总是会抛出异常,所以它的返回值类型是never,用来表明它的返回值是不存在的:
never类型是任何类型的子类型,所以它可以赋值给任何类型;而没有类型是never的子类型,所以除了它自身以外,其他类型(包括any类型)都不能为never类型赋值。
上面代码定义了一个立即执行函数,函数体是一个死循环,这个函数调用后的返回值类型为never,所以赋值之后neverVariable的类型是never类型,当给neverVariable赋值123时,就会报错,因为除它自身外任何类型都不能赋值给never类型。
基于never的特性,我们可以把never作为接口类型下的属性类型,用来禁止操作接口下特定的属性:
可以看到,无论给props.name赋什么类型的值,它都会提示类型错误,这就相当于将name属性设置为了只读。
unknown是TypeScript在3.0版本新增的类型,主要用来描述类型并不确定的变量。它看起来和any很像,但是还是有区别的,unknown相对于any更安全。
对于any,来看一个例子:
上面这些语句都不会报错,因为value是any类型,所以后面三个操作都有合法的情况,当value是一个对象时,访问name属性是没问题的;当value是数值类型的时候,调用它的toFixed方法没问题;当value是字符串或数组时获取它的length属性是没问题的。
当指定值为unknown类型的时候,如果没有缩小类型范围的话,是不能对它进行任何操作的。总之,unknown类型的值不能随便操作。那什么是类型范围缩小呢?下面来看一个例子:
这里由于把value的类型缩小为Date实例的范围内,所以进行了value.toISOString(),也就是使用ISO标准将Date对象转换为字符串。
使用以下方式也可以缩小类型范围:
关于unknown类型,在使用时需要注意以下几点:
在实际使用中,如果有类型无法确定的情况,要尽量避免使用any,因为any会丢失类型信息,一旦一个类型被指定为any,那么在它上面进行任何操作都是合法的,所以会有意想不到的情况发生。因此如果遇到无法确定类型的情况,要先考虑使用unknown。
ts和js有什么区别ts需要静态编译,它提供了强类型与更多面向对象的内容。
ts最终仍要编译为弱类型的js文件,基于对象的原生的js,再运行。故ts相较java/C#这样天生面向对象语言是有区别和局限的
ts是由微软牵头主导的,主要来自C#
TypeScript是一个应用程序级的JavaScript开发语言。(这也表示TypeScript比较牛逼,可以开发大型应用,或者说更适合开发大型应用)
TypeScript是JavaScript的超集,可以编译成纯JavaScript。这个和我们CSS离的Less或者Sass是很像的,
我们用更好的代码编写方式来进行编写,最后还是友好生成原生的JavaScript语言。
TypeScript跨浏览器、跨操作系统、跨主机、且开源。由于最后他编译成了JavaScript所以只要能运行JS的地方,都可以运行我们写的程序,设置在node.js里。
TypeScript始于JavaScript,终于JavaScript。遵循JavaScript的语法和语义
TypeScript可以重用JavaScript代码,调用流行的JavaScript库。
TypeScript提供了类、模块和接口,更易于构建组件和维护。
TypeScript和JavaScript的区别1.从历史包袱角度说JavaScript的包袱是前向兼容,即使老版本的ES中有落后的方面,为了兼容,也要支持,而TypeScript宣称完全兼容JavaScript,这导致了TypeScript继承了JavaScript一切的缺点,所以从这点上看可以说是不相伯仲。
2.TypeScript的作者也是C#的作者,这导致了TypeScript从C#继承了很多优雅的设计比如枚举,泛型等语言特性,这让TypeScript增色不少。
3.TypeScript带有编译期类型检查,在写大程序的时候有优势,更容易重构和让别人理解代码的意图,但是这带来了一个问题就
关于“springboot解析文件?”这个话题的介绍,今天小编就给大家分享完了,如果对你有所帮助请保持对本站的关注!
本文来自作者[曼青]投稿,不代表长隆号立场,如若转载,请注明出处:https://clcgzw.com/cshi/202502-1018.html
评论列表(4条)
我是长隆号的签约作者“曼青”!
希望本篇文章《springboot解析文件?_1》能对你有所帮助!
本站[长隆号]内容主要涵盖:国足,欧洲杯,世界杯,篮球,欧冠,亚冠,英超,足球,综合体育
本文概览:网上科普有关“springboot解析文件?”话题很是火热,小编也是针对springboot解析文件?寻找了一些与之相关的一些信息进行分析,如果能碰巧解决你现在面临的问题,希望...