Spring Initializr是一个非常实用的工具,从工具创建的项目可以快速启动,非常适合新手上手或者新建大量项目的时候。但是有时候可能会遇到一些问题,比如一些特定情况需要手动创建项目,但是创建以后运行不起来,这个时候就需要参考一下。
新版的Spring Initializr提供了预览功能,即选择了版本,功能等以后不需要下载ZIP包然后解压查看,可以直接在线预览,排查问题。


…

Spring Initializr是一个非常实用的工具,从工具创建的项目可以快速启动,非常适合新手上手或者新建大量项目的时候。但是有时候可能会遇到一些问题,比如一些特定情况需要手动创建项目,但是创建以后运行不起来,这个时候就需要参考一下。
新版的Spring Initializr提供了预览功能,即选择了版本,功能等以后不需要下载ZIP包然后解压查看,可以直接在线预览,排查问题。


…
写文档这个事情绝对是软件开发中最纠结的时候了,大部分时候你首先考虑的项目交付,但是从长远来看,一份文档,可以减少很多问题。
Spring提供了一个Rest doc扩展,可以从单元测试中读取一些一些信息并生成文档。
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
首先配置上需要应用文档生成器,这个是一行代码的时候,无所谓。
但是对于文档的内容,你其实需要从代码中注明,比如
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index",
responseFields(
subsectionWithPath("contact")
.description("The user's contact details"))));
andDo之后的和测试本身并无关联,只是为了文档生成。
这个方案在我看来无非就是把文档写到了代码中,并没有根据测试的期待输入和期待输出直接生成文档。
ScaCap的Auto-Restdocs项目是我目前找到的比较好的解决方案,它除了要求andDo之后表明文档名称以外不强制要求别的信息。
它会从请求的字段中,还有请求的POJO中抽取字段和验证信息,文档本身还是使用的Spring Rest Docs的模板和样式。

https://projects.spring.io/spring-restdocs/
https://github.com/ScaCap/spring-auto-restdocs
https://www.slideshare.net/fbenz/introducing-spring-auto-rest-docs…

Openshift Origin是开源的PAAS平台,如果只是为了单机体验,还可以使用minishift。
我之前使用过Openshift 2, 还在上面运行了一些Spring Boot应用。最近又要开始接触Openshift新版本,所以想着用这个练练手,先迁移旧应用过来一下。
在Openshift 2时代,我们需要处理cartridge,它定义了一些基本的东西,比如Tomcat,Cron任务等等,如果你需要的不在这之中,还可以使用Diy。也就是说你需要在代码库中提供action hooks,包括start和stop。我当时是自定义了这两个脚本,然后运行了spring boot应用。

然后到了Openshift Origin 3,我发现我找不到名为Diy的模板了。一番研究才发现居然没有 cartridge,改成images了。当然原因有很多,官方也给出了一篇详细的说明,但是我并不是很关心,我只想知道怎么迁移。
为了方便的从源代码生成Docker用镜像,Openshift Origin 3使用了s2i,大致原理如下:
我们用Spring Boot应用来举个例子,大概步骤如下:
对于具体的脚本名称,s2i又有如下约定
Spring Cloud Function是基于Spring Boot的函数框架,它提供了很大程度上的抽象除了函数编程本身之外的东西,如果你的业务逻辑由多个独立函数提供,那么就可以使用Spring Cloud Function。
这个是官方的例子
@SpringBootApplication
public class Application {
@Bean
public Function<Flux<String>, Flux<String>> uppercase() {
return flux -> flux.map(value -> value.toUpperCase());
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
其中的Function uppercase使我们暴露的函数,默认会直接暴露一个路径为uppercase的API。
因为Spring Cloud Function是Spring …
Kotlin因为之前Android宣布正式支持之后成为了Android世界的Swift,也是大火了一把。
今天使用Spring Boot Initializr生成一个新项目时,突然发现选择的语言中也出现了Kotlin。

感觉试验一下看一下具体长啥样。
首先看看buildscript中多了两个插件,一个是kotlin-gradle-plugin,另外一个是kotlin-allopen。前者是语言支持插件,而后者是专门针对Spring AOP等特性应用的插件,它可以把Kotlin把类和字段变成final的。
对应的插件使用方法为
apply plugin: 'kotlin' apply plugin: 'kotlin-spring'
而配置Java语言等级的方法也要使用Kotlin的
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
其他的变化不大,毕竟是JVM语言,稍微了解一下语法就可以看懂,比如main函数是
package com.huangyunkun.kotlin import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication @SpringBootApplication class KotlinApplication fun main(args: Array<String>)…
很多应用的运行是需要数据库支持的,而随着快速迭代,产品更替的节奏加快,除了产品本身需要不断更新以外,数据库也需要做出合适的管理了。
比如第一个版本的产品只包含了最基本的功能,而第二版本就需要增加评论功能,这就涉及到数据结构的修改(包括创建新表,修改旧表的列,增加已有表的列等等)。直接进入产品数据库修改数据库并不适合快速的开发节奏,不仅仅不安全,更多的情况下数据库可能并不对外或者并不适合对外直接暴露连接,比如PAAS平台的数据库以服务的形式直接提供。
对比代码管理的一些实践,很明显在数据库方面做的还欠缺很多。比如代码管理中我们有
而在数据库方面会遇到很多问题
数据库迁移工具可以很好的管理这些问题,并提供了以下特性
数据库迁移工具很多,这里我们选择Flyway和Liquibase来说主要是两个原因,一是它们都是Java生态圈的,其次就是Spring Boot提供了这两者的内建支持,可以很快应用到产品中。
Flyway相对简单,直接将你需要执行的SQL语句保存为文件,放入应用中执行即可。比如
V1__init-database.sql V2__add-comment.sql
Flyway的好处在于简单,而且直接书写SQL并不需要额外的学习。
Liquibase相对就复杂了很多,它支持四种格式
如果使用过Flyway就会有一定的体会,Flyway的简单是有代价的,举个简单的例子,如果我们开发环境是h2数据库,而测试环境和产品环境是MySQL,这里就有一个问题,SQL语句并不是一个广泛兼容的语言,有些关键字是独有的,而我们并不希望放弃这部分功能。这种情况下你就需要书写两套SQL迁移文件。Spring Boot是内建这种支持的,可以从目录上做区分。
而Liquibase可以根据数据库的情况为你生成最后的迁移语句,同时因为数据库变动首先是被Liquibase解析,所以也可以简单支持回滚。
来看一个Liquibase的例子,以XML为例,我个人觉得yaml更简洁,但是经常有对齐的问题。
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog …Spring Boot 1.5今天发布了。
新版本添加了基于unboundid对于LDAP的支持和Apache Kafka的支持。还有一个有趣的改动是Flyway添加了对于vendor的支持,默认的数据库版本管理地址从db/migration/ 变成了flyway.locations=db/migration/{vendor} ,这里的vendor指的是不同的数据库类型,比如db/migration/mysql 。
新版本还移除了Gradle 1.x的支持(Gradle最新版是3.x)。比较遗憾的是因为CRaSH project已经处于不活跃状态,所以Spring Boot 1.5移除了所有关于CRaSH的支持,也就是remote功能被废弃了。…

Spring Boot对于配置文件的支持非常完善,在配置文件中的内容可以很方便的在程序中应用。在配置文件中可能会出现中文的情况,而这个也是一个可能出现乱码的地方。
首先Spring Boot支持两大类配置文件,一类是java properties文件,即application.properties文件;一类是yaml文件,即application.yml文件。
这两种配置文件分别使用PropertiesPropertySourceLoader 和YamlPropertySourceLoader加载。这两种加载器对于编码的处理是不同的。
PropertiesPropertySourceLoader 会首先检查配置文件后缀,如果是xml文件,那么是可以支持UTF-8的,但是如果是非xml文件,那么它调用的实际上是java的Properties类的load方法,而这个方法默认配置文件的编码是ISO 8859-1的。参考下面的Java API文档:
…The load(Reader) / store(Writer, String) methods load and store properties from and to a character based stream in a simple line-oriented format specified below. The load(InputStream) / store(OutputStream,

编码算不上一个大问题,即使你什么都不管,也有很大的可能你不会遇到任何问题,因为大部分框架都有默认的编码配置,有很多是UTF-8,那么遇到中文乱码的机会很低,所以很多人也忽视了。
Spring系列产品大量运用在网站开发中,而Spring Boot是为了简化配置而出现的,理论上讲Spring Boot应该默认配置UTF-8为默认编码,但是网络上依然可以看到很多关于Spring Boot乱码的文章,大部分解决方案沿用Spring MVC的方案,自定义EncodingFilter。
但是仔细查看Spring Boot的文档,可以看到默认的编码的确是UTF-8
spring.http.encoding.charset=UTF-8 # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly. spring.http.encoding.enabled=true # Enable http encoding support.
而相关的配置会在HttpEncodingAutoConfiguration中使用
@Bean
@ConditionalOnMissingBean(CharacterEncodingFilter.class)
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter …Spring Boot中一个很重要的特性就是自动配置,可以根据很多条件决定是否生成相应的bean或者其他操作。
大部分情况下自动配置的条件是ConditionalOnMissingBean,也就是没有这个bean,那就用这个自动配置配置一个。
有时候自动配置条件并不是那么直接,有时候还涉及到一些自己的逻辑,这个时候就可以自定义SpringBootCondition来处理复杂一些的情况。
继承SpringBootCondition并将自己的逻辑实现在getMatchOutcome方法中。
自定义的逻辑大部分有两种,第一种是自动配置针对一类情况,需要检测多个独立类的存在性,比如EmbeddedDatabaseCondition,就会自动检测所有可能的数据库驱动,包括H2,DERBY,HSQL
@Override
public ConditionOutcome getMatchOutcome( ConditionContext context,
AnnotatedTypeMetadata metadata )
{
if ( anyMatches( context, metadata, this.pooledCondition ) )
{
return(ConditionOutcome.noMatch( "supported DataSource class found" ) );
}
EmbeddedDatabaseType type = EmbeddedDatabaseConnection
.get( context.getClassLoader() ).getType();
…