原 荐 SpringBoot 2.0 系列0

SpringBoot 2.0 系列004 --启动实战之配置文件

配置文件

配置文件加载流程

很多文档包括官方文档说SB的默认配置文件是application开头的文件,那么是为什么呢?

  • 我们先看下流程图

由上述流程我们发现,在执行SpringApplication的run方法中的prepareEnvironment子方法时,触发ConfigFileApplicationListener类中的 load方法,完成配置文件的加载

  • ConfigFileApplicationListener分析
public void load() {
  this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
  this.processedProfiles = new LinkedList<>();
  this.activatedProfiles = false;
  this.loaded = new LinkedHashMap<>();
  // 核心初始化方法  负责初始化 profile (spring.profiles.active) 
  // 如果没有则使用默认的AbstractEnvironment类中的(spring.profiles.default)default(可以是application-default名称的,也可以起不加)
  initializeProfiles();
  // 不为空时 循环加载  初始化了  理论上不会为空
  while (!this.profiles.isEmpty()) {
    Profile profile = this.profiles.poll();
    // 第二步 此方法加载 文件的前缀 active 以及后缀  和路径 
    load(profile, this::getPositiveProfileFilter,
         addToLoaded(MutablePropertySources::addLast, false));
    this.processedProfiles.add(profile);
  }
  // 默认加载的
  load(null, this::getNegativeProfileFilter,
       addToLoaded(MutablePropertySources::addFirst, true));
  addLoadedPropertySources();
  • 接 上边标注第二步的位置
private void load(Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
            // getSearch 如果你配置了spring.config.location 路径  则使用此处路径
            // 否则是用默认的 ConfigFileApplicationListener.DEFAULT_SEARCH_LOCATIONS
            // 即 private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
			getSearchLocations().forEach((location) -> {
				boolean isFolder = location.endsWith("/");
				// getSearchNames 是你配置文件的名称 ,如果配置了spring.config.name 则使用配置的这个名字
				// 否则使用默认的 private static final String DEFAULT_NAMES = "application";
				// 这就是默认是application这个名字的原因
				Set<String> names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES);
				names.forEach(
				       
                        // 第三步  location是路径  name是文件名 profile则是-defalut部分或者其他-dev之类的
						(name) -> load(location, name, profile, filterFactory, consumer));
			});
		}
  • 接上边标注第三步
private void load(String location, String name, Profile profile,
				DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
    
            // 这里需要注意的是propertySourceLoaders 此处是在new loader的时候初始化的,即我们流程中的addPropertySources一步
               				        //底层使用的是META-INF/spring.factories
               				        /** org.springframework.boot.env.PropertySourceLoader=\
                                          org.springframework.boot.env.PropertiesPropertySourceLoader,\
                                          org.springframework.boot.env.YamlPropertySourceLoader
                                       */
           // 这也就是 文件后缀支持 yml,yaml,properties,xml的原因
           // 没有名字的情况
			if (!StringUtils.hasText(name)) {
				for (PropertySourceLoader loader : this.propertySourceLoaders) {
					if (canLoadFileExtension(loader, location)) {
						load(loader, location, profile,
								filterFactory.getDocumentFilter(profile), consumer);
					}
				}
			}
			// 带后缀名的情况
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
				for (String fileExtension : loader.getFileExtensions()) {
					String prefix = location + name;
					fileExtension = "." + fileExtension;
					// 第四步 通过后缀名方式加载
					loadForFileExtension(loader, prefix, fileExtension, profile,
							filterFactory, consumer);
				}
			}
		}
  • 接第四步
private void loadForFileExtension(PropertySourceLoader loader, String prefix,
				String fileExtension, Profile profile,
				DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
			DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
			// 前边说的 -default和-dev部分
			if (profile != null) {
				// Try profile-specific file & profile section in profile file (gh-340)
				// prefix 是路径名+文件名
				// fileExtension是.yml这种
				// profile 则是第一步initializeProfiles 是this.profiles注入的
				String profileSpecificFile = prefix + "-" + profile + fileExtension;
				// 各处执行装载 扫描不同路径下是否有对应配置文件
				load(loader, profileSpecificFile, profile, defaultFilter, consumer);
				load(loader, profileSpecificFile, profile, profileFilter, consumer);
				// Try profile specific sections in files we've already processed
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						String previouslyLoaded = prefix + "-" + processedProfile
								+ fileExtension;
						load(loader, previouslyLoaded, profile, profileFilter, consumer);
					}
				}
			}
			// Also try the profile-specific section (if any) of the normal file
			//// 各处执行装载 扫描不同路径下是否有对应配置文件
			// 第五步
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}
  • 接 第五步

第三步和第四步都会执行下面的方法

private void load(PropertySourceLoader loader, String location, Profile profile,
				DocumentFilter filter, DocumentConsumer consumer) {
			try {
				Resource resource = this.resourceLoader.getResource(location);
				String description = getDescription(location, resource);
				if (profile != null) {
					description = description + " for profile " + profile;
				}
				if (resource == null || !resource.exists()) {
					this.logger.trace("Skipped missing config " + description);
					return;
				}
				if (!StringUtils.hasText(
						StringUtils.getFilenameExtension(resource.getFilename()))) {
					this.logger.trace("Skipped empty config extension " + description);
					return;
				}
				String name = "applicationConfig: [" + location + "]";
				// 转换成docment对象 
				List<Document> documents = loadDocuments(loader, name, resource);
				if (CollectionUtils.isEmpty(documents)) {
					this.logger.trace("Skipped unloaded config " + description);
					return;
				}
				List<Document> loaded = new ArrayList<>();
				for (Document document : documents) {
				    // 文档Filter
					if (filter.match(document)) {
						maybeActivateProfiles(document.getActiveProfiles());
						addProfiles(document.getIncludeProfiles());
						loaded.add(document);
					}
				}
				Collections.reverse(loaded);
				if (!loaded.isEmpty()) {
				    // consumer对象来自 addToLoaded方法 目的是装载到PropertySources中
					loaded.forEach((document) -> consumer.accept(profile, document));
					
					// 开启debug后 打印出这句话 表示文件被加载完毕。 
					this.logger.debug("Loaded config file " + description);
				}
			}
			catch (Exception ex) {
				throw new IllegalStateException("Failed to load property "
						+ "source from location '" + location + "'", ex);
			}
		}

配置文件使用

由上述分析可知,默认的配置文件名为application(-{profile}).yml/xml/yaml/properties,且默认支持项目的resources路径

问题1 怎么使用名称不是application的文件?

根据流程分析可知,文件默认完整名称是由spring.config.name和spring.profiles.default两条属性控制。由此可知,在启动前注入此属性即可。

  • 目标 ,修改默认application为ricky01 ,defalut默认bgt01
  • 在resources新建如下几个文件及内容

默认端口是8010 其他是8011,8012,8013,

  • 注入 spring.config.name和spring.profiles.default属性

主要有如下3种方式

/**
     *   测试自定义加载文件的方式 01  通过启动设置参数
     *  --spring.config.name=ricky01 --spring.profiles.default=bgt01
     * @param args
     */
    public static void main01(String[] args) throws InterruptedException {
        SpringApplication application = new SpringApplication(SpringBootApplication01.class);
        application.run(args);
    }

    /**
     *   测试自定义加载文件的方式 01  通过启动设置参数
     *  设置环境变量
     * @param args
     */
    public static void main02(String[] args) throws InterruptedException {
        System.setProperty("spring.config.name", "ricky01");
        System.setProperty("spring.profiles.default", "bgt02");
        SpringApplication application = new SpringApplication(SpringBootApplication01.class);
        application.run(args);
    }

    /**
     *   测试自定义加载文件的方式 01  通过启动设置参数
     *  设置传入参数
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        args=new String[2];
        args[0]="--spring.config.name=ricky01";
        args[1]="--spring.profiles.default=bgt03";
        SpringApplication application = new SpringApplication(SpringBootApplication01.class);
        application.run(args);
    }
  • 结果

公用ricky01.yml中的contextpath,服务端口却是各自定义的。以下是bgt03时的启动日志

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.1.RELEASE)

2018-05-16 13:40:25.165  INFO 24984 --- [           main] com.ricky.SpringBootApplication01        : Starting SpringBootApplication01 on jsb-bgt with PID 24984 (D:\work\ideawork\SpringBootLearn\chapter04\target\classes started by zdwljs in D:\work\ideawork\SpringBootLearn)
2018-05-16 13:40:25.169  INFO 24984 --- [           main] com.ricky.SpringBootApplication01        : No active profile set, falling back to default profiles: bgt03
2018-05-16 13:40:25.263  INFO 24984 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3e92efc3: startup date [Wed May 16 13:40:25 CST 2018]; root of context hierarchy
2018-05-16 13:40:28.721  INFO 24984 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8013 (http)
2018-05-16 13:40:28.756  INFO 24984 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-05-16 13:40:28.757  INFO 24984 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.29
2018-05-16 13:40:28.770  INFO 24984 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [D:\Program Files\Java\jdk1.8.0_161\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;d:\work\Git\cmd;C:\Program Files (x86)\MySQL\MySQL Server 5.5\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Users\zdwljs\AppData\Local\Microsoft\WindowsApps;D:\Program Files\Java\jdk1.8.0_161\bin;;.]
2018-05-16 13:40:28.924  INFO 24984 --- [ost-startStop-1] o.a.c.c.C.[.[localhost].[/ricky01]       : Initializing Spring embedded WebApplicationContext
2018-05-16 13:40:28.924  INFO 24984 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3672 ms
2018-05-16 13:40:29.149  INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-05-16 13:40:29.155  INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-05-16 13:40:29.155  INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-05-16 13:40:29.155  INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-05-16 13:40:29.155  INFO 24984 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-05-16 13:40:29.371  INFO 24984 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 13:40:29.783  INFO 24984 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3e92efc3: startup date [Wed May 16 13:40:25 CST 2018]; root of context hierarchy
2018-05-16 13:40:29.905  INFO 24984 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-05-16 13:40:29.907  INFO 24984 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-05-16 13:40:29.947  INFO 24984 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 13:40:29.947  INFO 24984 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 13:40:30.183  INFO 24984 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-05-16 13:40:30.245  INFO 24984 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8013 (http) with context path '/ricky01'
2018-05-16 13:40:30.251  INFO 24984 --- [           main] com.ricky.SpringBootApplication01        : Started SpringBootApplication01 in 5.845 seconds (JVM running for 6.639)

问题2 怎么使用外部配置文件加载 方便统一管理

比如我们放在D:\work\ricky\config目录下管理

  • 目标 使用d盘config目录下的文件,如下
  • 第一步 在d盘新建上述文件 默认端口是8020 bgt01端口则是8021
  • 第二步 更改加载路径,如下

这里 只罗列一种方式 其他的和上面修改apllication方式一样

public static void main(String[] args) throws InterruptedException {
        args=new String[3];
        args[0]="--spring.config.location=file:d:/work/ricky/config/";
        args[1]="--spring.config.name=ricky02";
        args[2]="--spring.profiles.default=bgt01";
        SpringApplication application = new SpringApplication(SpringBootApplication02.class);
        application.run(args);
    }
  • 结果
.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.1.RELEASE)

2018-05-16 14:09:21.111  INFO 19708 --- [           main] com.ricky.SpringBootApplication02        : Starting SpringBootApplication02 on jsb-bgt with PID 19708 (D:\work\ideawork\SpringBootLearn\chapter04\target\classes started by zdwljs in D:\work\ideawork\SpringBootLearn)
2018-05-16 14:09:21.118  INFO 19708 --- [           main] com.ricky.SpringBootApplication02        : No active profile set, falling back to default profiles: bgt01
2018-05-16 14:09:21.221  INFO 19708 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1ab06251: startup date [Wed May 16 14:09:21 CST 2018]; root of context hierarchy
2018-05-16 14:09:24.122  INFO 19708 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8021 (http)
2018-05-16 14:09:24.187  INFO 19708 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-05-16 14:09:24.187  INFO 19708 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.29
2018-05-16 14:09:24.208  INFO 19708 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [D:\Program Files\Java\jdk1.8.0_161\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;d:\work\Git\cmd;C:\Program Files (x86)\MySQL\MySQL Server 5.5\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Users\zdwljs\AppData\Local\Microsoft\WindowsApps;D:\Program Files\Java\jdk1.8.0_161\bin;;.]
2018-05-16 14:09:24.427  INFO 19708 --- [ost-startStop-1] o.a.c.c.C.[.[localhost].[/ricky02]       : Initializing Spring embedded WebApplicationContext
2018-05-16 14:09:24.429  INFO 19708 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3225 ms
2018-05-16 14:09:24.748  INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-05-16 14:09:24.756  INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-05-16 14:09:24.757  INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-05-16 14:09:24.757  INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-05-16 14:09:24.758  INFO 19708 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-05-16 14:09:24.980  INFO 19708 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 14:09:25.424  INFO 19708 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1ab06251: startup date [Wed May 16 14:09:21 CST 2018]; root of context hierarchy
2018-05-16 14:09:25.583  INFO 19708 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-05-16 14:09:25.586  INFO 19708 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-05-16 14:09:25.672  INFO 19708 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 14:09:25.672  INFO 19708 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-05-16 14:09:25.989  INFO 19708 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-05-16 14:09:26.185  INFO 19708 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8021 (http) with context path '/ricky02'
2018-05-16 14:09:26.191  INFO 19708 --- [           main] com.ricky.SpringBootApplication02        : Started SpringBootApplication02 in 13.196 seconds (JVM running for 14.36)

这里实现的只是外部的,如果你还是需要在项目中则可以修改location=classpath:/XXX/

演示项目地址,欢迎fork和star

码云:SpringBootLearn

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏happyJared

Spring Boot 1.0 && 2.0 + JPA 多数据源配置与使用

mysql 对应的数据源配置中,定义了实体 Student 和对应的数据层接口 StudentRepository:

2873
来自专栏CodingToDie

传统Spring项目使用FeignClient组件访问微服务

传统Spring项目使用 这里的传统 Spring项目指的是没有使用 spring boot的 spring项目,例如 ssm api 文件 和在spring ...

9.2K8
来自专栏乐沙弥的世界

MyCAT 日志文件描述

    MyCat是一个基于cobar兴起的开源数据库中间件系统,当前深受广大开源爱好者的追捧以及DBA粉丝们的广泛研究。主要是面对解决高并发,高负载,海量存...

1482
来自专栏阿杜的世界

在Spring Boot项目中使用Spock框架

Spock框架是基于Groovy语言的测试框架,Groovy与Java具备良好的互操作性,因此可以在Spring Boot项目中使用该框架写优雅、高效以及DSL...

2931
来自专栏Spring相关

快速创建SpringBoot+SSM解析

此处使用IDEA快速搭建SpringBoot应用,首先用SpringBoot搭建WEB工程:

1612
来自专栏好好学java的技术栈

SpringMVC+RestFul详细示例实战教程(实现跨域访问)

**REST(Representational State Transfer)**,中文翻译叫“表述性状态转移”。是 Roy Thomas Fielding 在...

2094
来自专栏Python爱好者

MarkDown简单使用A First Level Header

1497
来自专栏一个会写诗的程序员的博客

《Springboot极简教程》使用Spring Boot, JPA, Mysql, ThymeLeaf,gradle, Kotlin快速构建一个CRUD Web App

使用Spring Boot, JPA, Mysql, ThymeLeaf,gradle, Kotlin快速构建一个CRUD Web App

2102
来自专栏用户2442861的专栏

cmake教程4(find_package使用)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/d...

4383
来自专栏大魏分享(微信公众号:david-share)

怎样一个金箍圈(Pipeline),让至尊宝(Openshift)完成了到孙悟空(DevOps)的蜕变

但说出这句话,和实现Devops全工具链落地之间的差距,与造出原子弹和E=MC2公式的差距,实不逞多让。

6224

扫码关注云+社区

领取腾讯云代金券