<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[汪云飞的工具箱]]></title><description><![CDATA[汪云飞的工具箱]]></description><link>https://wisely.top</link><generator>RSS for Node</generator><lastBuildDate>Thu, 14 May 2026 09:52:29 GMT</lastBuildDate><atom:link href="https://wisely.top/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[用Java 17的Records加速Spring Boot开发]]></title><description><![CDATA[在《Spring Boot 2.6新特性：使用Java 17的Record作为配置属性》，我们提到了使用Java Records来作为Spring Boot的配置属性（configuration properties），从而减少了大量样板代码的编写，我们本篇将进一步拓展Records在Spring Boot下的应用场景，从而进一步减少我们的样板代码，使代码看上去更简洁清晰。

1、什么是Records
record是一种特殊类型的类声明，目的是为了减少样板代码。record引入的主要目的是快速创...]]></description><link>https://wisely.top/java-17-records-spring-boot</link><guid isPermaLink="true">https://wisely.top/java-17-records-spring-boot</guid><category><![CDATA[Springboot]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Fri, 11 Nov 2022 10:06:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668161132633/QwRaKEFUi.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在《Spring Boot 2.6新特性：使用Java 17的Record作为配置属性》，我们提到了使用Java Records来作为Spring Boot的配置属性（configuration properties），从而减少了大量样板代码的编写，我们本篇将进一步拓展Records在Spring Boot下的应用场景，从而进一步减少我们的样板代码，使代码看上去更简洁清晰。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668160736017/UfANf3lLj.png" alt="image.png" /></p>
<h2 id="heading-1records">1、什么是Records</h2>
<p>record是一种特殊类型的类声明，目的是为了减少样板代码。record引入的主要目的是快速创建数据载体类。</p>
<p>这种类的主要目的就是在不同的模块或者层之间包含并传递数据，它们表现为POJO（Plain Old Java Objects）和DTO（Data Transfer Objects）。</p>
<p>record声明有专门的的关键字record，我们比较下一个简单的POJO类和record上语法的区别：</p>
<p>POJO类：</p>
<pre><code>@Data
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Point</span> </span>{
    private <span class="hljs-built_in">String</span> x;
    private <span class="hljs-built_in">String</span> y;
}
</code></pre><p>record：</p>
<pre><code>public record Point(<span class="hljs-built_in">String</span> x, <span class="hljs-built_in">String</span> y) {
}
</code></pre><p>我们创建一个简单的演示项目，依赖如图所示：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668160777497/rQb-_28Am.png" alt="image.png" /></p>
<h2 id="heading-recorddto">使用record替代普通DTO</h2>
<p>我们在Spring MVC的控制器中可以用一个record的DTO来接受前端传递来的数据：</p>
<pre><code>@RestController
@RequestMapping(<span class="hljs-string">"/people"</span>)
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonController</span> </span>{

    private final PersonService personService;

    public PersonController(PersonService personService) {
        <span class="hljs-built_in">this</span>.personService = personService;
    }

    @PostMapping
    public ResponseEntity&lt;Person&gt; save(@RequestBody PersonDto personDto){
        <span class="hljs-keyword">return</span> ResponseEntity.ok(personService.save(personDto));
    }

    @GetMapping(<span class="hljs-string">"/findByLastName"</span>)
    public ResponseEntity&lt;List&lt;PersonOnlyWithName&gt;&gt; findByLastName(<span class="hljs-built_in">String</span> lastName){
        <span class="hljs-keyword">return</span> ResponseEntity.ok(personService.findByLastName(lastName));
    }
}
</code></pre><p>上面的PersonDto是一个record：</p>
<pre><code>public record PersonDto(<span class="hljs-built_in">String</span> firstName, <span class="hljs-built_in">String</span> lastName,Integer age) {
}
</code></pre><h2 id="heading-3recordspringbean">3、使用record作为Spring的Bean</h2>
<p>上面注入的PersonService，是一个Spring的Bean，它同样可以是一个record，我们只需要在record的参数里写上要被注入的bean，这个bean就会自动被注入：</p>
<pre><code>@Service
public record PersonService(PersonRepository personRepository) {

  <span class="hljs-comment">//保存person</span>
    public Person save(PersonDto personDto){
        Person person = <span class="hljs-keyword">new</span> Person(personDto.firstName(), personDto.lastName(), personDto.age());
        <span class="hljs-keyword">return</span> personRepository.save(person);
    }

  <span class="hljs-comment">//按照lastName查询people，返回值只有firstName和lastName</span>
    public List&lt;PersonOnlyWithName&gt; findByLastName(<span class="hljs-built_in">String</span> lastName){
        <span class="hljs-keyword">return</span> personRepository.findByLastName(lastName);
    }
}
</code></pre><p>在这里的PersonRepository的bean可以自动被注入，代码上比属性@Autowired注入，甚至构造器注入代码更简洁。</p>
<p>Spring Data JPA用作数据访问的Repository：</p>
<pre><code>public interface PersonRepository <span class="hljs-keyword">extends</span> JpaRepository&lt;Person, Long&gt; {

    List&lt;PersonOnlyWithName&gt; findByLastName(<span class="hljs-built_in">String</span> lastName);
}
</code></pre><p>使用record来声明bean，有一些潜在的问题：</p>
<blockquote>
<p>1、record中，被注入的对象在当前对象里其实是有一个隐藏的get方法：“
personService.personRepository()”，这违反了信息隐藏的封装原则。</p>
<p>2、record定义了equals和hasCode方法，作为service并不需要。</p>
<p>3、service的变量属性一般都是final。</p>
<p>如果上述的东西对你并没有什么影响，你可以自由决定是否使用。</p>
</blockquote>
<h2 id="heading-3recordspring-data-jpaprojection">3、使用record作为Spring Data JPA的projection</h2>
<p>Spring Data JPA的projection目的是定制查询的数据返回，而不是返回整个实体。一般情况下都是使用接口或者dto类，现在支持使用record。</p>
<p>定制的返回的record内容为：</p>
<pre><code>public record PersonOnlyWithName(<span class="hljs-built_in">String</span> firstName, <span class="hljs-built_in">String</span> lastName) {
}
</code></pre><p>即我们查询返回的结果，不需要id和age，只需要firstName和lastName。</p>
<h2 id="heading-4">4、演示应用</h2>
<p>启动程序，保存Person，插入两条数据：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668161036673/2tzMWBWHS.png" alt="image.png" /></p>
<p>按照lastName查询，查看我们projection的效果：</p>
<h2 id="heading-recordcontroller">用record改造Controller控制器</h2>
<p>在上面我们的Controller用的还是普通的class，既然record可以声明为bean并注入bean，那我们改造一下上面的Controller。</p>
<pre><code>@RestController
@RequestMapping(<span class="hljs-string">"/people"</span>)
public record PersonController(PersonService personService) {

    @PostMapping
    public ResponseEntity&lt;Person&gt; save(@RequestBody PersonDto personDto){
        <span class="hljs-keyword">return</span> ResponseEntity.ok(personService.save(personDto));
    }

    @GetMapping(<span class="hljs-string">"/findByLastName"</span>)
    public ResponseEntity&lt;List&lt;PersonOnlyWithName&gt;&gt; findByLastName(<span class="hljs-built_in">String</span> lastName){
        <span class="hljs-keyword">return</span> ResponseEntity.ok(personService.findByLastName(lastName));
    }
}
</code></pre><p>代码比构造器注入更精简。</p>
<p>感谢对我的书《从企业级开发到云原生微服务：Spring Boot实战》的支持。</p>
<p>头条原文地址：https://www.toutiao.com/article/7161419177252127239</p>
]]></content:encoded></item><item><title><![CDATA[Spring Boot 2.6新特性：使用Java 17的Record作为配置属性]]></title><description><![CDATA[Spring Boot 3.0的基线版本是Java 17，Spring Boot 3.0版本将全面对Java 17的支持。较新版本的2.x的Spring Boot版本也可以使用Java 17的特性。
本文介绍Spring Boot 2.6对Java 17支持的一个新特性，使用Java 17的Record来做为Spring Boot的配置属性（ConfiguartionProperties）。
什么是Record
record是一种特殊类型的类声明，目的是为了减少样板代码。record引入的主要目...]]></description><link>https://wisely.top/spring-boot-26-java-17-record</link><guid isPermaLink="true">https://wisely.top/spring-boot-26-java-17-record</guid><category><![CDATA[java 17]]></category><category><![CDATA[Springboot]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Thu, 03 Nov 2022 09:58:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667469159762/5DVks67DU.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Spring Boot 3.0的基线版本是Java 17，Spring Boot 3.0版本将全面对Java 17的支持。较新版本的2.x的Spring Boot版本也可以使用Java 17的特性。</p>
<p>本文介绍Spring Boot 2.6对Java 17支持的一个新特性，使用Java 17的Record来做为Spring Boot的配置属性（ConfiguartionProperties）。</p>
<h2 id="heading-record">什么是Record</h2>
<p>record是一种特殊类型的类声明，目的是为了减少样板代码。record引入的主要目的是快速创建数据载体类。</p>
<p>这种类的主要目的就是在不同的模块或者层之间包含并传递数据，它们表现为POJO（Plain Old Java Objects）和DTO（Data Transfer Objects）。</p>
<p>record声明有专门的的关键字record，我们比较下一个简单的POJO类和record上语法的区别：</p>
<p>POJO类：</p>
<pre><code class="lang-java"><span class="hljs-meta">@Data</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Point</span> </span>{
    <span class="hljs-keyword">private</span> String x;
    <span class="hljs-keyword">private</span> String y;
}
</code></pre>
<p>record：</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Point</span><span class="hljs-params">(String x, String y)</span> </span>{
}
</code></pre>
<p>Spring Boot的配置属性（Configuration Properties）类也是一个简单POJO。</p>
<h2 id="heading-pojoconfiguration-properties">用POJO类作为Configuration Properties</h2>
<ul>
<li>新建演示项目，注意添加Spring Configuration Processor</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667469249411/eRsGHsGyk.png" alt="image.png" /></p>
<ul>
<li>演示的Configuration Properties，首先我们使用常规的Java类来演示</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@ConfigurationProperties(prefix = "author")</span>
<span class="hljs-meta">@Data</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthorProperties</span> </span>{
    <span class="hljs-keyword">private</span> String firstName;
    <span class="hljs-keyword">private</span> String lastName;
}
</code></pre>
<ul>
<li>application.properties 中的配置</li>
</ul>
<pre><code class="lang-java">author.firstName=wang
author.lastName=yunfei
</code></pre>
<ul>
<li>注册Configuration Properties为Bean，并使用</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-meta">@EnableConfigurationProperties(AuthorProperties.class)</span><span class="hljs-comment">//将AuthorProperties注册为Bean</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RecordConfigurationPropertiesApplication</span> </span>{

   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
      SpringApplication.run(RecordConfigurationPropertiesApplication.class, args);
   }

   <span class="hljs-meta">@Bean</span> <span class="hljs-comment">//注入并使用AuthorProperties的Bean</span>
   <span class="hljs-function">CommandLineRunner <span class="hljs-title">commandLineRunner</span><span class="hljs-params">(AuthorProperties authorProperties)</span></span>{
      <span class="hljs-keyword">return</span> args -&gt; {
         log.info(authorProperties.toString());
      };
   }
}
</code></pre>
<ul>
<li>运行程序</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667469364736/Dgpt_sCvQ.png" alt="image.png" /></p>
<h2 id="heading-recordconfiguration-properties">用Record作为Configuration Properties</h2>
<p>我们只需要将AuthorProperties类修改为record即可：</p>
<pre><code class="lang-java"><span class="hljs-meta">@ConfigurationProperties(prefix = "author")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">AuthorProperties</span><span class="hljs-params">(String firstName, String lastName)</span> </span>{
}
</code></pre>
<p>运行结果和上面保持一致。</p>
<p>感谢对我的书《从企业级开发到云原生微服务：Spring Boot实战》的支持。</p>
<p>头条原文地址：<a target="_blank" href="https://www.toutiao.com/article/7159057842337530371/">https://www.toutiao.com/article/7159057842337530371/</a></p>
]]></content:encoded></item><item><title><![CDATA[使用Gradle全面加速Spring Boot开发]]></title><description><![CDATA[大家都知道Gradle和Maven一样，是一个项目的构建工具。它通过任务来控制开发的进程，这些任务包括：编译、打包、测试、部署和发布等。Gradle诞生于2008年，仅仅比Maven晚4年。Android也采用Gradle作为默认的构建工具。
本文期望通过以下的讲述，帮助你快速轻松的使用Gradle加速您的Spring Boot开发应用。

1、Gradle越来越流行
2012年开始，Spring框架已全部使用Gradle来构建；2020年开始，Spring Boot也全部采用Gradle来构建...]]></description><link>https://wisely.top/use-gradle-accelerate-spring-boot-dev</link><guid isPermaLink="true">https://wisely.top/use-gradle-accelerate-spring-boot-dev</guid><category><![CDATA[gradle]]></category><category><![CDATA[Springboot]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Mon, 31 Oct 2022 04:43:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667188309187/bOMJzltYc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>大家都知道Gradle和Maven一样，是一个项目的构建工具。它通过任务来控制开发的进程，这些任务包括：编译、打包、测试、部署和发布等。Gradle诞生于2008年，仅仅比Maven晚4年。Android也采用Gradle作为默认的构建工具。</p>
<p>本文期望通过以下的讲述，帮助你快速轻松的使用Gradle加速您的Spring Boot开发应用。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667188384543/Ms2ZM6K9p.png" alt="image.png" /></p>
<h2 id="heading-1gradle">1、Gradle越来越流行</h2>
<p>2012年开始，Spring框架已全部使用Gradle来构建；2020年开始，Spring Boot也全部采用Gradle来构建。官方使用Gradle的主要原因还是“构建项目花费更少的时间”。（可参考：<a target="_blank" href="https://www.toutiao.com/article/7100765593200067080/">Spring/Spring Boot编译工具从Maven迁移到了Gradle</a>）</p>
<p>但对于我们普通的应用开发者来说，Gradle对于我们的优势在于：更简洁的代码和更丰富的功能。（可参考：<a target="_blank" href="https://www.toutiao.com/article/7099358673314644492/">Gradle大战Maven，胜负已分？</a>）</p>
<p>最近的一些统计报告，越来越显示出Gradle有越来越多的人在使用它。</p>
<ul>
<li>stackoverflow的趋势报告</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667188404513/hQlNzxU0e.png" alt="image.png" /></p>
<ul>
<li>OpenLogic发布的《2022年度开源报告》</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667188419790/HvfgepDqp.png" alt="image.png" /></p>
<p>最近有出了一个振奋人心的消息是，Spring官方生成应用程序的网站：spring initializr（<a target="_blank" href="https://start.spring.io/">https://start.spring.io/</a>），也将默认的构建工具从Maven切换到了Gradle。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667188436287/x5uxRfPoz.png" alt="image.png" /></p>
<p>综合上述种种，我觉得有必要了解一下快速易用的Gradle了。</p>
<h2 id="heading-2spring-boot">2、了解Spring Boot项目的结构</h2>
<ul>
<li>打开:<a target="_blank" href="https://start.spring.io">https://start.spring.io</a>，生成演示项目，项目构建工具选中的默认的Gradle。</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667188510689/owR0vAhsW.png" alt="image.png" /></p>
<ul>
<li>点击“GENERATE”生成项目压缩包，解压这个压缩包，项目的结构如下：</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667188569281/fSM7BgI99.png" alt="image.png" /></p>
<p>和Gradle有关系的有：settings.gralde 、gradle wrapper[gradle/wrapper 目录、gradlew （gradlew.bat ）]、build.gradle 。下面我们逐一的了解。</p>
<h3 id="heading-21-settingsgradle">2.1 settings.gradle</h3>
<p>本文件配置构建项目所需要的信息，Spring Boot项目的该文件内容很简单：</p>
<pre><code class="lang-groovy">rootProject.name = 'gradle-demo'
</code></pre>
<h3 id="heading-22-gradle-wrapper">2.2 gradle wrapper</h3>
<p>我们通过使用gradle wrapper脚本，可以直接下载、配置gradle，无需外部安装配置gradle。这样也可以为你的当前程序匹配最合适版本的Gradle版本。</p>
<p>可以直接通过gradle wrapper脚本对程序进行编译、打包、测试、部署和发布等任务操作。</p>
<p>针对Linux/MacOS的gradle wrapper是gradlew ；</p>
<p>针对Windows的gradler wrapper是gradlew.bat 。</p>
<h3 id="heading-23-gradle">2.3 如何加速gradle下载</h3>
<p>gradle wrapper会自动下载gradle，若默认的下载gradle的url网速很慢，可以替换成阿里云或腾讯云的地址。当然可以是你公司内部的文件下载地址。</p>
<p>我们在
gradle/wrapper/gradle-wrapper.properties 的<code>distributionUrl</code> 类配置：</p>
<pre><code class="lang-properties">distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
</code></pre>
<p>若使用阿里云的地址加速（列表页面：
<a target="_blank" href="https://mirrors.aliyun.com/macports/distfiles/gradle/">https://mirrors.aliyun.com/macports/distfiles/gradle/</a>）：</p>
<pre><code class="lang-properties">distributionUrl=https\://mirrors.aliyun.com/macports/distfiles/gradle/gradle-7.5.1-bin.zip
</code></pre>
<p>若使用腾讯云地址加速，可在页面：
<a target="_blank" href="https://mirrors.cloud.tencent.com/gradle/">https://mirrors.cloud.tencent.com/gradle/</a>查找。</p>
<h3 id="heading-24-buildgradle">2.4 build.gradle</h3>
<p>build.gradle 相当于Maven的pom.xml，不过它比Maven要简介很多，如同样的项目，如果用Maven生成的代码对比的话：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667188763707/CHqotFUn7.png" alt="image.png" /></p>
<p>xml是一门很清晰但是却很啰嗦的表意语言，在Java生态里使用的越来越少，在Spring Boot的开发中几乎没有任何和xml有关系的内容。由图中的对比也可以看出Gradle比Maven易读性更强且更易维护！</p>
<p>build.gradle的核心内容包括：plugins（插件）、库（repositories）、依赖（dependecies）、任务（tasks）。</p>
<p>因build.gradle是本文核心的内容之一，我们将专门用一个大标题来讲解build.gradle。</p>
<h2 id="heading-3buildgradle">3、build.gradle</h2>
<p>生成的build.gradle的文件内容如下：</p>
<pre><code class="lang-groovy">plugins {
    id 'org.springframework.boot' version '2.7.5'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    id 'java'
}

group = 'top.wisely'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    maven { url 'https://maven.aliyun.com/repository/public/' }
    maven { url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}
</code></pre>
<p>下面我们对重点内容进行讲解。</p>
<h3 id="heading-31-plugins">3.1 plugins（插件）</h3>
<p>build.gradle中声明了plugins的依赖。</p>
<p>核心插件只需要使用id简称即可，社区插件需要使用全名称的id。</p>
<pre><code class="lang-groovy">plugins {
    id 'org.springframework.boot' version '2.7.5'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    id 'java'
}
</code></pre>
<p>社区插件：Spring Boot Gradle插件，它为我们提供Spring Boot运行，创建可执行jar包或者war包的能力，这个我们在下一节会着重的去讲解：</p>
<pre><code class="lang-groovy">id 'org.springframework.boot' version '2.7.5'
</code></pre>
<p>社区插件：Spring Boot依赖管理插件，它为我们提供对项目直接或间接依赖的库的版本的控制，这些版本都是Spring Boot做过兼容性测试的版本：</p>
<pre><code class="lang-groovy">id 'io.spring.dependency-management' version '1.0.15.RELEASE'
</code></pre>
<p>核心插件：Java插件将Java编译以及测试等功能添加到项目中。</p>
<pre><code class="lang-groovy">id 'java'
</code></pre>
<h3 id="heading-32-repositories">3.2 repositories（库）</h3>
<p>Gradle默认使用Maven的中心库下载依赖包：</p>
<pre><code class="lang-groovy">repositories {
    mavenCentral()
}
</code></pre>
<p>如果从中心库下载jar包的速度慢，我们可以通过阿里云或者腾讯云的Maven库镜像来加速，当然也可以使用你公司内部的Maven库私服来加速。</p>
<pre><code class="lang-groovy">repositories {
    maven { url 'https://maven.aliyun.com/repository/public/' }
    maven { url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
    mavenCentral()
}
</code></pre>
<h3 id="heading-33-dependecies">3.3 dependecies（依赖）</h3>
<ul>
<li>普通依赖：implementation
我们只需要按照下面的格式即可引入依赖：</li>
</ul>
<pre><code class="lang-groovy">implementation group: '***', name: '***', version: '***'
</code></pre>
<p>或简写成：</p>
<pre><code class="lang-groovy">implementation 'group:name:version'
</code></pre>
<p>因为插件，只要被Spring Boot所管理的依赖，版本也无须维护，可以更加精简的写成：</p>
<pre><code class="lang-groovy">implementation 'group:name'
</code></pre>
<p>依赖样子一般为：</p>
<pre><code class="lang-groovy">dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    //...
}
</code></pre>
<p>在我们绝大部分时间，我们和gradle做依赖相关的工作，在了解这些后都能解决了。</p>
<p>我们可以通过：https://mvnrepository.com 网站查找添加依赖，我们在依赖中选中Gradle（short）来复制内容到我们的依赖中。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189139857/RtVFIEHZ1.png" alt="image.png" /></p>
<ul>
<li>测试依赖：testImplementation</li>
</ul>
<p>测试相关的依赖，使用testImplementation，规则和普通依赖一致。</p>
<pre><code class="lang-groovy">dependencies {
    //...
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
</code></pre>
<ul>
<li>编译依赖：compileOnly
只在编译期起效的依赖。</li>
</ul>
<pre><code class="lang-groovy">dependencies {
    //...
    compileOnly 'org.projectlombok:lombok'
  //...
}
</code></pre>
<ul>
<li>运行时依赖：runtimeOnly</li>
</ul>
<p>只在运行时起效的依赖。</p>
<pre><code class="lang-groovy">dependencies {
    //...
    runtimeOnly 'com.mysql:mysql-connector-j'
    //...
}
</code></pre>
<h2 id="heading-4spring-boot-gradle-plugin">4、Spring Boot Gradle Plugin</h2>
<p>能够让Spring Boot各种能力运行起来，主要是依靠Spring Boot Gradle插件。Spring Boot Gradle插件中包含了大量的task（任务），我们可以用gradlew来查看他提供的所有tasks（任务）。</p>
<ul>
<li>项目任务列表：通过下面的任务来查询当前项目的任务列表，这些大部分任务都是由Spring Boot Gradle插件提供的。</li>
</ul>
<pre><code class="lang-shell">./gradlew tasks
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189265758/lBqd0aSX2.png" alt="image.png" /></p>
<ul>
<li>查询具体的任务信息的帮助</li>
</ul>
<pre><code class="lang-shell">./gradlew help --task bootRun
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189295501/09z-5gfiq.png" alt="image.png" /></p>
<ul>
<li>添加简单的演示代码</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GradleDemoApplication</span> </span>{

   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
      SpringApplication.run(GradleDemoApplication.class, args);
   }

   <span class="hljs-meta">@RequestMapping("/")</span>
   <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">hello</span><span class="hljs-params">()</span></span>{
      <span class="hljs-keyword">return</span> <span class="hljs-string">"hello gradle!!!"</span>;
   }
}
</code></pre>
<ul>
<li>运行Spring Boot程序的task：bootRun</li>
</ul>
<pre><code class="lang-shell">./gradlew bootRun
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189343305/oG9Khmu_-.png" alt="image.png" /></p>
<p>访问：http://localhost:8080/</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189359877/kBMeI4dNl.png" alt="image.png" /></p>
<ul>
<li>编译可执行jar包task：build /bootJar</li>
</ul>
<pre><code class="lang-shell">./gradlew bootJar
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189418618/IO6DP7Kq4.png" alt="image.png" /></p>
<p>会在build/libs 目录下，生成可执行jar包。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189434434/uT7pHM8HU.png" alt="image.png" /></p>
<p>jar包的名字为
gradle-demo-0.0.1-SNAPSHOT.jar，jar包的名字是由上面的settrings.gradle 中设置的名字</p>
<ul>
<li>清理build的任务：clean</li>
</ul>
<pre><code class="lang-shell">./gradlew clean
</code></pre>
<ul>
<li>执行测试的任务：test</li>
</ul>
<pre><code class="lang-shell">./gradlew test
</code></pre>
<h2 id="heading-5intellij-ideagradle">5、Intellij IDEA和Gradle：如虎添翼</h2>
<h3 id="heading-51-gradlespring-boot">5.1 创建Gradle的Spring Boot项目</h3>
<p>Intellij IDEA支持新建Spring Initializr的项目，注意选择Gradle作为构建工具。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189557623/VfnNOe94v.png" alt="image.png" /></p>
<h3 id="heading-52-gradlespring-boot">5.2 可直接打开基于Gradle的Spring Boot项目</h3>
<p>可直接在File-》Open选择基于Gradle的Spring Boot项目，导入到Intellij IDEA后，会自动下载相关的依赖。下面是导入项目导入到Intellij IDEA的样子。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189579553/k595Jm5Ia.png" alt="image.png" /></p>
<h3 id="heading-53-gradletasks">5.3 在Gradle工具窗口执行tasks（任务）</h3>
<p>在上图中，我们看见右侧有Gradle的页签，点开可以看见Gradle工具窗口，窗口中有任务列表。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189584249/tVOlRmgSl.png" alt="image.png" /></p>
<p>在任务列表，我们选择一个任务如bootJar 双击运行：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189590423/RIeubbYE_.png" alt="image.png" /></p>
<h3 id="heading-54-buildgradle">5.4 加载build.gradle的变化</h3>
<ul>
<li>手动加载：可以点击Gradle工具窗口的刷新按钮或build.gradle文件上的刷新按钮实现手动加载。</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189601274/zHA24nAzo.png" alt="image.png" /></p>
<ul>
<li>自动加载：在设置的Build Tool 中的Reload project after changes in the build scripts 选中Any changes 。</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189618958/9qk8J7fAx.png" alt="image.png" /></p>
<h3 id="heading-55">5.5 快捷检索、添加依赖</h3>
<p>在build.gradle 页面，MacOS下使用command + N ，Windows下使用Alt+Insert。可调出Add depedency。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189626127/g1SD2QZJH.png" alt="image.png" /></p>
<p>点击Add depedency后，我们会多一个依赖的检索视图，可以在此检索并添加依赖：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189635194/N8HoWhf5t.png" alt="image.png" /></p>
<h3 id="heading-56-buildgradle">5.6 build.gradle脚本自动补全</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189639998/Jn7Hbz9V6.png" alt="image.png" /></p>
<h3 id="heading-57-gradle">5.7 在Gradle工具窗口查看项目的依赖</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667189650383/glL7MFrZB.png" alt="image.png" /></p>
<h2 id="heading-6">6、结语</h2>
<p>通过本文的讲解，一般情况下可以满足你在项目开发中绝大部分的需求。如果有更深层的使用需求，可留言讨论。</p>
<p>最后送给大家一句话：</p>
<p>"I can’t understand why people are frightened of new ideas. I’m frightened of the old ones." — John Cage
"我不明白为什么人们害怕新想法。 我害怕那些旧的。" - 约翰凯奇。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667190757005/6Emy4qike.png" alt="image.png" /></p>
<p>感谢对我的书《从企业级开发到云原生微服务：Spring Boot实战》的支持。</p>
<p>头条原文地址：<a target="_blank" href="https://www.toutiao.com/article/7155054951813644803/">https://www.toutiao.com/article/7155054951813644803/</a></p>
<p>参考资料：</p>
<p>https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/</p>
<p>https://gustavopeiretti.com/spring-boot-with-gradle-wrapper/</p>
<p>https://tomgregory.com/10-tips-to-use-gradle-with-intellij-idea/</p>
]]></content:encoded></item><item><title><![CDATA[Spring/Spring Boot下如何动态配置计划任务]]></title><description><![CDATA[在Spring/Spring Boot下实现计划任务是很简单的，我们只需通过@EnableScheduling 开启计划任务的支持，然后通过@Scheduled 注解来制定计划任务，这样的实现解决了我们对计划任务的绝大部分需求。
但在很多时候，通过上述方式实现的计划任务是在代码里写死的，我们需要修改计划任务只能通过修改代码的方式实现。很多时候，我们需要从外部来设置计划任务执行的时间和方式。所以在本文中，我们着重讲解一下如何动态地配置计划任务。
和Spring对异步任务的支持一样，通过@Enabl...]]></description><link>https://wisely.top/spring-boot-dynamic-scheduler</link><guid isPermaLink="true">https://wisely.top/spring-boot-dynamic-scheduler</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Sat, 29 Oct 2022 10:16:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667037966222/c7wbXoh1Q.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在Spring/Spring Boot下实现计划任务是很简单的，我们只需通过<code>@EnableScheduling</code> 开启计划任务的支持，然后通过<code>@Scheduled</code> 注解来制定计划任务，这样的实现解决了我们对计划任务的绝大部分需求。</p>
<p>但在很多时候，通过上述方式实现的计划任务是在代码里写死的，我们需要修改计划任务只能通过修改代码的方式实现。很多时候，我们需要从外部来设置计划任务执行的时间和方式。所以在本文中，我们着重讲解一下如何动态地配置计划任务。</p>
<p>和Spring对异步任务的支持一样，通过<code>@EnableAsync</code> 注解开启异步的支持，然后通过<code>@Async</code> 注解来指定异步的方法，而真正的异步任务的执行者是<code>TaskExecutor</code>接口 ，它的实现是<code>ThreadPoolTaskExecutor</code>。（若感兴趣可参考《<a target="_blank" href="https://www.toutiao.com/article/6906814128846127619/">Spring Boot 实战21 - Spring Boot的多线程异步任务</a>》，此处只为比较加深记忆）</p>
<p>同样，Spring对计划任务的支持也是通过<code>@EnableScheduling</code> 开启计划任务的支持，然后通过<code>@Scheduled</code> 注解来制定计划任务，而真正的任务执行者是<code>TaskScheduler</code> 接口，它的实现是<code>ThreadPoolTaskScheduler</code> 。（若对于固定配置的计划任务感兴趣可参考《<a target="_blank" href="https://www.toutiao.com/article/6907219076704453128/">Spring Boot 实战22 - Spring Boot的计划任务</a>》）</p>
<p><code>TaskScheduler</code>的接口有三类主要方法：</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">TaskScheduler</span> </span>{

  <span class="hljs-comment">//指定时间启动计划任务</span>
   ScheduledFuture&lt;?&gt; schedule(Runnable task, Trigger trigger);
  ScheduledFuture&lt;?&gt; schedule(Runnable task, Date startTime);

  <span class="hljs-comment">//指定频率启动计划任务</span>
   ScheduledFuture&lt;?&gt; scheduleAtFixedRate(Runnable task, Date startTime, <span class="hljs-keyword">long</span> period);
   ScheduledFuture&lt;?&gt; scheduleAtFixedRate(Runnable task, <span class="hljs-keyword">long</span> period);

  <span class="hljs-comment">//指定延迟启动计划任务</span>
   ScheduledFuture&lt;?&gt; scheduleWithFixedDelay(Runnable task, Date startTime, <span class="hljs-keyword">long</span> delay);
   ScheduledFuture&lt;?&gt; scheduleWithFixedDelay(Runnable task, <span class="hljs-keyword">long</span> delay);
}
</code></pre>
<p>方法的返回值都是<code>ScheduledFuture</code> 接口,可以使用它来取消任务或者检查任务是否完成。而实际要执行的任务是Runnable的实现类。</p>
<p>那我们可以通过<code>TaskScheduler</code>来实现动态的计划任务的实现。Spring Boot为我们提供了
<code>TaskSchedulingAutoConfiguration</code>自动配置来自动注册<code>TaskScheduler</code> 的Bean。所以我们无需配置可直接注入<code>TaskScheduler</code>的Bean即可使用。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667038422388/yulmYyH7p.png" alt="image.png" /></p>
<p>Spring Boo同样提供了<code>TaskSchedulingProperties</code>来<code>TaskScheduler</code> 进行配置，我们可以通过：<code>spring.task.scheduling</code>的属性进行配置。</p>
<ul>
<li>新建一个演示项目</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667038411382/JAfe9R-Qn.png" alt="image.png" /></p>
<ul>
<li>构建需要定时启动的任务</li>
</ul>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RunnableTask</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Runnable</span></span>{
    <span class="hljs-keyword">private</span> String taskName;
    <span class="hljs-keyword">private</span> WorkingService workingService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">RunnableTask</span><span class="hljs-params">(String taskName, WorkingService workingService)</span> </span>{
        <span class="hljs-keyword">this</span>.taskName = taskName;
        <span class="hljs-keyword">this</span>.workingService = workingService;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{
        workingService.work( taskName );
    }
}
</code></pre>
<p>这里为了还原实际，一般我们会在任务中调用Spring容器中的其他类进行操作，在此我们引入了一个<code>WorkingService</code>来真正执行任务。</p>
<ul>
<li>简单的WorkingService</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WorkingService</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">work</span><span class="hljs-params">(String taskName)</span></span>{
        log.info(<span class="hljs-string">"当前任务："</span> + taskName +<span class="hljs-string">" 在线程"</span> + Thread.currentThread().getName() + <span class="hljs-string">"上运行"</span>);
    }
}
</code></pre>
<ul>
<li>使用<code>TaskScheduler</code> 的Bean启动定时任务</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SchedulingService</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> TaskScheduler taskScheduler; <span class="hljs-comment">// 可直接注入TaskScheduler的Bean</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> WorkingService workingService; 

    <span class="hljs-comment">//构造器注入，免@Autowired</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">SchedulingService</span><span class="hljs-params">(TaskScheduler taskScheduler, WorkingService workingService)</span> </span>{ 
        <span class="hljs-keyword">this</span>.taskScheduler = taskScheduler;
        <span class="hljs-keyword">this</span>.workingService = workingService;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">schedule</span><span class="hljs-params">(String taskName, String cronExpression)</span></span>{
          <span class="hljs-comment">//RunnableTask不是一个Bean，不能直接注入workingService，在它的构造器直接传入</span>
        RunnableTask task = <span class="hljs-keyword">new</span> RunnableTask(taskName,workingService);
        CronTrigger cronTrigger = <span class="hljs-keyword">new</span> CronTrigger(cronExpression);
       <span class="hljs-comment">//api接受实际执行的任务，和定时启动的cron表达式</span>
        taskScheduler.schedule(task,cronTrigger);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">scheduleAtFixedRate</span><span class="hljs-params">(String taskName, <span class="hljs-keyword">long</span> period)</span></span>{
        RunnableTask task = <span class="hljs-keyword">new</span> RunnableTask(taskName,workingService);
       <span class="hljs-comment">//api接受实际执行的任务，和间隔时间</span>
        taskScheduler.scheduleAtFixedRate(task, period);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">scheduleWithFixedDelay</span><span class="hljs-params">(String taskName, <span class="hljs-keyword">long</span> delay)</span></span>{
        RunnableTask task = <span class="hljs-keyword">new</span> RunnableTask(taskName,workingService);
       <span class="hljs-comment">//api接受实际执行的任务，和延迟时间</span>
        taskScheduler.scheduleWithFixedDelay(task, delay);
    }
}
</code></pre>
<ul>
<li>在启动类设置动态配置</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-meta">@EnableScheduling</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DynamicTaskSchedulerApplication</span> </span>{

   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
      SpringApplication.run(DynamicTaskSchedulerApplication.class, args);
   }

   <span class="hljs-meta">@Bean</span>
   <span class="hljs-function">CommandLineRunner <span class="hljs-title">commandLineRunner</span><span class="hljs-params">(SchedulingService schedulingService, WorkingService workingService)</span></span>{
      <span class="hljs-keyword">return</span> args -&gt; {
         schedulingService.schedule(<span class="hljs-string">"指定时间任务"</span>, <span class="hljs-string">"50 * * * * THU"</span>);
         schedulingService.scheduleAtFixedRate(<span class="hljs-string">"指定频率任务"</span>, <span class="hljs-number">1000</span>);
         schedulingService.scheduleWithFixedDelay(<span class="hljs-string">"指定延迟任务"</span>, <span class="hljs-number">1000</span>);
      } ;
   }
}
</code></pre>
<ul>
<li><code>application.properties</code> 的配置</li>
</ul>
<pre><code class="lang-properties">spring.task.scheduling.pool.size=3
spring.task.scheduling.thread-name-prefix=wisely-
</code></pre>
<ul>
<li>启动程序验证</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667038391437/Ug_XlAzMo.png" alt="image.png" /></p>
<p>头条原文地址：<a target="_blank" href="https://www.toutiao.com/article/7158705923182182927/">https://www.toutiao.com/article/7158705923182182927/</a></p>
<p>感谢对我的书《从企业级开发到云原生微服务：Spring Boot实战》的支持。</p>
]]></content:encoded></item><item><title><![CDATA[Spring 6/Spring Boot 3新特性：优雅的业务异常处理]]></title><description><![CDATA[当你使用Spring Boot(Spring MVC)进行RESTful API开发的时候，你会发现HTTP的状态码很多时候不能足够有效的传递错误的信息。
HTTP里有一个RFC 7807规范：https://www.rfc-editor.org/rfc/rfc7807。这个规范里定义了HTTP API的“问题细节”（Problem Details）内容。
该规范定义了一个“问题细节”（Problem Details），用它来携带HTTP错误返回信息，避免自定义新的错误返回格式。我们通常情况下是...]]></description><link>https://wisely.top/spring-6-spring-boot-3-business-exception</link><guid isPermaLink="true">https://wisely.top/spring-6-spring-boot-3-business-exception</guid><category><![CDATA[SpringBoot3]]></category><category><![CDATA[spring6]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Fri, 28 Oct 2022 01:25:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666919741595/nsDEUqUP2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>当你使用Spring Boot(Spring MVC)进行RESTful API开发的时候，你会发现HTTP的状态码很多时候不能足够有效的传递错误的信息。</p>
<p>HTTP里有一个RFC 7807规范：<a target="_blank" href="https://www.rfc-editor.org/rfc/rfc7807">https://www.rfc-editor.org/rfc/rfc7807</a>。这个规范里定义了HTTP API的“问题细节”（Problem Details）内容。</p>
<p>该规范定义了一个“问题细节”（Problem Details），用它来携带HTTP错误返回信息，避免自定义新的错误返回格式。我们通常情况下是自己定义错误返回格式的。</p>
<p>Spring 6.0为我们提供了一个<code>org.springframework.http.ProblemDetail</code> 来实现该规范。</p>
<p>RFC 7807是一个很简单的规范。它定义了一个JSON格式，并关联了一个媒体类型（media type），这个JSON格式包含了五个可选成员来描述问题细节：</p>
<ul>
<li><strong>type </strong>：一个URI引用，用来识别问题的类型。这个URI的路径内容应该用来显示人类可读的信息来描述类型；</li>
<li><strong>title </strong>：人类可读的问题类型描述；相同类型的问题，应该总是相同的描述；</li>
<li><strong>status </strong>：HTTP状态码，将它包含在问题细节里是一种方便的方式；</li>
<li><strong>detail </strong>：人类可读的问题实例描述，解释为什么当前的问题发生在这个特定的场景下；</li>
<li><strong>instance </strong>：一个URI引用，用来识别问题实例。这个URI的内容应该用来描述问题实例，但不是必须的。</li>
</ul>
<p>我们首先建立一个演示项目：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666919871020/IODkH3Upx.png" alt="image.png" /></p>
<h2 id="heading-1">1、通常的业务异常处理方法</h2>
<ul>
<li>定义一个业务异常类。异常含义为：当Person找不到的时候抛出的业务异常。</li>
</ul>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonNotFoundException</span>  <span class="hljs-keyword">extends</span> <span class="hljs-title">RuntimeException</span> </span>{
    <span class="hljs-meta">@Getter</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> HttpStatus status;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PersonNotFoundException</span><span class="hljs-params">(String message, HttpStatus status)</span> </span>{
        <span class="hljs-keyword">super</span>(message);
        <span class="hljs-keyword">this</span>.status = status;
    }
}
</code></pre>
<ul>
<li>注册这个业务异常到全局异常处理。</li>
</ul>
<p>通过在<code>@RestControllerAdvice</code> 中定义全局的切面处理。</p>
<p>通过<code>@ExceptionHandler</code> 来处理指定异常的处理方式。</p>
<p>这里返回的格式就是我们自定义的<code>ErrorMsg</code>格式。我们通过自定义这个<code>ErroMsg</code>完成和接口使用者协议，完成对业务异常的处理。</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestControllerAdvice</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalExceptionHandler</span> </span>{
    <span class="hljs-meta">@ExceptionHandler(PersonNotFoundException.class)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;ErrorMsg&gt; <span class="hljs-title">personNotFoundHandler</span><span class="hljs-params">(PersonNotFoundException e)</span> </span>{
        ErrorMsg msg = <span class="hljs-keyword">new</span> ErrorMsg(<span class="hljs-string">"0000"</span>, e.getMessage());
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ResponseEntity&lt;&gt;(msg, e.getStatus());
    }
}
</code></pre>
<ul>
<li>需自定义的错误返回</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@Data</span>
<span class="hljs-meta">@AllArgsConstructor</span>
<span class="hljs-meta">@NoArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ErrorMsg</span> </span>{
    <span class="hljs-keyword">private</span> String code;
    <span class="hljs-keyword">private</span> String msg;
}
</code></pre>
<ul>
<li>测试控制器</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonController</span> </span>{

    <span class="hljs-meta">@GetMapping("/getPerson")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getPerson</span><span class="hljs-params">()</span></span>{
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> PersonNotFoundException(<span class="hljs-string">"查找的人不存在"</span>, HttpStatus.NOT_FOUND);
    }
}
</code></pre>
<ul>
<li>启动程序，访问：http://localhost:8080/getPerson</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666920042624/DuU8hzYny.png" alt="image.png" /></p>
<h2 id="heading-2">2、基于“问题细节”的业务异常处理</h2>
<p>基于我们常规的异常处理，其实已经能满足我们的业务需求，使用RFC 7807规范，我们可以免去自定义的异常错误格式（<code>ErrorMsg</code> ），使用Spring 6.0给我们提供的<code>ProblemDetail</code> ，这样我们以后再无需自己自定义异常返回格式，且在不同的项目之间有了标准，从而客户端在使用的时候有了可预测性。</p>
<p>Spring 6.0的做法也很简单，我们只需要将我们自定返回的<code>ErrorMsg</code> 修改成<code>ProblemDetail</code> 即可。我们看一下示例代码：</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestControllerAdvice</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalExceptionHandler</span> </span>{

    <span class="hljs-meta">@ExceptionHandler(PersonNotFoundException.class)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ProblemDetail <span class="hljs-title">personNotFoundHandler</span><span class="hljs-params">(PersonNotFoundException e)</span> </span>{
      ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.NOT_FOUND); 
      problemDetail.setType(URI.create(<span class="hljs-string">"https://www.toutiao.com/c/user/token/MS4wLjABAAAAJxW0bvHKNNwIpcsocIDAjNHHNXg2yaj1upViHO2JVNw/"</span>));
      problemDetail.setTitle(<span class="hljs-string">"Person Not Found"</span>);
      problemDetail.setDetail(String.format(<span class="hljs-string">"错误信息：‘%s’"</span>, e.getMessage()));
      <span class="hljs-keyword">return</span> problemDetail;
    }
}
</code></pre>
<p>值得说的是<code>ProblemDetail</code> 还支持设置一个<code>Map</code>的<code>properties</code>：</p>
<pre><code class="lang-java"><span class="hljs-keyword">private</span> Map&lt;String, Object&gt; properties;
</code></pre>
<p>这样也为我们的定制扩展提供了更大的空间。</p>
<p>启动，访问：
http://localhost:8080/getPerson，其实的错误返回符合RFC 7807规范。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666920004825/9aQ9Fx8j8.png" alt="image.png" /></p>
<p>注意查看返回的头信息，我们看到了，返回数据的媒体类型为：application/problem+json：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666919995781/tMBzjaeZ-.png" alt="image.png" /></p>
<p>我会持续关注Spring Boot 6.0和Spring Boot 3.0的最新的特性相关信息，并发布相关的文章，请保持关注。</p>
<p>感谢支持我的书：《从企业级开发到云原生微服务:Spring Boot实战》</p>
<p>头条原文地址：<a target="_blank" href="https://www.toutiao.com/article/7158705923182182927/">https://www.toutiao.com/article/7158705923182182927/</a></p>
<p>参考资料：<a target="_blank" href="https://docs.spring.io/spring-framework/docs/6.0.0-RC2/javadoc-api/org/springframework/http/ProblemDetail.html">https://docs.spring.io/spring-framework/docs/6.0.0-RC2/javadoc-api/org/springframework/http/ProblemDetail.html</a></p>
]]></content:encoded></item><item><title><![CDATA[Spring Boot 3.0第一个候选发布版来了，正式版11月报到]]></title><description><![CDATA[在2022.10.12日，Spring发布了6.0的第一个候选发布版。在2022.10.20日，Spring发布了6.0的第二个候选发布版。
Spring 6.0最大的更新有：

Java 17作为最低版本
迁移到Jakarta EE 9+（Tomcat 10/Jetty 11，最新的hibernate 6.1）
AOT（Ahead-Of-Time ）

在2022.10.20日的下午，Spring Boot 3.0的第一个候选发布。我们可以从
https://repo.spring.io/mi...]]></description><link>https://wisely.top/spring-boot-3-coming-in-november</link><guid isPermaLink="true">https://wisely.top/spring-boot-3-coming-in-november</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Tue, 25 Oct 2022 02:59:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666666470888/swKwvKxtG.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在2022.10.12日，Spring发布了6.0的第一个候选发布版。在2022.10.20日，Spring发布了6.0的第二个候选发布版。</p>
<p>Spring 6.0最大的更新有：</p>
<ol>
<li>Java 17作为最低版本</li>
<li>迁移到Jakarta EE 9+（Tomcat 10/Jetty 11，最新的hibernate 6.1）</li>
<li>AOT（Ahead-Of-Time ）</li>
</ol>
<p>在2022.10.20日的下午，Spring Boot 3.0的第一个候选发布。我们可以从
<a target="_blank" href="https://repo.spring.io/milestone">https://repo.spring.io/milestone</a> 里使用Spring Boot 3.0 RC1的依赖了。预计Spring 6.0和Spring Boot 3.0都会在下个月也就是11月发布正式版。</p>
<p>这是Spring Boot 3.0的第一个候选版本，预计将于2022年11月24日发布正式版本。现在不会再添加更多的特性，只会在发现问题时进行API更改。我们选择大可以放心的去学习使用Spring Boot 3.0了！</p>
<p>此版本是11个月工作的结晶，是第一个完全实现在Spring Boot 3.0中提供的主题规划的版本。 最值得注意的是，此版本建立在多年研发工作：实验性Spring Native项目之上，旨在为 GraalVM 原生镜像（native image）提供支持。</p>
<p>现在，你可以使用标准的Spring Boot Maven或Gradle插件将Spring Boot应用程序转换为本地原生（native）可执行程序，不需要任何特殊配置。</p>
<p>这个发布的版本包含了新的文档部分，来介绍原生镜像（native image）相关的内容和概念，在此链接可以查看：<a target="_blank" href="https://docs.spring.io/spring-boot/docs/3.0.0-RC1/reference/html/native-image.html">https://docs.spring.io/spring-boot/docs/3.0.0-RC1/reference/html/native-image.html</a>。通过此文档，我们可以了解到Spring Boot 3.0的最大特性之一AOT的功能的使用。</p>
<p>和Spring 6.0一样，除了对GraalVM原生镜像的支持，该版本还完成了我们到JakartaEE 9的迁移和到Java 17的基线升级。</p>
<p>其它值得关注的RC1版本的新特性有：</p>
<ol>
<li><p>Spring Data JDBC更灵活的自动配置。</p>
</li>
<li><p>Prometheus Examplars的自动配置。</p>
</li>
<li><p>log4j2 增强功能包括：profile文件支持和环境属性查找。</p>
</li>
</ol>
<p>关于当前RC1版本和历史的里程碑版本的详细发布记录，可查看：<a target="_blank" href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0.0-RC1-Release-Notes">https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0.0-RC1-Release-Notes</a>。</p>
<p>我会持续关注Spring Boot 6.0和Spring Boot 3.0的最新的特性相关信息，请保持关注。</p>
<p>感谢支持我的书：《从企业级开发到云原生微服务:Spring Boot实战》</p>
<p>参考资料：</p>
<p><a target="_blank" href="https://spring.io/blog/2022/10/20/spring-framework-6-0-0-rc2-available-now">https://spring.io/blog/2022/10/20/spring-framework-6-0-0-rc2-available-now</a></p>
<p><a target="_blank" href="https://spring.io/blog/2022/10/12/spring-framework-6-0-goes-rc1">https://spring.io/blog/2022/10/12/spring-framework-6-0-goes-rc1</a></p>
<p><a target="_blank" href="https://spring.io/blog/2022/10/20/spring-boot-3-0-0-rc1-available-now">https://spring.io/blog/2022/10/20/spring-boot-3-0-0-rc1-available-now</a></p>
]]></content:encoded></item><item><title><![CDATA[如何通过互联网访问本地Spring Boot程序]]></title><description><![CDATA[当你需要别人远程访问你本地的Spring Boot的程序的时候，你可以通过Ngrok来帮助你来实现。
什么是Ngrok
Ngrok可以创建一个http隧道，并为您提供一个公共URL，重定向到本地机器上的指定端口。它是一个很棒的开发或者测试目的使用的工具。
Ngrok的官网地址是：https://ngrok.com 。

Ngrok Spring Boot Starter
Spring Boot的Web端口可以通过Ngrok Spring Boot Starter暴露到互联网。
Ngrok Spr...]]></description><link>https://wisely.top/spring-boot-internet-access-local</link><guid isPermaLink="true">https://wisely.top/spring-boot-internet-access-local</guid><category><![CDATA[Springboot]]></category><category><![CDATA[ngrok]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Thu, 20 Oct 2022 03:16:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235735407/TCmI1GA-3.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>当你需要别人远程访问你本地的Spring Boot的程序的时候，你可以通过Ngrok来帮助你来实现。</p>
<h2 id="heading-ngrok">什么是Ngrok</h2>
<p>Ngrok可以创建一个http隧道，并为您提供一个公共URL，重定向到本地机器上的指定端口。它是一个很棒的开发或者测试目的使用的工具。</p>
<p>Ngrok的官网地址是：<a target="_blank" href="https://ngrok.com">https://ngrok.com</a> 。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235327094/7fYvdt4eu.png" alt="image.png" /></p>
<h2 id="heading-ngrok-spring-boot-starter">Ngrok Spring Boot Starter</h2>
<p>Spring Boot的Web端口可以通过<strong>Ngrok Spring Boot Starter</strong>暴露到互联网。</p>
<p><strong>Ngrok Spring Boot Starter</strong>将会根据你的操作系统自动下载Ngrok的二进制文件并缓存到<code>home_directory/.ngrok2</code> 目录。</p>
<p>每次运行Spring Boot程序的时候，Ngrok会自动构建指向Spring Boot Web程序的http隧道。</p>
<p>该Starter的地址为：<a target="_blank" href="https://github.com/kilmajster/ngrok-spring-boot-starter">https://github.com/kilmajster/ngrok-spring-boot-starter</a></p>
<h2 id="heading-ngrok">注册Ngrok</h2>
<ul>
<li><p>注册Ngrok
访问Ngrok官网：https://ngrok.com/，注册Ngrok账号</p>
</li>
<li><p>获取Ngrok的认证码
访问：
https://dashboard.ngrok.com/get-started/your-authtoken ，即可看到authtoken。</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235396168/x-9Z1SSId.png" alt="image.png" /></p>
<h2 id="heading-spring-boot">Spring Boot 代码演示</h2>
<ul>
<li>新建演示项目</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235424763/PENGixIqC.png" alt="image.png" /></p>
<ul>
<li>简单的演示代码</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NgrokDemoApplication</span> </span>{

   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
      SpringApplication.run(NgrokDemoApplication.class, args);
   }

   <span class="hljs-meta">@GetMapping("/")</span>
   <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">index</span><span class="hljs-params">()</span></span>{
      <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello Ngrok!"</span>;
   }

}
</code></pre>
<ul>
<li>添加ngrok-spring-boot-starter依赖</li>
</ul>
<p>Gradle:</p>
<pre><code class="lang-groovy">implementation 'io.github.kilmajster:ngrok-spring-boot-starter:0.6.0'
</code></pre>
<p>或Maven：</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>io.github.kilmajster<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>ngrok-spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.6.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<ul>
<li>简单配置
在application.yaml 中:</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">ngrok:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 开启ngrok</span>
  <span class="hljs-attr">auth-token:</span>  <span class="hljs-string">------#</span> <span class="hljs-string">复制上面获取的authtoken到此处</span>
</code></pre>
<ul>
<li>启动程序</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235558143/BogwBy0V8.png" alt="image.png" /></p>
<p>通过Spring Boot控制台，你可以发现：</p>
<ol>
<li>启动时程序自动会自动下载ngrok并解压缓存到系统用户的.ngrok下。</li>
<li>启动ngrok，ngrok dashboard的地址是：http://127.0.0.1:4040</li>
</ol>
<ul>
<li>访问ngrok dashboard</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235606232/I4Tes90pJ.png" alt="image.png" /></p>
<p>你会发现ngrok为我们提供了两个公网访问的地址，分别是http和https的。</p>
<ul>
<li>访问ngrok提供的公网地址</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235622342/wJwfG4X1D.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235629313/7MHIbfR-U.png" alt="image.png" /></p>
<p>用手机访问：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235638955/RRRldv5A2.png" alt="image.png" /></p>
<p>ping 域名返回的也是公网的ip：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666235651583/HUizeP4bv.png" alt="image.png" /></p>
<p>更多关于如何使用的，请参考Starter的项目地址。</p>
<p>感谢支持我的书：《从企业级开发到云原生微服务:Spring Boot实战》</p>
<p>头条文章地址：<a target="_blank" href="https://www.toutiao.com/article/7080505822463214093/">https://www.toutiao.com/article/7080505822463214093/</a></p>
<p>参考资料：
https://github.com/kilmajster/ngrok-spring-boot-starter</p>
]]></content:encoded></item><item><title><![CDATA[Spring Boot下如何记录SQL语句、SQL参数、慢SQL日志]]></title><description><![CDATA[在我们程序在访问数据库，出现bug或者性能问题的时候，我们希望把SQL语句以及参数都打印出来，以便于我们定位bug和性能问题。
你可能会使用的方式
通常情况下，以使用Spring Data JPA和Hibernate为例（别走开，方案是和数据库访问技术无关的，理论上Mybatis，JDBC都可以使用），我们在application.yaml 里配置使用：
spring.jpa.show-sql: true

但这样的设置只能在开发测试环境里设置，因为使用此属性等同于使用System.out.pr...]]></description><link>https://wisely.top/spring-boot-sql-param-slow-sql</link><guid isPermaLink="true">https://wisely.top/spring-boot-sql-param-slow-sql</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Spring Data Jpa]]></category><category><![CDATA[hibernate]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Wed, 19 Oct 2022 01:20:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666142394457/XQaSSrv6d.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在我们程序在访问数据库，出现bug或者性能问题的时候，我们希望把SQL语句以及参数都打印出来，以便于我们定位bug和性能问题。</p>
<h2 id="heading-5l2g5yv6io95lya5l255so55qe5pa55byp">你可能会使用的方式</h2>
<p>通常情况下，以使用Spring Data JPA和Hibernate为例（别走开，方案是和数据库访问技术无关的，理论上Mybatis，JDBC都可以使用），我们在application.yaml 里配置使用：</p>
<pre><code class="lang-yaml"><span class="hljs-attr">spring.jpa.show-sql:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>但这样的设置只能在开发测试环境里设置，因为使用此属性等同于使用<code>System.out.println</code>打印SQL语句，这将会有性能的问题。而且也不能显示SQL的参数。</p>
<p>或者我们通过设置Hibernate属性在application.yaml配置：</p>
<pre><code class="lang-yaml"><span class="hljs-attr">logging.level.org.hibernate.SQL:</span> <span class="hljs-string">debug</span>
<span class="hljs-attr">logging.level.org.hibernate.type.descriptor.sql:</span> <span class="hljs-string">trace</span>
</code></pre>
<p>这个方案比上面的方案好一些，但是也有几个问题：</p>
<ol>
<li>在批处理情况，不清楚有多少语句实际发送到数据库服务器，因为日志消息是在准备阶段打印的，而不是在调用<code>executeBatch</code>方法时打印的。</li>
<li><code>org.hibernate.type.descriptor.sql</code>只能记录内置的Hibernate核心类型，如果你使用自定义的类型，将不会被打印。</li>
</ol>
<p>现在我们介绍一个开源项目，叫做：datasource-proxy，地址：
<a target="_blank" href="https://github.com/jdbc-observations/datasource-proxy">https://github.com/jdbc-observations/datasource-proxy</a> 。</p>
<p>它提供了一个JDBC的<code>DataSource</code> 的代理：<code>ProxyDataSource</code> ，这就意味着它可以用在任何在Spring Boot下的数据访问技术，如：JPA、Mybatis等。就算混合使用多种数据访问技术，datasource-proxy也能打印所有的通过JDBC连接的语句。</p>
<h2 id="heading-datasource-proxy">使用datasource-proxy</h2>
<ul>
<li>新建演示项目</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666142333107/I6DqWP-uT.png" alt="image.png" /></p>
<ul>
<li>添加相关依赖
我们可以直接添加datasource-proxy的依赖，然后自己通过ProxyDataSourceBuilder 来创建ProxyDataSource。不过我们并不需要这样做，因为已经有人给我们写好了datasource-proxy的Spring Boot Starter，我们直接使用这个starter就可以直接自动配置好datasource-proxy。starter的地址：
https://github.com/gavlyukovskiy/spring-boot-data-source-decorator 。</li>
</ul>
<p>Gradle</p>
<pre><code class="lang-groovy">implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.8.1'
</code></pre>
<p>或Maven</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.github.gavlyukovskiy<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>datasource-proxy-spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.8.1<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<ul>
<li>简单的数据访问演示类</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@Data</span>
<span class="hljs-meta">@Entity</span>
<span class="hljs-meta">@AllArgsConstructor</span>
<span class="hljs-meta">@NoArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{

    <span class="hljs-meta">@Id</span>
    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span>
    <span class="hljs-keyword">private</span> Long id;
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> Integer age;
}
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">PersonRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span> &lt;<span class="hljs-title">Person</span>, <span class="hljs-title">Long</span>&gt; </span>{
    <span class="hljs-function">List&lt;Person&gt; <span class="hljs-title">findByNameStartsWith</span><span class="hljs-params">(String name)</span></span>;
}
</code></pre>
<ul>
<li>简单的数据访问查询测试</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoggingSqlApplication</span> </span>{

   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
      SpringApplication.run(LoggingSqlApplication.class, args);
   }

   <span class="hljs-meta">@Bean</span>
   <span class="hljs-function">CommandLineRunner <span class="hljs-title">commandLineRunner</span><span class="hljs-params">(PersonRepository personRepository)</span></span>{
      <span class="hljs-keyword">return</span> args -&gt; {
         personRepository.save(<span class="hljs-keyword">new</span> Person(<span class="hljs-keyword">null</span>,<span class="hljs-string">"wiselyman001"</span>, <span class="hljs-number">18</span>));
         personRepository.save(<span class="hljs-keyword">new</span> Person(<span class="hljs-keyword">null</span>,<span class="hljs-string">"wiselyman002"</span>, <span class="hljs-number">18</span>));
         personRepository.save(<span class="hljs-keyword">new</span> Person(<span class="hljs-keyword">null</span>,<span class="hljs-string">"wiselyman003"</span>, <span class="hljs-number">18</span>));
         personRepository.save(<span class="hljs-keyword">new</span> Person(<span class="hljs-keyword">null</span>,<span class="hljs-string">"wiselyman004"</span>, <span class="hljs-number">18</span>));
         personRepository.findByNameStartsWith(<span class="hljs-string">"wiselyman"</span>);
      };
   }
}
</code></pre>
<ul>
<li>datasource-proxy的核心配置</li>
</ul>
<p>datasource-proxy-spring-boot-starter 为我们提供了一个<code>DataSourceProxyProperties</code>来配置<code>DataSourceProxy</code> ，我们可以通过application.yaml 来配置。如：</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># 设置日志库，默认为slf4j(slf4j, jul, common, sysout)</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.logging:</span> <span class="hljs-string">slf4j</span>

<span class="hljs-comment"># 开启所有的查询到日志，默认为true</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.query.enable-logging:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.query.log-level:</span> <span class="hljs-string">debug</span>
<span class="hljs-comment"># 日志名称设置</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.query.logger-name:</span>

<span class="hljs-comment"># 设置慢SQL的情况，慢SQL的日志级别是WARN</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.slow-query.enable-logging:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.slow-query.log-level:</span> <span class="hljs-string">warn</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.slow-query.logger-name:</span>
<span class="hljs-comment"># 设置被认为是慢sql的时间并用日志记录下来</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.slow-query.threshold:</span> <span class="hljs-number">300</span>

<span class="hljs-attr">decorator.datasource.datasource-proxy.multiline:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.json-format:</span> <span class="hljs-literal">false</span>
<span class="hljs-comment"># 开启查询指标</span>
<span class="hljs-attr">decorator.datasource.datasource-proxy.count-query:</span> <span class="hljs-literal">false</span>
</code></pre>
<p>上面是默认配置，若满足要求无需单独进行设置。</p>
<p>如我们没有特殊的定制，我们只需在application.yaml加上即可使用：</p>
<pre><code class="lang-yaml"><span class="hljs-attr">logging.level.net.ttddyy.dsproxy.listener:</span> <span class="hljs-string">debug</span>
</code></pre>
<p>运行程序</p>
<pre><code>Name:dataSource, <span class="hljs-attr">Connection</span>:<span class="hljs-number">3</span>, <span class="hljs-attr">Time</span>:<span class="hljs-number">36</span>, <span class="hljs-attr">Success</span>:True
<span class="hljs-attr">Type</span>:Prepared, <span class="hljs-attr">Batch</span>:False, <span class="hljs-attr">QuerySize</span>:<span class="hljs-number">1</span>, <span class="hljs-attr">BatchSize</span>:<span class="hljs-number">0</span>
<span class="hljs-attr">Query</span>:[<span class="hljs-string">"insert into person (age, name) values (?, ?)"</span>]
<span class="hljs-attr">Params</span>:[(<span class="hljs-number">18</span>,wiselyman001)]
<span class="hljs-number">2022</span><span class="hljs-number">-10</span><span class="hljs-number">-17</span> <span class="hljs-number">17</span>:<span class="hljs-number">13</span>:<span class="hljs-number">16.907</span> DEBUG <span class="hljs-number">12740</span> --- [           main] n.t.d.l.l.SLF4JQueryLoggingListener      : 
Name:dataSource, <span class="hljs-attr">Connection</span>:<span class="hljs-number">4</span>, <span class="hljs-attr">Time</span>:<span class="hljs-number">34</span>, <span class="hljs-attr">Success</span>:True
<span class="hljs-attr">Type</span>:Prepared, <span class="hljs-attr">Batch</span>:False, <span class="hljs-attr">QuerySize</span>:<span class="hljs-number">1</span>, <span class="hljs-attr">BatchSize</span>:<span class="hljs-number">0</span>
<span class="hljs-attr">Query</span>:[<span class="hljs-string">"insert into person (age, name) values (?, ?)"</span>]
<span class="hljs-attr">Params</span>:[(<span class="hljs-number">18</span>,wiselyman002)]
<span class="hljs-number">2022</span><span class="hljs-number">-10</span><span class="hljs-number">-17</span> <span class="hljs-number">17</span>:<span class="hljs-number">13</span>:<span class="hljs-number">17.041</span> DEBUG <span class="hljs-number">12740</span> --- [           main] n.t.d.l.l.SLF4JQueryLoggingListener      : 
Name:dataSource, <span class="hljs-attr">Connection</span>:<span class="hljs-number">5</span>, <span class="hljs-attr">Time</span>:<span class="hljs-number">33</span>, <span class="hljs-attr">Success</span>:True
<span class="hljs-attr">Type</span>:Prepared, <span class="hljs-attr">Batch</span>:False, <span class="hljs-attr">QuerySize</span>:<span class="hljs-number">1</span>, <span class="hljs-attr">BatchSize</span>:<span class="hljs-number">0</span>
<span class="hljs-attr">Query</span>:[<span class="hljs-string">"insert into person (age, name) values (?, ?)"</span>]
<span class="hljs-attr">Params</span>:[(<span class="hljs-number">18</span>,wiselyman003)]
<span class="hljs-number">2022</span><span class="hljs-number">-10</span><span class="hljs-number">-17</span> <span class="hljs-number">17</span>:<span class="hljs-number">13</span>:<span class="hljs-number">17.183</span> DEBUG <span class="hljs-number">12740</span> --- [           main] n.t.d.l.l.SLF4JQueryLoggingListener      : 
Name:dataSource, <span class="hljs-attr">Connection</span>:<span class="hljs-number">6</span>, <span class="hljs-attr">Time</span>:<span class="hljs-number">36</span>, <span class="hljs-attr">Success</span>:True
<span class="hljs-attr">Type</span>:Prepared, <span class="hljs-attr">Batch</span>:False, <span class="hljs-attr">QuerySize</span>:<span class="hljs-number">1</span>, <span class="hljs-attr">BatchSize</span>:<span class="hljs-number">0</span>
<span class="hljs-attr">Query</span>:[<span class="hljs-string">"insert into person (age, name) values (?, ?)"</span>]
<span class="hljs-attr">Params</span>:[(<span class="hljs-number">18</span>,wiselyman004)]
<span class="hljs-number">2022</span><span class="hljs-number">-10</span><span class="hljs-number">-17</span> <span class="hljs-number">17</span>:<span class="hljs-number">13</span>:<span class="hljs-number">17.407</span> DEBUG <span class="hljs-number">12740</span> --- [           main] n.t.d.l.l.SLF4JQueryLoggingListener      : 
Name:dataSource, <span class="hljs-attr">Connection</span>:<span class="hljs-number">7</span>, <span class="hljs-attr">Time</span>:<span class="hljs-number">37</span>, <span class="hljs-attr">Success</span>:True
<span class="hljs-attr">Type</span>:Prepared, <span class="hljs-attr">Batch</span>:False, <span class="hljs-attr">QuerySize</span>:<span class="hljs-number">1</span>, <span class="hljs-attr">BatchSize</span>:<span class="hljs-number">0</span>
<span class="hljs-attr">Query</span>:[<span class="hljs-string">"select person0_.id as id1_0_, person0_.age as age2_0_, person0_.name as name3_0_ from person person0_ where person0_.name like ? escape ?"</span>]
<span class="hljs-attr">Params</span>:[(wiselyman%,\)]
</code></pre><p>感谢支持我的书：《从企业级开发到云原生微服务:Spring Boot实战》</p>
<p>头条原文地址：<a target="_blank" href="https://www.toutiao.com/article/7153135743584551438/">https://www.toutiao.com/article/7153135743584551438/</a></p>
<p>参考资料：</p>
<p><a target="_blank" href="https://github.com/jdbc-observations/datasource-proxy">https://github.com/jdbc-observations/datasource-proxy</a></p>
<p><a target="_blank" href="https://github.com/gavlyukovskiy/spring-boot-data-source-decorator">https://github.com/gavlyukovskiy/spring-boot-data-source-decorator</a></p>
<p><a target="_blank" href="https://vladmihalcea.com/the-best-way-to-log-jdbc-statements/">https://vladmihalcea.com/the-best-way-to-log-jdbc-statements/</a></p>
<p><a target="_blank" href="https://vladmihalcea.com/log-sql-spring-boot/">https://vladmihalcea.com/log-sql-spring-boot/</a></p>
]]></content:encoded></item><item><title><![CDATA[Spring Boot 2.7新特性：@JsonMixin]]></title><description><![CDATA[在我们普通的应用中，如果我们要定制一个类对象的Json输出的话，我们可以轻松的通过Jackson提供的注解如：@JsonProperties等注解在类上轻松实现对Json输出的定制。
但也存在着这样的Java类对象的源码不受控制的情况：
1、Java类在第三方的类库中，你无法修改；
2、不想修改已有的类的代码，因当前业务和已有业务在不同的模块中。
Jackson为我们mixin来解决这个问题，在不修改已有的Java类库的情况下，定制Json的输出。Spring Boot 2.7为我们提供了@Js...]]></description><link>https://wisely.top/spring-boot-27jsonmixin</link><guid isPermaLink="true">https://wisely.top/spring-boot-27jsonmixin</guid><category><![CDATA[jsonmixin]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[jackson]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Sun, 16 Oct 2022 09:25:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665911133468/X4ZjHQBhy.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在我们普通的应用中，如果我们要定制一个类对象的Json输出的话，我们可以轻松的通过Jackson提供的注解如：<code>@JsonProperties</code>等注解在类上轻松实现对Json输出的定制。</p>
<p>但也存在着这样的Java类对象的源码不受控制的情况：</p>
<p>1、Java类在第三方的类库中，你无法修改；</p>
<p>2、不想修改已有的类的代码，因当前业务和已有业务在不同的模块中。</p>
<p>Jackson为我们mixin来解决这个问题，在不修改已有的Java类库的情况下，定制Json的输出。Spring Boot 2.7为我们提供了<code>@JsonMixin</code>注解来快速注册mixin。</p>
<h2 id="heading-1">1、演示项目</h2>
<p>打开https://start.spring.io，Spring Boot版本选择2.7.x依赖选择“Spring Web”和“Lombok”。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665911237395/UhSHNQd0x.png" alt="image.png" /></p>
<h2 id="heading-2">2、演示代码</h2>
<p>需要定制输出的类：</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> lombok.AllArgsConstructor;
<span class="hljs-keyword">import</span> lombok.Data;

<span class="hljs-meta">@Data</span>
<span class="hljs-meta">@AllArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> Integer age;
}
</code></pre>
<p>演示用控制器：</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonController</span> </span>{
    <span class="hljs-meta">@GetMapping("/getDemoPerson")</span>
    <span class="hljs-function">Person <span class="hljs-title">getDemoPerson</span><span class="hljs-params">()</span></span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Person(<span class="hljs-string">"wiselyman"</span>,<span class="hljs-number">18</span>);
    }
}
</code></pre>
<p>运行访问：
http://localhost:8080/getDemoPerson</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665911334782/MmdifssEC.png" alt="image.png" /></p>
<p>下面我们需要将“name”输出修改为“fullName”，我们先用最简单的常规实现，这意味着我们能直接修改“Person”类的源码。</p>
<h2 id="heading-3">3、可控时的实现</h2>
<p>在可修改源码的情况下，定制输出是很简单的：</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonProperty;

<span class="hljs-meta">@Data</span>
<span class="hljs-meta">@AllArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{
    <span class="hljs-meta">@JsonProperty("fullName")</span>
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> Integer age;
}
</code></pre>
<p>运行访问：
http://localhost:8080/getDemoPerson</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665911426830/UJSSBt8ZF.png" alt="image.png" /></p>
<h2 id="heading-4mixin">4、不可控时mixin的实现</h2>
<p>我们通过定义一下个抽象类来实现mixin的功能，然后通过Spring Boot的<code>@JsonMixin</code>注解将这个抽象类注册到自动配置的<code>ObjectMapper</code>。</p>
<p>首先我们的<code>Person</code>类恢复原样。</p>
<pre><code class="lang-java"><span class="hljs-meta">@Data</span>
<span class="hljs-meta">@AllArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> Integer age;
}
</code></pre>
<p>定义一个叫抽象类<code>FullNameMixin</code>，将<code>FullNameMixin</code>附加到目标类<code>Person</code>：</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonProperty;
<span class="hljs-keyword">import</span> org.springframework.boot.jackson.JsonMixin;

<span class="hljs-meta">@JsonMixin(Person.class)</span> <span class="hljs-comment">//1</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FullNameMixin</span> </span>{
    <span class="hljs-meta">@JsonProperty("fullName")</span> <span class="hljs-comment">//2</span>
    String name; <span class="hljs-comment">//3</span>
}
</code></pre>
<ol>
<li>Spring Boot的Jackson自动配置将扫描应用程序的包以查找带有“@JsonMixin”注释的类，并将它们注册到自动配置的“ObjectMapper”。 注册动作由Spring Boot的“JsonMixinModule”执行。“Person.class”是被附加mixin的目标类。</li>
<li>被定制的新名称。</li>
<li>被定制的原属性。</li>
</ol>
<p>运行访问：
http://localhost:8080/getDemoPerson</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665911625494/eDjPhbfUo.png" alt="image.png" /></p>
]]></content:encoded></item><item><title><![CDATA[Spring 6.0新特性都有哪些]]></title><description><![CDATA[Spring 6.x都有哪些新特性呢，我们在本文快速地罗列一下：
JDK 17+和Jakarta 9+基线

整个框架的代码基于Java 17源码级别。
Servlet、JPA等从"javax"迁移到"jakarta"命名空间。
兼容最新一代的web容器：Tomcat 10，Jetty 11.
JDK 19的“虚拟线程”的早期兼容。一般核心修订

升级到ASM 9.4和Kotlin 1.7。
完成CGLIB分支，支持捕捉CGLIB生成的类。
AOT（Ahead-Of-Time）转换的全面基础。
...]]></description><link>https://wisely.top/spring-6-new-features</link><guid isPermaLink="true">https://wisely.top/spring-6-new-features</guid><category><![CDATA[spring framework 6]]></category><category><![CDATA[Spring framework]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Sun, 16 Oct 2022 09:01:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665910736232/UjULv3xSU.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Spring 6.x都有哪些新特性呢，我们在本文快速地罗列一下：</p>
<h2 id="heading-jdk-17jakarta-9">JDK 17+和Jakarta 9+基线</h2>
<ol>
<li>整个框架的代码基于Java 17源码级别。</li>
<li>Servlet、JPA等从"javax"迁移到"jakarta"命名空间。</li>
<li>兼容最新一代的web容器：Tomcat 10，Jetty 11.</li>
<li>JDK 19的“虚拟线程”的早期兼容。<h2 id="heading-5lia6iis5qc45bd5lu6k6i">一般核心修订</h2>
</li>
<li>升级到ASM 9.4和Kotlin 1.7。</li>
<li>完成CGLIB分支，支持捕捉CGLIB生成的类。</li>
<li>AOT（Ahead-Of-Time）转换的全面基础。</li>
<li>对GraalVM原生镜像第一级别的支持。<h2 id="heading-5qc45bd5a655zmo">核心容器</h2>
</li>
<li><code>GenericApplicationContext</code>(<code>refreshForAotProcessing</code>)中对AOT处理的支持。</li>
<li>基于预先解析构造器和工厂方法的bean的定义和转换。</li>
<li>为AOP代理和配置类提供早期代理类确定的支持。</li>
<li><code>PathMatchingResourcePatternResolver</code>使用NIO和module path API来扫描。<h2 id="heading-5pww5o2u6k66zeu5zkm5lql5yqh">数据访问和事务</h2>
</li>
<li>支持预先确定 JPA 管理类型（用于包含在 AOT 处理中）。</li>
<li>JPA支持Hibernate 6.1（保持Hibernate 5.6）的支持。</li>
<li>升级到R2DBC 1.0（响应式编程的数据库驱动，包含R2DBC事务定义）。</li>
<li>移除JCA CCI的支持。<h2 id="heading-spring">Spring消息</h2>
</li>
<li>RSocket接口客户端基于<code>@RSocketExchange</code>服务接口。</li>
<li>基于Netty 5 alpha 的Reactor Netty 2的早期支持。<h2 id="heading-web">一般Web修订</h2>
</li>
<li>HTTP接口客户端基于<code>@HttpExchange</code>服务接口。</li>
<li>支持RFC 7807问题<a target="_blank" href="https://docs.spring.io/spring-framework/docs/6.0.0-RC1/reference/html/web.html#mvc-ann-rest-exceptions">https://docs.spring.io/spring-framework/docs/6.0.0-RC1/reference/html/web.html#mvc-ann-rest-exceptions</a>。</li>
<li>统一的HTTP状态码的处理。</li>
<li>为RestTemplate提供基于Micrometer的观测性支持。<h2 id="heading-spring-mvc">Spring MVC</h2>
</li>
<li>默认使用<code>PathPatternParser</code>（可以选择<code>PathMatcher</code>）</li>
<li>为控制器方法返回的Flux和Mono返回值，提供与Micrometer上下文传播集成。</li>
<li>移除过时的Tiles和FreeMarker JSP的支持。<h2 id="heading-spring-webflux">Spring WebFlux</h2>
</li>
<li>使用新的PartEvent API用于form文件上传。</li>
<li>添加<code>ResponseEntityExceptionHandler</code>定制WebFlux一场和渲染RFC 7807错误响应。</li>
<li>用于非流的媒体类型的Flux返回值。</li>
<li>基于Netty 5 alpha 的Reactor Netty 2的早期支持。</li>
<li>JDK HttpClient与WebClient 集成。</li>
<li>为WebClient提供基于Micrometer的观测性支持。<h2 id="heading-5rwl6kv">测试</h2>
</li>
<li>支持测试AOT处理的application context。</li>
<li>与HtmlUnit 2.64请求参数处理集成。</li>
</ol>
<p>我会陆续更新关于Spring 6.0和Spring Boot 3.0新特性的文章，请保持关注，感谢！</p>
<p>参考资料：
<a target="_blank" href="https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-6.x/">https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-6.x/</a></p>
]]></content:encoded></item><item><title><![CDATA[Spring Boot下如何实现数据库的多租户]]></title><description><![CDATA[在我们常规开发的SaaS平台中，我们有个很常用的需求是通过多租户将不同用户之间的数据和资源隔离开。
通常情况下，多租户有三种形式：
1、分区（Partitioned）数据：不同租户的数据都在一张表里，通过一个值（tenantId）来区分不同的租户。

2、分结构（Schema）：不同的租户数据放置在相同数据库实例的不同结构（Schema）中。

3、分数据库（Database）：不同租户的数据放置在不同的数据中。

在Spring Boot中，多租户的能力是由Hibernate提供的，我们在本文...]]></description><link>https://wisely.top/spring-boot-multitenant</link><guid isPermaLink="true">https://wisely.top/spring-boot-multitenant</guid><category><![CDATA[multitenant]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[hibernate]]></category><category><![CDATA[Spring Data Jpa]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Sun, 16 Oct 2022 08:45:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665909867738/HNZ3SKnJ0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在我们常规开发的SaaS平台中，我们有个很常用的需求是通过多租户将不同用户之间的数据和资源隔离开。</p>
<p>通常情况下，多租户有三种形式：</p>
<p>1、<strong>分区（Partitioned）数据</strong>：不同租户的数据都在一张表里，通过一个值（tenantId）来区分不同的租户。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665205947973/c_gjZeGGA.webp" alt="shareddatabase.webp" /></p>
<p>2、<strong>分结构（Schema）</strong>：不同的租户数据放置在相同数据库实例的不同结构（Schema）中。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665205971591/OVJl9_FUo.webp" alt="separate_schema.webp" /></p>
<p>3、<strong>分数据库（Database）</strong>：不同租户的数据放置在不同的数据中。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665206015161/VHihGCURo.webp" alt="database_per_tenant.webp" /></p>
<p>在Spring Boot中，多租户的能力是由Hibernate提供的，我们在本文中结合Spring Data JPA一起对三种多租户的模式进行演示。本文不需要你具备任何Spring Data JPA和Hibernate基础，也可以通过本文领略下Spring Data JPA。</p>
<p>多租户最最简单也是最常用的多租户方式是“分区数据“”，而这个功能是Hibernate 6.0才具有的功能，而Spring Boot 2.x只支持Hibernate 5.x，所以使用“分区数据”的方式进行多租户需要采用Spring Boot 3.x。幸运的是Spring Boot 3.0将于今年11月份发布，到时候你就可以在生产环境使用本功能了。</p>
<p>“分结构”和“分数据库”的实现方式Spring Boot 2.x是支持的，本文为了演示简单以及远期的前瞻性，将全部以Spring Boot 3.0来实现。</p>
<p>首先，我们从最简单的开始。</p>
<h2 id="heading-1">1、“分区数据”多租户</h2>
<ul>
<li>新建演示项目：<code>spring-boot-multitenant-partition</code>,依赖为<code>Spring Web</code>、<code>Spring Data JPA</code>、<code>Lombok</code>，Spring Boot版本注意选择3.x，Spring Boot 3.x的最小支持JDK版本为17。</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665209347612/UEOLyM5z_.png" alt="image.png" /></p>
<ul>
<li>演示实体：<pre><code class="lang-java"><span class="hljs-meta">@Entity</span> <span class="hljs-comment">//1</span>
<span class="hljs-meta">@Data</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{
  <span class="hljs-meta">@Id</span> <span class="hljs-comment">//2</span>
  <span class="hljs-meta">@GeneratedValue(strategy= GenerationType.IDENTITY)</span> <span class="hljs-comment">//3</span>
  <span class="hljs-keyword">private</span> Long id;
  <span class="hljs-meta">@TenantId</span> <span class="hljs-comment">//4</span>
  <span class="hljs-keyword">private</span> String tenantId;
  <span class="hljs-keyword">private</span> String name;
  <span class="hljs-keyword">private</span> Integer age;
}
</code></pre>
<blockquote>
<p>1、通过<code>@Entity</code>注解定义一个实体，对应数据库一张表；<br />
2、通过<code>@Id</code>注解表名该属性对应数据库的主键；<br />
3、通过<code>@GeneratedValue(strategy= GenerationType.IDENTITY)</code>配置使用MySQL的主键自增；<br />
4、使用<code>@TenantId</code>注解的属性<code>tenantId</code>作为<strong>分区数据</strong>多租户的的区分标识。</p>
</blockquote>
</li>
</ul>
<ul>
<li>演示数据访问</li>
</ul>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.data.jpa.repository.JpaRepository;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">PersonRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">Person</span>,<span class="hljs-title">Long</span>&gt; </span>{
    <span class="hljs-function">List&lt;Person&gt; <span class="hljs-title">findByName</span><span class="hljs-params">(String name)</span></span>;
}
</code></pre>
<blockquote>
<p>这个是Spring Data JPA神奇的地方，通过一个定义一个接口<code>PersonRepository</code>继承框架提供的<code>JpaRepository</code>接口，框架将会给我们自动代理一个实现类，这个实现类除了基本的增删查改以外，还会通过方法名自动推算查询语句。如<code>findByName</code>相当于<code>select * from person where name = ?</code></p>
</blockquote>
<ul>
<li>演示如何确定TenantId的来源</li>
</ul>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.hibernate.cfg.AvailableSettings;
<span class="hljs-keyword">import</span> org.hibernate.context.spi.CurrentTenantIdentifierResolver;
<span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;

<span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WiselyTenantIdResolver</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">CurrentTenantIdentifierResolver</span>, //1
                                                <span class="hljs-title">HibernatePropertiesCustomizer</span>  </span>{ <span class="hljs-comment">//2</span>

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> ThreadLocal&lt;String&gt; CURRENT_TENANT = <span class="hljs-keyword">new</span> ThreadLocal&lt;&gt;(); <span class="hljs-comment">//1.1</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setCurrentTenant</span><span class="hljs-params">(String currentTenant)</span> </span>{ <span class="hljs-comment">//1.2</span>
        CURRENT_TENANT.set(currentTenant); 
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">resolveCurrentTenantIdentifier</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">//1</span>
        <span class="hljs-keyword">return</span> Optional.ofNullable(CURRENT_TENANT.get()).orElse(<span class="hljs-string">"unknown"</span>);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">validateExistingCurrentSessions</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">customize</span><span class="hljs-params">(Map&lt;String, Object&gt; hibernateProperties)</span> </span>{ <span class="hljs-comment">//2</span>
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, <span class="hljs-keyword">this</span>);
    }

}
</code></pre>
<blockquote>
<p>1、通过实现<code>CurrentTenantIdentifierResolver</code>接口来获取确定TenantId的来源。
  1.1、 使用线程本地变量<code>CURRENT_TENANT</code>来存储当前的TenantId；<br /> 
  1.2、通过<code>setCurrentTenant</code>方法接受外部设置当前访问者的TenantId，并存储在线程本地变量<code>CURRENT_TENANT</code>中；<br /> 
  1.3、通过重写接口的<code>resolveCurrentTenantIdentifier</code>方法，获得当前的TenantId；<br /> 
2、通过重写<code>HibernatePropertiesCustomizer</code>接口的<code>customize</code>方法，可以将当前类注册到Hibernate的配置。</p>
</blockquote>
<ul>
<li>通过请求头设置TenantId</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TenantIdInterceptor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">HandlerInterceptor</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> WiselyTenantIdResolver tenantIdResolver;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TenantIdInterceptor</span><span class="hljs-params">(WiselyTenantIdResolver tenantIdResolver)</span> </span>{
        <span class="hljs-keyword">this</span>.tenantIdResolver = tenantIdResolver;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">preHandle</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="hljs-keyword">throws</span> Exception </span>{
        tenantIdResolver.setCurrentTenant(request.getHeader(<span class="hljs-string">"x-tenant-id"</span>));
        <span class="hljs-keyword">return</span> HandlerInterceptor.<span class="hljs-keyword">super</span>.preHandle(request, response, handler);
    }
}
</code></pre>
<blockquote>
<p>通过定义一个Spring MVC的拦截器，在每个request请求的头部设置key：<code>x-tenant-id</code>（如：companya），在拦截器中获取到TenantId后设置<code>WiselyTenantIdResolver</code>的TenantId，即当前的TenantId。</p>
</blockquote>
<ul>
<li>注册此拦截器</li>
</ul>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.context.annotation.Configuration;
<span class="hljs-keyword">import</span> org.springframework.web.servlet.config.annotation.InterceptorRegistry;
<span class="hljs-keyword">import</span> org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

<span class="hljs-meta">@Configuration</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WebConfig</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">WebMvcConfigurer</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> TenantIdInterceptor tenantIdInterceptor;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">WebConfig</span><span class="hljs-params">(TenantIdInterceptor tenantIdInterceptor)</span> </span>{
        <span class="hljs-keyword">this</span>.tenantIdInterceptor = tenantIdInterceptor;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addInterceptors</span><span class="hljs-params">(InterceptorRegistry registry)</span> </span>{
        registry.addInterceptor(tenantIdInterceptor);
        WebMvcConfigurer.<span class="hljs-keyword">super</span>.addInterceptors(registry);
    }
}
</code></pre>
<ul>
<li>演示控制器</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping("/people")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> PersonRepository personRepository;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PersonController</span><span class="hljs-params">(PersonRepository personRepository)</span> </span>{
        <span class="hljs-keyword">this</span>.personRepository = personRepository;
    }


    <span class="hljs-meta">@PostMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Person <span class="hljs-title">save</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> PersonDto personDto)</span></span>{ <span class="hljs-comment">//1</span>
        <span class="hljs-keyword">return</span>  personRepository.save(personDto.createPerson());
    }

    <span class="hljs-meta">@GetMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> List&lt;Person&gt; <span class="hljs-title">all</span><span class="hljs-params">()</span></span>{ <span class="hljs-comment">//2</span>
        <span class="hljs-keyword">return</span> personRepository.findAll();
    }
}
</code></pre>
<blockquote>
<p>1、通过设置在头信息中设置不同的TenantId，数据库中的<code>tenant_id</code>字段将自动存储头中的租户；<br />
2、通过设置在头信息中设置不同的TenantId，只能查询到该租户下的数据。</p>
</blockquote>
<ul>
<li>控制器所需要的DTO</li>
</ul>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> lombok.Value;

<span class="hljs-meta">@Value</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonDto</span> </span>{
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> Integer age;
    <span class="hljs-function"><span class="hljs-keyword">public</span> Person <span class="hljs-title">createPerson</span><span class="hljs-params">()</span></span>{
        Person person = <span class="hljs-keyword">new</span> Person();
        person.setName(<span class="hljs-keyword">this</span>.name);
        person.setAge(<span class="hljs-keyword">this</span>.age);
        <span class="hljs-keyword">return</span> person;
    }
}
</code></pre>
<ul>
<li>配置：</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">spring.datasource.url:</span> <span class="hljs-string">jdbc:mysql://localhost:3306/partitioned</span> <span class="hljs-comment">#1</span>
<span class="hljs-attr">spring.datasource.username:</span> <span class="hljs-string">root</span>
<span class="hljs-attr">spring.datasource.password:</span> <span class="hljs-string">example</span>
<span class="hljs-attr">spring.datasource.driver-class-name:</span> <span class="hljs-string">com.mysql.cj.jdbc.Driver</span>
<span class="hljs-attr">spring.jpa.hibernate.ddl-auto:</span> <span class="hljs-string">update</span> <span class="hljs-comment"># 2</span>
<span class="hljs-attr">server.port:</span> <span class="hljs-number">80</span>
</code></pre>
<blockquote>
<p>1、在数据库中创建一个schema叫<code>partitioned</code>；<br />
2、设置为<code>update</code>属性，hibernate会自动因实体类的变化自动创建和更新数据库的表；</p>
</blockquote>
<ul>
<li><p>启动程序</p>
</li>
<li><p>测试需保存的数据</p>
</li>
</ul>
<p>通过postman构造四条数据，分别为：</p>
<p>租户：<code>companya</code>，通过<code>x-tenant-id</code>头来设置：</p>
<pre><code class="lang-javascript">{<span class="hljs-string">"name"</span>:<span class="hljs-string">"wang"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">22</span>}
{<span class="hljs-string">"name"</span>:<span class="hljs-string">"li"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">23</span>}
</code></pre>
<p>租户：<code>companya</code>，通过<code>x-tenant-id</code>头来设置：</p>
<pre><code class="lang-javascript">{<span class="hljs-string">"name"</span>:<span class="hljs-string">"peng"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">24</span>}
{<span class="hljs-string">"name"</span>:<span class="hljs-string">"zhang"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">25</span>}
</code></pre>
<ul>
<li>请求需保存的数据</li>
</ul>
<p>在postman的样子是这样的：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665584837578/h-Ope2t7-.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665584864964/NfdMe124a.png" alt="image.png" />
我们一次对上述四条数据进行请求，查看数据库：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665584980548/U7P2YCNO2.png" alt="image.png" /></p>
<p>我们看见数据都添加了正确的<code>tenant_id</code>。</p>
<ul>
<li>按租户查询数据</li>
</ul>
<p>查询租户<code>companya</code>的数据：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665585167473/eI4q8Cts6.png" alt="image.png" /></p>
<p>查询租户<code>companyb</code>的数据：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665585192956/pHDxlmTdD.png" alt="image.png" /></p>
<h2 id="heading-2">2、“分结构”多租户</h2>
<p>第二个例子，我们在一个数据库中分别建立2个schema，分别是<code>companya</code>,用来放置租户companya的数据；<code>companyb</code>用来放置租户companyb的数据。再建一个schema：<code>public</code>作为默认的连接的schema。</p>
<ul>
<li>演示实体</li>
</ul>
<pre><code>public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private <span class="hljs-built_in">String</span> name;
    private Integer age;
}
</code></pre><blockquote>
<p>无须标识租户的<code>tenantId</code>字段，因为租户已经通过schema来隔离开了。</p>
</blockquote>
<ul>
<li>如何获得当前租户id</li>
</ul>
<pre><code>@Component
public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WiselyTenantIdResolver</span> <span class="hljs-title">implements</span> <span class="hljs-title">CurrentTenantIdentifierResolver</span>, <span class="hljs-title">HibernatePropertiesCustomizer</span> </span>{
    private <span class="hljs-keyword">static</span> final ThreadLocal&lt;<span class="hljs-built_in">String</span>&gt; CURRENT_TENANT = <span class="hljs-keyword">new</span> ThreadLocal&lt;&gt;();
    public <span class="hljs-keyword">void</span> setCurrentTenant(<span class="hljs-built_in">String</span> currentTenant) {
        CURRENT_TENANT.set(currentTenant);
    }

    @Override
    public <span class="hljs-built_in">String</span> resolveCurrentTenantIdentifier() {
        <span class="hljs-keyword">return</span> Optional.ofNullable(CURRENT_TENANT.get()).orElse(<span class="hljs-string">"public"</span>);
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }

    @Override
    public <span class="hljs-keyword">void</span> customize(<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">Object</span>&gt; hibernateProperties) {
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, <span class="hljs-built_in">this</span>);
    }

}
</code></pre><blockquote>
<p>这里和上例没有什么区别，只是上例设置如果没有获取到TenantId，则将<code>tenant_id</code>字段设置为<code>unknown</code>,本例是将数据库的schema连到<code>public</code>。</p>
</blockquote>
<ul>
<li>通过获得tenantId，连接到对应的schema</li>
</ul>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.hibernate.cfg.AvailableSettings;
<span class="hljs-keyword">import</span> org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
<span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
<span class="hljs-keyword">import</span> javax.sql.DataSource;
<span class="hljs-keyword">import</span> java.sql.Connection;
<span class="hljs-keyword">import</span> java.sql.SQLException;



<span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WiselyMultiTenantConnectionProvider</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">MultiTenantConnectionProvider</span>, <span class="hljs-title">HibernatePropertiesCustomizer</span> </span>{ 
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> DataSource dataSource; 

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">WiselyMultiTenantConnectionProvider</span><span class="hljs-params">(DataSource dataSource)</span></span>{
        <span class="hljs-keyword">this</span>.dataSource = dataSource;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Connection <span class="hljs-title">getAnyConnection</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> SQLException </span>{
        <span class="hljs-keyword">return</span> dataSource.getConnection();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">releaseAnyConnection</span><span class="hljs-params">(Connection connection)</span> <span class="hljs-keyword">throws</span> SQLException </span>{
        connection.close();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Connection <span class="hljs-title">getConnection</span><span class="hljs-params">(String tenantIdentifier)</span> <span class="hljs-keyword">throws</span> SQLException </span>{
        <span class="hljs-keyword">final</span> Connection connection = getAnyConnection();
        connection.createStatement().execute(String.format(<span class="hljs-string">"use %s;"</span>, tenantIdentifier));
        <span class="hljs-keyword">return</span> connection;
    }


    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">releaseConnection</span><span class="hljs-params">(String tenantIdentifier, Connection connection)</span> <span class="hljs-keyword">throws</span> SQLException </span>{
        connection.createStatement().execute(<span class="hljs-string">"use public;"</span>);
        connection.close();
    }

<span class="hljs-comment">//...省略一些非关键方法</span>

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">customize</span><span class="hljs-params">(Map&lt;String, Object&gt; hibernateProperties)</span> </span>{
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, <span class="hljs-keyword">this</span>);
    }
}
</code></pre>
<blockquote>
<p>因为我们是连接单个数据库的不同schema，所以我们只需要在系统中配置一个<code>dataSource</code>，通过这个<code>dataSource</code>我们获得数据库的连接。<br />
通过重写<code>MultiTenantConnectionProvider</code>接口的<code>Connection getConnection(String tenantIdentifier)</code>方法，我们根据在上面的<code>WiselyTenantIdResolver</code>获得的<code>tenantId</code>，即当前方法的<code>tenantIdentifier</code>参数来切换<code>dataSource</code>连接到不同的schema.</p>
</blockquote>
<ul>
<li>配置</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">spring.datasource.url:</span> <span class="hljs-string">jdbc:mysql://127.0.0.1:3306/public</span>
<span class="hljs-attr">spring.datasource.username:</span> <span class="hljs-string">root</span>
<span class="hljs-attr">spring.datasource.password:</span> <span class="hljs-string">example</span>
<span class="hljs-attr">spring.datasource.driver-class-name:</span> <span class="hljs-string">com.mysql.cj.jdbc.Driver</span>
<span class="hljs-attr">spring.jpa.show-sql:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">server.port:</span> <span class="hljs-number">80</span>
</code></pre>
<ul>
<li><p>其余代码和上例保持一致，省略</p>
</li>
<li><p>测试数据和postman和上例保持一致，查看数据库中的数据：</p>
</li>
</ul>
<p>租户：companya</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665586424002/JDLeJrNrk.png" alt="image.png" /></p>
<p>租户：companyb</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665586521343/4BoMiC4oH.png" alt="image.png" /></p>
<p>查询租户：companya</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665586602215/Qo9TsvVfO.png" alt="image.png" /></p>
<p>查询租户：companyb</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665586572772/oX_pkE2Xz.png" alt="image.png" /></p>
<h2 id="heading-3">3、“分数据库”多租户</h2>
<p>第三个例子是不同租户的数据分别在不同的数据库里，为了演示方便，本例还是用2个schema来模拟两个数据库，区别是相同数据库时我们使用一个<code>dataSource</code>来切换不同的schema；而本例中会有多个<code>dataSource</code>，使用tenantId来切换到不同的<code>dataSource</code>。</p>
<p><code>spring-jdbc</code>包为我们提供一个类叫做<code>AbstractRoutingDataSource</code>,它可以设置多个数据源，并通过一个key来切换这个数据源，很显然，这个key是我们的tenantId。</p>
<ul>
<li>动态切换数据源</li>
</ul>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.boot.jdbc.DataSourceBuilder;
<span class="hljs-keyword">import</span> org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
<span class="hljs-keyword">import</span> javax.sql.DataSource;


<span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WiselyTenantRoutingDatasource</span>  <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractRoutingDataSource</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> WiselyTenantIdResolver wiselyTenantIdResolver; <span class="hljs-comment">//1</span>


     <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">WiselyTenantRoutingDatasource</span><span class="hljs-params">(WiselyTenantIdResolver wiselyTenantIdResolver)</span> </span>{
        <span class="hljs-keyword">this</span>.wiselyTenantIdResolver = wiselyTenantIdResolver;
        setDefaultTargetDataSource(createDatabase(<span class="hljs-string">"jdbc:mysql://127.0.0.1:3306/public"</span>, <span class="hljs-string">"root"</span>, <span class="hljs-string">"example"</span>)); <span class="hljs-comment">//2</span>
        HashMap&lt;Object, Object&gt; targetDataSources = <span class="hljs-keyword">new</span> HashMap&lt;&gt;(); <span class="hljs-comment">//3</span>
        targetDataSources.put(<span class="hljs-string">"companya"</span>,createDatabase(<span class="hljs-string">"jdbc:mysql://127.0.0.1:3306/companya"</span>, <span class="hljs-string">"root"</span>, <span class="hljs-string">"example"</span>)); <span class="hljs-comment">//4</span>
        targetDataSources.put(<span class="hljs-string">"companyb"</span>,createDatabase(<span class="hljs-string">"jdbc:mysql://127.0.0.1:3306/companyb"</span>, <span class="hljs-string">"root"</span>, <span class="hljs-string">"example"</span>));
        setTargetDataSources(targetDataSources); <span class="hljs-comment">//5</span>
    }


    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> String <span class="hljs-title">determineCurrentLookupKey</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">//6</span>
        <span class="hljs-keyword">return</span> wiselyTenantIdResolver.resolveCurrentTenantIdentifier();
    }


    <span class="hljs-function"><span class="hljs-keyword">private</span> DataSource <span class="hljs-title">createDatabase</span><span class="hljs-params">(String databaseUrl, String username, String password)</span> </span>{
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName(<span class="hljs-string">"com.mysql.cj.jdbc.Driver"</span>);
        dataSourceBuilder.url(databaseUrl);
        dataSourceBuilder.username(username);
        dataSourceBuilder.password(password);
        <span class="hljs-keyword">return</span> dataSourceBuilder.build();
    }
}
</code></pre>
<blockquote>
<p>1、注入<code>WiselyTenantIdResolver</code>的bean获得当前的tenantId；<br />
2、添加默认数据源到动态路由数据源里；<br />
3、定义一个<code>Map</code>，在里面存储不同租户的数据源；<br />
4、通过代码编程的方式构建一个数据源；<br />
5、讲这些数据源都添加到动态路由数据源里；<br />
6、通过从<code>WiselyTenantIdResolver</code>中拿到的TenantId，切换到不同的数据源。</p>
</blockquote>
<ul>
<li>在数据源被切换后，我们就可以轻松获得数据库的连接</li>
</ul>
<pre><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WiselyMultiTenantConnectionProvider</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">MultiTenantConnectionProvider</span>, <span class="hljs-title">HibernatePropertiesCustomizer</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> DataSource dataSource;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">WiselyMultiTenantConnectionProvider</span><span class="hljs-params">(DataSource dataSource)</span></span>{
        <span class="hljs-keyword">this</span>.dataSource = dataSource;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Connection <span class="hljs-title">getAnyConnection</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> SQLException </span>{
        <span class="hljs-keyword">return</span> dataSource.getConnection();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">releaseAnyConnection</span><span class="hljs-params">(Connection connection)</span> <span class="hljs-keyword">throws</span> SQLException </span>{
        connection.close();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Connection <span class="hljs-title">getConnection</span><span class="hljs-params">(String tenantIdentifier)</span> <span class="hljs-keyword">throws</span> SQLException </span>{
        <span class="hljs-keyword">return</span> dataSource.getConnection();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">releaseConnection</span><span class="hljs-params">(String tenantIdentifier, Connection connection)</span> <span class="hljs-keyword">throws</span> SQLException </span>{
        connection.close();
    }

  <span class="hljs-comment">// 省略不重要的方法</span>

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">customize</span><span class="hljs-params">(Map&lt;String, Object&gt; hibernateProperties)</span> </span>{
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, <span class="hljs-keyword">this</span>);
    }
}
</code></pre>
<blockquote>
<p>这里直接注入前面切换数据源后的得到的<code>dataSource</code>，从<code>dataSource</code>中得到数据库的连接</p>
</blockquote>
<ul>
<li><p>其余代码与上例保持一致，数据源是编程获得，所以无须在配置中配置。</p>
</li>
<li><p>演示效果和上例一致，再此就不做演示了</p>
</li>
</ul>
<h2 id="heading-4">4、源码地址</h2>
<p><a target="_blank" href="https://github.com/wiselyman/spring-boot-multitenant-partition">分区数据多租户</a></p>
<p><a target="_blank" href="https://github.com/wiselyman/spring-boot-multitenant-schema">分结构多租户</a></p>
<p><a target="_blank" href="https://github.com/wiselyman/spring-boot-multitenant-database">分数据库多租户</a></p>
<h2 id="heading-5">5、参考资料</h2>
<p><a target="_blank" href="https://spring.io/blog/2022/07/31/how-to-integrate-hibernates-multitenant-feature-with-spring-data-jpa-in-a-spring-boot-application">https://spring.io/blog/2022/07/31/how-to-integrate-hibernates-multitenant-feature-with-spring-data-jpa-in-a-spring-boot-application</a></p>
<p><a target="_blank" href="https://www.baeldung.com/hibernate-5-multitenancy">https://www.baeldung.com/hibernate-5-multitenancy</a></p>
<p><a target="_blank" href="https://www.baeldung.com/multitenancy-with-spring-data-jpa">https://www.baeldung.com/multitenancy-with-spring-data-jpa</a></p>
]]></content:encoded></item><item><title><![CDATA[Spring注解的内部工作原理]]></title><description><![CDATA[注解工作原理
注解本身只是元数据，即描述数据的数据，被描述的数据可以是类、方法、属性、参数、构造器等。注解仅是标记本身没有任何可执行的功能代码，但是我们只要标注了注解，我们就能得到想要的功能，这是怎么做到的呢？我们理解肯定是有某处代码根据我们标注的注解找到我们注解的类、方法、属性、参数等数据本身，并根据注解本身的含义以及注解属性里的内容执行功能代码。
第一种：BeanPostProcessor
BeanPostProcessor实现中有一类名称为*AnnotationBeanPostProces...]]></description><link>https://wisely.top/how-spring-annotations-work-inside</link><guid isPermaLink="true">https://wisely.top/how-spring-annotations-work-inside</guid><category><![CDATA[Spring framework]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Mon, 19 Sep 2022 02:59:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663556306274/jo--ymHpH.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-5roo6kej5bel5l2c5y6f55cg">注解工作原理</h3>
<p>注解本身只是元数据，即描述数据的数据，被描述的数据可以是类、方法、属性、参数、构造器等。注解仅是标记本身没有任何可执行的功能代码，但是我们只要标注了注解，我们就能得到想要的功能，这是怎么做到的呢？我们理解肯定是有某处代码根据我们标注的注解找到我们注解的类、方法、属性、参数等数据本身，并根据注解本身的含义以及注解属性里的内容执行功能代码。</p>
<h3 id="heading-beanpostprocessor">第一种：<code>BeanPostProcessor</code></h3>
<p><code>BeanPostProcessor</code>实现中有一类名称为<code>*AnnotationBeanPostProcessor</code>都是针对处理注解的，对容器内标注了指定注解的Bean，进行功能处理。如：</p>
<ul>
<li><code>AutowiredAnnotationBeanPostProcessor</code>：让<code>@Autowired</code>、<code>@Value</code>、<code>@Inject</code>注解起效；</li>
<li><code>CommonAnnotationBeanPostProcessor</code>：让<code>@PostConstruct</code>、<code>@PreDestroy</code>注解起效；</li>
<li><code>AsyncAnnotationBeanPostProcessor</code>：让<code>@Async</code>或<code>@Asynchronous</code>注解起效；</li>
<li><code>ScheduledAnnotationBeanPostProcessor</code>：让<code>@Scheduled</code>注解起效；</li>
<li><code>PersistenceAnnotationBeanPostProcessor</code>：让<code>@PersistenceUnit</code> 、 <code>@PersistenceContext</code>注解起效；</li>
<li><code>JmsListenerAnnotationBeanPostProcessor</code>：让<code>@JmsListener</code>注解起效。
它们都会在构造器里指定其能处理的注解类型，并在对应的方法进行功能处理，我们下面演示一个简单的例子帮助大家理解，我们定义注解<code>@InjectLogger</code>向Bean注入<code>org.slf4j.Logger</code>来做系统日志。</li>
</ul>
<p>我们先定义要使用的注解，只能注解在类上，默认的后缀为“-Bean”</p>
<pre><code class="lang-java"><span class="hljs-meta">@Target(ElementType.FIELD)</span>
<span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span>
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> InjectLogger {
}
</code></pre>
<p>下面我们定义处理注解的
<code>InjectLoggerAnnotationBeanPostPorcessor</code>，我们在上节已经学习过，我们只需要实现<code>BeanPostPorcessor</code>接口即可：</p>
<pre><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InjectLoggerAnnotationBeanPostPorcessor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">BeanPostProcessor</span> </span>{
    <span class="hljs-keyword">private</span> Class&lt;? extends Annotation&gt; changeAnnotationType; <span class="hljs-comment">//1</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">InjectLoggerAnnotationBeanPostPorcessor</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">this</span>.changeAnnotationType = InjectLogger.class; //<span class="hljs-number">1</span>
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">postProcessAfterInitialization</span><span class="hljs-params">(Object bean, String beanName)</span> <span class="hljs-keyword">throws</span> BeansException </span>{
        ReflectionUtils.doWithFields(bean.getClass(), field -&gt; { <span class="hljs-comment">//2</span>
            ReflectionUtils.makeAccessible(field); <span class="hljs-comment">//3</span>
            <span class="hljs-keyword">if</span>(field.isAnnotationPresent(changeAnnotationType)){ <span class="hljs-comment">//4</span>
                Logger logger = LoggerFactory.getLogger(bean.getClass()); <span class="hljs-comment">//5</span>
                field.set(bean, logger); <span class="hljs-comment">//6</span>
            }
        });
        <span class="hljs-keyword">return</span> bean;
    }
}
</code></pre>
<ol>
<li>指明当前类处理<code>@InjectLogger</code>注解；</li>
<li>通过反射机制对类的每个属性（<code>Field</code>）进行处理，第一个参数是Bean的Class，第二参数是入参为Field无返回值的函数接口的Lambda实现；</li>
<li>通过反射机制让当前属性可访问；</li>
<li>新建Logger的实例logger</li>
<li><p>通过反射将logger值设置到bean实例的当前属性（field）上。
我们将注解使用到其他Bean上使用：</p>
<pre><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DemoLoggerService</span> </span>{
 <span class="hljs-meta">@InjectLogger</span>
 <span class="hljs-keyword">private</span> Logger log;

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doSomething</span><span class="hljs-params">()</span></span>{
     log.info(<span class="hljs-string">"通过自定义InjectLoggerAnnotationBeanPostPorcessor让注解@InjectLogger注入Logger对象"</span>);
 }
}
</code></pre>
<p>通过在<code>JavaConfig</code>运行：</p>
<pre><code class="lang-java"><span class="hljs-meta">@Bean</span>
<span class="hljs-function">CommandLineRunner <span class="hljs-title">changeAnnotationBeanPostProcessorClr</span><span class="hljs-params">(DemoLoggerService demoLoggerService)</span></span>{
 <span class="hljs-keyword">return</span> args -&gt; {
     demoLoggerService.doSomething();
 };
}
</code></pre>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663555293589/zqUuk81pS.png" alt="image.png" /></p>
<h3 id="heading-beanfactorypostprocessor">第二种：<code>BeanFactoryPostProcessor</code></h3>
<p>前面我们讲了针对Bean进行处理的<code>BeanPostProcessor</code>，而<code>BeanFactoryPostProcessor</code>是针对Bean的配置元数据（注解等）进行处理操作，而这项工作属于<code>BeanFactory</code>的职责范畴。</p>
<ul>
<li><code>ConfigurationClassPostProcessor</code>：使@<code>PropertySource</code>、<code>@ComponentScan</code>、<code>@Component</code>类、<code>@Configuration</code>、<code>@Bean</code>、<code>@Import</code>和<code>@ImportResource</code>注解起效；</li>
<li><code>EventListenerMethodProcessor</code>：使<code>@EventListener</code>注解起效；
我们也通过一个自定义的注解<code>@CustomBean</code>，来自己自动注册Bean，我们的`@CustomBean的作用即是配置元数据。</li>
</ul>
<p>自定义的注解为：</p>
<pre><code class="lang-java"><span class="hljs-meta">@Target(ElementType.TYPE)</span>
<span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span>
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> CustomBean {
}
</code></pre>
<p>使用在Bean上：</p>
<pre><code class="lang-java"><span class="hljs-meta">@CustomBean</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomBeanService</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doSomething</span><span class="hljs-params">()</span></span>{
        System.out.println(<span class="hljs-string">"通过自定义的注解成功注册bean"</span>);
    }
}
</code></pre>
<p>同样，我们也实现<code>BeanFactoryPostProcessor</code>接口：</p>
<pre><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomBeanDefinitionRegistryPostProcessor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">BeanFactoryPostProcessor</span> </span>{

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">postProcessBeanFactory</span><span class="hljs-params">(ConfigurableListableBeanFactory beanFactory)</span> <span class="hljs-keyword">throws</span> BeansException </span>{
        ClassPathBeanDefinitionScanner scanner = <span class="hljs-keyword">new</span> ClassPathBeanDefinitionScanner((BeanDefinitionRegistry) beanFactory); <span class="hljs-comment">//1</span>
        scanner.addIncludeFilter(<span class="hljs-keyword">new</span> AnnotationTypeFilter(CustomBean.class)); //<span class="hljs-number">2</span>
        scanner.scan(<span class="hljs-string">"top.wisely.springfundamentals.custom_scan"</span>); <span class="hljs-comment">//3</span>

    }
}
</code></pre>
<ol>
<li>定义一个类路径Bean定义扫描器，它的入参是<code>BeanDefinitionRegistry</code>类型，而<code>ConfigurableListableBeanFactory</code>是它的子类，可强制转换使用，当然我们可以让我们的类直接实现<code>BeanDefinitionRegistryPostProcessor</code>接口，它的方法<code>void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)</code>直接提供了<code>BeanDefinitionRegistry</code>的对象；</li>
<li>为扫描器添加包含的注解<code>@CustomBean</code>的过滤器；</li>
<li>在包<code>top.wisely.springfundamentals.custom_scan</code>下扫描注解；
在<code>JavaConfig</code>中注入我们自定义的Bean，因为是我们自定义的，IDE不能自动检测而显示红色，但可以正常执行：<pre><code class="lang-java"><span class="hljs-meta">@Bean</span>
<span class="hljs-function">CommandLineRunner <span class="hljs-title">customBeanDefinitionRegistryPostProcessorClr</span><span class="hljs-params">(CustomBeanService customBeanService)</span></span>{
 <span class="hljs-keyword">return</span> args -&gt; {
     customBeanService.doSomething();
 };
}
</code></pre>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663555430733/v_OFleE6M.png" alt="image.png" /></p>
<h3 id="heading-aop">第三种：AOP</h3>
<p>我们还可以通过基于AOP来让注解具备功能，通过拦截标注了指定注解的方法或类，然后再建言执行功能代码。如：</p>
<ul>
<li><code>AnnotationTransactionAspect</code>：让<code>@Transactional</code>注解起效；</li>
<li><code>AnnotationCacheAspect</code>：让<code>@Cacheable</code>注解起效；</li>
</ul>
<h3 id="heading-yuan">第四种：组合元注解</h3>
<p>Spring的大部分的元注解，我们可以使用元注解到其它的注解，即用元注解（元数据）描述注解，从而使其它的注解具备元注解的功能，一般我们认为组合注解是元注解在新的语义下的特例。</p>
<ul>
<li><code>@Component</code>元注解：<code>@Service</code>、<code>@Repository</code>、<code>@Controller</code>、<code>@Configuration</code>：<pre><code class="lang-java"><span class="hljs-meta">@Target({ElementType.TYPE})</span>
<span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span>
<span class="hljs-meta">@Documented</span>
<span class="hljs-meta">@Component</span> <span class="hljs-comment">//组合了@Component注解，具备了声明Bean的能力</span>
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> Service {}
</code></pre>
</li>
<li><code>@Import</code>元注解：大量的<code>@Enable*</code>注解：<pre><code class="lang-java"><span class="hljs-meta">@Target(ElementType.TYPE)</span>
<span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span>
<span class="hljs-meta">@Documented</span>
<span class="hljs-meta">@Import(AsyncConfigurationSelector.class)</span> <span class="hljs-comment">//组合了@Import注解，具备了导入配置的能力</span>
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> EnableAsync {}
</code></pre>
</li>
<li><code>@Conditional</code>元注解：<code>@Profile</code>以及Spring Boot的大量条件注解<code>@ConditionalOn*</code><pre><code class="lang-java"><span class="hljs-meta">@Target({ ElementType.TYPE, ElementType.METHOD })</span>
<span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span>
<span class="hljs-meta">@Documented</span>
<span class="hljs-meta">@Conditional(OnClassCondition.class)</span>
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> ConditionalOnClass {}
</code></pre>
<h3 id="heading-5oc757ut">总结</h3>
Spring框架的注解的主要工作方式大体罗列在本文，如果大家发现还有其他的Spring注解的工作方式，欢迎补充。</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Spring Framework 6.0/Spring Boot 3.0新特性：简单的远程调用HTTP Interfaces]]></title><description><![CDATA[在Spring 6.0之前，我们访问第三方服务或者微服务的http接口主要有这些方式：

RestTemplate：可同步访问HTTP服务。
WebClient：可同步或异步访问HTTP服务。
Feign：在微服务架构中（不限于微服务），用声明式的方式访问HTTP服务。
在Spring 6.0中，我们有了一个更方便的HTTP请求的手段，HTTP Interfaces。HTTP Interfaces类似于Spring Data的Repository或者Spring Cloud OpenFeign一...]]></description><link>https://wisely.top/spring-framework-6-spring-boot3-http-interfaces</link><guid isPermaLink="true">https://wisely.top/spring-framework-6-spring-boot3-http-interfaces</guid><category><![CDATA[Spring framework]]></category><category><![CDATA[Spring Boot]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Fri, 16 Sep 2022 08:11:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663315806666/jXBcFTbcm.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在Spring 6.0之前，我们访问第三方服务或者微服务的http接口主要有这些方式：</p>
<ul>
<li>RestTemplate：可同步访问HTTP服务。</li>
<li>WebClient：可同步或异步访问HTTP服务。</li>
<li>Feign：在微服务架构中（不限于微服务），用声明式的方式访问HTTP服务。
在Spring 6.0中，我们有了一个更方便的HTTP请求的手段，HTTP Interfaces。HTTP Interfaces类似于Spring Data的Repository或者Spring Cloud OpenFeign一样，我们只需要声明接口就可以完成工作，框架会直接给我们自动代理一个实现。</li>
</ul>
<p>在实际使用中HTTP Interfaces和Spring Cloud OpenFeign使用感受极其类似。</p>
<p>下面我进行一个极其简单的演示。</p>
<h3 id="heading-1spring-boot-3">1、创建Spring Boot 3工程</h3>
<p>打开https://start.spring.io，Spring Boot版本选择3.0.0（SNAPSHOT）（当前最新版本，若有更新版本选择最新版本），Java版本选择17（Spring Boot最低支持版本），依赖选择Spring Web、Spring Reactive Web、Lombok（此时我们还是常规使用Tomcat的同步调用，添加Spring Reactive Web是为了使用WebClient）。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663315497594/w5mBUw4T8.png" alt="image.png" /></p>
<h3 id="heading-2rest-http">2、一个在线REST HTTP服务</h3>
<p><a target="_blank" href="https://jsonplaceholder.typicode.com">https://jsonplaceholder.typicode.com</a></p>
<p>提供多个REST资源。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663315534867/-xTpL-tu1.png" alt="image.png" /></p>
<p>我们可以对这些资源进行增删查改的操作，本例选用todos资源。</p>
<h3 id="heading-3dto">3、编写客户端和服务端之间的数据传值对象（DTO）</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663315546069/JwpjrMzQT.png" alt="image.png" /></p>
<p>根据在线资源服务的数据结构编写DTO</p>
<pre><code class="lang-java"><span class="hljs-meta">@Data</span>
<span class="hljs-meta">@AllArgsConstructor</span>
<span class="hljs-meta">@NoArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Todo</span> </span>{
   <span class="hljs-keyword">private</span> Long id;
   <span class="hljs-keyword">private</span> Long userId;
   <span class="hljs-keyword">private</span> String title;
   <span class="hljs-keyword">private</span> Boolean completed;
}
</code></pre>
<h3 id="heading-4http">4、编写HTTP客户端</h3>
<pre><code class="lang-java"><span class="hljs-meta">@HttpExchange("/todos")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">TodoClient</span> </span>{

    <span class="hljs-meta">@GetExchange("/{todoId}")</span>
    <span class="hljs-function">Todo <span class="hljs-title">getTodo</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> Integer todoId)</span></span>;

    <span class="hljs-meta">@GetExchange</span>
    <span class="hljs-function">List&lt;Todo&gt; <span class="hljs-title">getTodos</span><span class="hljs-params">()</span></span>;

    <span class="hljs-meta">@PostExchange</span>
    <span class="hljs-function">Todo <span class="hljs-title">save</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> Todo todo)</span></span>;
}
</code></pre>
<p>这里和Spring WebMVC的用法是很类似的，不过它声明的是远程的资源，和Spring Cloud OpenFeign一样。</p>
<ul>
<li><code>@HttpExchange</code>：最通用的一个注解，在类级别将会被所有方法继承，如基础路径地址；在方法级别，需要指定具体的method类型。相当于<code>@RequestMapping</code>。</li>
<li><code>@GetExchange</code>：HTTP GET请求。相当于<code>@GetMapping</code>。</li>
<li><code>@PostExchange</code>：HTTP POST请求。相当于<code>@PostMapping</code>。</li>
<li><code>@PutExchange</code>：HTTP PUT请求。相当于<code>@PutMapping</code>。</li>
<li><code>@DeleteExchange</code>：HTTP DELETE请求。相当于<code>@DeleteMapping</code>。</li>
<li><code>@PatchExchange</code>：HTTP PATCH请求。相当于<code>@PatchMapping</code>。<h3 id="heading-5http">5、注册HTTP客户端</h3>
<pre><code class="lang-java"><span class="hljs-meta">@Configuration</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ClientConfig</span> </span>{
  <span class="hljs-meta">@Bean</span>
  <span class="hljs-function"><span class="hljs-keyword">public</span> TodoClient <span class="hljs-title">todoClient</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
      WebClient webClient = WebClient.builder()
              .baseUrl(<span class="hljs-string">"https://jsonplaceholder.typicode.com"</span>)
              .build();
      HttpServiceProxyFactory factory = <span class="hljs-keyword">new</span> HttpServiceProxyFactory(WebClientAdapter.forClient(webClient));
      factory.afterPropertiesSet();
      <span class="hljs-keyword">return</span> factory.createClient(TodoClient.class);
  }
}
</code></pre>
我们的HTTP客户端TodoClient是利用WebClient来实现的。这块以后WebClient应该从Spring Reactive Web剥离出来，或者提供更多其他的HTTP Client的实现，这样就不需要依赖于Spring Reactive Web了。</li>
</ul>
<h3 id="heading-6">6、演示</h3>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HttpInterfacesApplication</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        SpringApplication.run(HttpInterfacesApplication.class, args);
    }

    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function">CommandLineRunner <span class="hljs-title">clr</span><span class="hljs-params">(TodoClient todoClient)</span></span>{
        <span class="hljs-keyword">return</span> args -&gt; {
            log.info(todoClient.getTodo(<span class="hljs-number">1</span>).toString());
            log.info(todoClient.save(<span class="hljs-keyword">new</span> Todo(<span class="hljs-keyword">null</span>, <span class="hljs-number">1L</span>, <span class="hljs-string">"看书"</span>, <span class="hljs-keyword">false</span>)).toString());
            <span class="hljs-keyword">for</span>(Todo todo : todoClient.getTodos()){
                log.info(todo.toString());
            }
        };
    }

}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663315631528/ytFCK9pv4.png" alt="image.png" /></p>
<h3 id="heading-57ut6kt">结语</h3>
<p>HTTP interfaces使用起来比RestTemplate和WebClient要简单很多，使用声明式类似于OpenFeign的方式进行远程的HTTP资源的调用，但不需要额外引用Spring Cloud OpenFeign，极大的简化了代码和提高了开发效率。</p>
]]></content:encoded></item><item><title><![CDATA[Exploring the Magic of Spring Boot runnable standalone jar]]></title><description><![CDATA[Spring Boot uses Spring Boot Gradle Plugin or Spring Boot Maven Plugin to package application as a runnable standalone jar file.
Spring Boot uses the Spring Boot Loader to lauch the Spring Boot jar file usingjava -jar.
Let's uncompress the jar file a...]]></description><link>https://wisely.top/exploring-the-magic-of-spring-boot-runnable-standalone-jar</link><guid isPermaLink="true">https://wisely.top/exploring-the-magic-of-spring-boot-runnable-standalone-jar</guid><category><![CDATA[Springboot]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Tue, 02 Aug 2022 09:10:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1659431818794/UvfxddgjZ.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Spring Boot uses <a target="_blank" href="https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin">Spring Boot Gradle Plugin</a> or <a target="_blank" href="https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin">Spring Boot Maven Plugin</a> to package application as a runnable standalone jar file.</p>
<p>Spring Boot uses the <a target="_blank" href="https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-tools/spring-boot-loader">Spring Boot Loader</a> to lauch the Spring Boot jar file using<code>java -jar</code>.</p>
<p>Let's uncompress the jar file and see its structure(jar is actually a zip file):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659424262025/cGJwJPwby.png" alt="image.png" /></p>
<p>We see three sub-folders in the uncompressed folder：<code>BOOT-INF</code>,<code>META-INF</code>,<code>org</code></p>
<pre><code>example.jar
 <span class="hljs-operator">|</span>
 <span class="hljs-operator">+</span><span class="hljs-operator">-</span>META<span class="hljs-operator">-</span>INF
 <span class="hljs-operator">|</span>  <span class="hljs-operator">+</span><span class="hljs-operator">-</span>MANIFEST.MF
 <span class="hljs-operator">+</span><span class="hljs-operator">-</span>org
 <span class="hljs-operator">|</span>  <span class="hljs-operator">+</span><span class="hljs-operator">-</span>springframework
 <span class="hljs-operator">|</span>     <span class="hljs-operator">+</span><span class="hljs-operator">-</span>boot
 <span class="hljs-operator">|</span>        <span class="hljs-operator">+</span><span class="hljs-operator">-</span>loader
 <span class="hljs-operator">|</span>           <span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">&lt;</span>spring boot loader classes<span class="hljs-operator">&gt;</span>
 <span class="hljs-operator">+</span><span class="hljs-operator">-</span>BOOT<span class="hljs-operator">-</span>INF
    <span class="hljs-operator">+</span><span class="hljs-operator">-</span>classes
    <span class="hljs-operator">|</span>  <span class="hljs-operator">+</span><span class="hljs-operator">-</span>mycompany
    <span class="hljs-operator">|</span>     <span class="hljs-operator">+</span><span class="hljs-operator">-</span>project
    <span class="hljs-operator">|</span>        <span class="hljs-operator">+</span><span class="hljs-operator">-</span>YourClasses.class
    <span class="hljs-operator">+</span><span class="hljs-operator">-</span>lib
       <span class="hljs-operator">+</span><span class="hljs-operator">-</span>dependency1.jar
       <span class="hljs-operator">+</span><span class="hljs-operator">-</span>dependency2.jar
</code></pre><p><strong>BOOT-INF</strong></p>
<ol>
<li><code>BOOT-INF/classes</code>: application classes</li>
<li><code>BOOT-INF/lib</code>: nested dependency jars</li>
<li><code>BOOT-INF/classpath.idx</code>: the ordering that jars should be added to the classpath</li>
<li><code>BOOT-INF/layers.idx</code>: allows a jar to be split into logical layers for Docker/OCI image creation</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659424752998/rwZwd2fdO.png" alt="image.png" /></p>
<p><strong>META-INF</strong></p>
<p><code>META-INF/MANIFEST.MF</code>: contains information about the files contained in the JAR file</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659424798687/IVpjG5xpJ.png" alt="image.png" /></p>
<p><strong>org</strong></p>
<p>spring boot loader classes</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659424834494/Uk3QAAqp7.png" alt="image.png" /></p>
<h2 id="heading-explore-the-magic">Explore the Magic</h2>
<p>We will explore the steps of running a standalone jar file:</p>
<ol>
<li><code>java -jar</code></li>
<li>Looking for the executable jar’s main entry point in the <code>META-INF/MANIFEST.MF</code><pre><code>Manifest<span class="hljs-operator">-</span>Version: <span class="hljs-number">1.0</span>
Main<span class="hljs-operator">-</span>Class: org.springframework.boot.loader.JarLauncher
Start<span class="hljs-operator">-</span>Class: top.wisely.springasync.SpringAsyncApplication
Spring<span class="hljs-operator">-</span>Boot<span class="hljs-operator">-</span>Version: <span class="hljs-number">2.7</span><span class="hljs-number">.2</span>
Spring<span class="hljs-operator">-</span>Boot<span class="hljs-operator">-</span>Classes: BOOT<span class="hljs-operator">-</span>INF<span class="hljs-operator">/</span>classes<span class="hljs-operator">/</span>
Spring<span class="hljs-operator">-</span>Boot<span class="hljs-operator">-</span>Lib: BOOT<span class="hljs-operator">-</span>INF<span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>
Spring<span class="hljs-operator">-</span>Boot<span class="hljs-operator">-</span>Classpath<span class="hljs-operator">-</span>Index: BOOT<span class="hljs-operator">-</span>INF<span class="hljs-operator">/</span>classpath.idx
Spring<span class="hljs-operator">-</span>Boot<span class="hljs-operator">-</span>Layers<span class="hljs-operator">-</span>Index: BOOT<span class="hljs-operator">-</span>INF<span class="hljs-operator">/</span>layers.idx
</code></pre></li>
<li><p><code>Main-Class: org.springframework.boot.loader.JarLauncher</code> is the main entry point.
<a target="_blank" href="https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java">JarLauncher</a></p>
</li>
<li><p>Because<code>JarLauncher extends ExecutableArchiveLauncher</code> and <code>ExecutableArchiveLauncher extends Launcher</code></p>
</li>
<li><p><code>JarLauncher</code>'s main method:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JarLauncher</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ExecutableArchiveLauncher</span> </span>{
<span class="hljs-comment">//...</span>
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
     <span class="hljs-keyword">new</span> JarLauncher().launch(args);
 }
<span class="hljs-comment">//...</span>
}
</code></pre>
</li>
<li>So the real <code>launch(args)</code> method is in <code>Launcher</code> class:<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Launcher</span> </span>{
 <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">launch</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    <span class="hljs-keyword">if</span> (!isExploded()) {
        JarFile.registerUrlProtocolHandler(); 
    }
    ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); <span class="hljs-comment">//①</span>
    String jarMode = System.getProperty(<span class="hljs-string">"jarmode"</span>); 
    String launchClass = (jarMode != <span class="hljs-keyword">null</span> &amp;&amp; !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass(); <span class="hljs-comment">//②</span>
    launch(args, launchClass, classLoader);  <span class="hljs-comment">//③</span>
}
}
</code></pre>
</li>
</ol>
<p>① : <code>classLoader</code> - Create a new <code>LaunchedURLClassLoader</code> instance. all <code>BOOT-INF/classes</code> and <code>BOOT-INF/jar</code> classes are loaded by <code>LaunchedURLClassLoader</code> .</p>
<p>② : <code>launchClass</code> - get the <code>Start-Class</code> from <code>META-INF/MANIFEST.MF</code>.</p>
<p>③：launch Application using <code>args</code>,<code>launchClass</code>,<code>classLoader</code>.</p>
<p>That'a all for my first English article.</p>
]]></content:encoded></item><item><title><![CDATA[深入浅出Java/Spring/Spring Boot异步多线程]]></title><description><![CDATA[1、Java的多线程
1.1 线程池模式
一个线程池可以维护多个线程，这些线程等待任务来进行并发处理。线程池模式避免了频繁创建和销毁短期任务线程，复用池中的线程从而提高了性能。线程池中的线程在处理任务时是并发进行的。

该模式允许创建的线程数量及其生命周期。 我们还能够安排任务的执行并将传入的任务保持在队列（Task Queue）中。
线程池数量的大小可根据程序可用的计算资源进行调整，它通常是程序的可调参数，经过调整以优化程序的性能。 确定最佳线程池大小对于优化性能至关重要。
1.2 Java的...]]></description><link>https://wisely.top/java-spring-springboot-multithreading</link><guid isPermaLink="true">https://wisely.top/java-spring-springboot-multithreading</guid><category><![CDATA[multithreading]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Thu, 28 Jul 2022 01:33:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658970153154/IvK-oz7st.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-1java">1、Java的多线程</h2>
<h3 id="heading-11">1.1 线程池模式</h3>
<p>一个线程池可以维护多个线程，这些线程等待任务来进行并发处理。线程池模式避免了频繁创建和销毁短期任务线程，复用池中的线程从而提高了性能。线程池中的线程在处理任务时是并发进行的。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658970179389/SxtEs65iI.png" alt="线程池（绿色方块）/等待处理任务队列（蓝色）/处理完成任务（黄色）" /></p>
<p>该模式允许创建的线程数量及其生命周期。 我们还能够安排任务的执行并将传入的任务保持在队列（Task Queue）中。</p>
<p>线程池数量的大小可根据程序可用的计算资源进行调整，它通常是程序的可调参数，经过调整以优化程序的性能。 确定最佳线程池大小对于优化性能至关重要。</p>
<h3 id="heading-12-javathreadpoolexecutor">1.2 Java的线程池ThreadPoolExecutor</h3>
<p>Java中的<code>ThreadPoolExecutor</code>是一个可扩展的线程池的实现，它提供了对很多参数的微调设置。这些参数包括：</p>
<p><code>ThreadPoolExecutor</code>的构造器：</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ThreadPoolExecutor</span><span class="hljs-params">(<span class="hljs-keyword">int</span> corePoolSize,
                              <span class="hljs-keyword">int</span> maximumPoolSize,
                              <span class="hljs-keyword">long</span> keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue&lt;Runnable&gt; workQueue,
                              RejectedExecutionHandler handler)</span> </span>{
  <span class="hljs-comment">//...</span>
    }
</code></pre>
<p>示例：</p>
<pre><code class="lang-java">ThreadPoolExecutor executorPool = <span class="hljs-keyword">new</span> ThreadPoolExecutor(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>, <span class="hljs-number">3</span>, TimeUnit.SECONDS, <span class="hljs-keyword">new</span> ArrayBlockingQueue&lt;Runnable&gt;(<span class="hljs-number">50</span>));
</code></pre>
<ul>
<li><p><code>corePoolSize</code>：第1个参数“5”。JVM会为前5个任务创建线程，后续的任务会放进队列里，直到队列满为止（第5个参数<code>workQueue</code>，50个任务）。</p>
</li>
<li><p><code>maximumPoolSize</code>：第2个参数“10”。JVM最多创建10个线程，这意味着当当前正有5个线程运行着5个任务，而这时任务队列的50个任务已满，此时如果有一个新的任务到达队列，JVM将创建一个新的线程，最多创建5个，直至10个。</p>
</li>
<li><p><code>keepAliveTime</code>：第3个参数“3秒”，超出核心线程而小于最大线程的这些线程，在一定的空闲时间之后将被清除掉。</p>
</li>
<li><p><code>unit</code>：第4个参数“秒”，keepAliveTime的时间单位。</p>
</li>
<li><p><code>workQueue</code>：第5个参数<code>new ArrayBlockingQueue&lt;Runnable&gt;(50)</code>，任务队列的大小。</p>
</li>
</ul>
<p>JVM创建线程的规则如下：</p>
<ol>
<li>如果线程数少于<code>corePoolSize</code>，创建新的线程来跑任务。</li>
<li>如何线程数等于或大于<code>corePoolSize</code>，将任务放进队列。</li>
<li>如果队列满了，且线程数小于<code>maximumPoolSize</code>，创建新的线程跑任务。</li>
<li>如果队列满了，且线程数量大于或等于<code>maximumPoolSize</code>，拒绝任务。</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658970467418/82UMTSs6L.png" alt="image.png" /></p>
<h2 id="heading-2spring">2、Spring的多线程</h2>
<p>Spring/Spring Boot只需要在配置类上注<code>@EnableAsync</code>，在需要使用单独线程的方法上使用<code>@Async</code>注解即可。Spring会自动检索线程池的定义，可以是<code>org.springframework.core.task.TaskExecutor</code>或者是<code>java.util.concurrent.Executor</code>的名为<code>taskExecutor</code>的bean。若都未找到，则使用<code>org.springframework.core.task.SimpleAsyncTaskExecutor</code>来处理异步方法的调用。</p>
<p>我们最简单可以通过自定一个名为<code>taskExecutor</code>的Bean即可。</p>
<pre><code class="lang-java"><span class="hljs-meta">@Configuration</span>
<span class="hljs-meta">@EnableAsync</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AsyncConfigurationByBean</span> </span>{
    <span class="hljs-meta">@Bean(name="taskExecutor")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Executor <span class="hljs-title">taskExecutor</span><span class="hljs-params">()</span> </span>{
        ThreadPoolTaskExecutor executor = <span class="hljs-keyword">new</span> ThreadPoolTaskExecutor();
        executor.setCorePoolSize(<span class="hljs-number">5</span>);
        executor.setMaxPoolSize(<span class="hljs-number">10</span>);
        executor.setQueueCapacity(<span class="hljs-number">50</span>);
        executor.setThreadNamePrefix(<span class="hljs-string">"poolThread-"</span>);
        executor.initialize();
        <span class="hljs-keyword">return</span> executor;
    }

}
</code></pre>
<p>Spring也提供了“AsyncConfigurer”接口用来定制实现异步多线程相关的配置。</p>
<pre><code class="lang-java"><span class="hljs-meta">@Configuration</span>
<span class="hljs-meta">@EnableAsync</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AsyncConfigurationByConfigurer</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">AsyncConfigurer</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Executor <span class="hljs-title">getAsyncExecutor</span><span class="hljs-params">()</span> </span>{
        ThreadPoolTaskExecutor executor = <span class="hljs-keyword">new</span> ThreadPoolTaskExecutor();
        executor.setCorePoolSize(<span class="hljs-number">5</span>);
        executor.setMaxPoolSize(<span class="hljs-number">10</span>);
        executor.setQueueCapacity(<span class="hljs-number">50</span>);
        executor.setThreadNamePrefix(<span class="hljs-string">"poolThread-"</span>);
        executor.initialize();
        <span class="hljs-keyword">return</span> executor;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> AsyncUncaughtExceptionHandler <span class="hljs-title">getAsyncUncaughtExceptionHandler</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> AsyncConfigurer.<span class="hljs-keyword">super</span>.getAsyncUncaughtExceptionHandler();
    }
}
</code></pre>
<h2 id="heading-3spring-boot">3、Spring Boot的多线程</h2>
<p>在Spring Boot下，通过<code>TaskExecutionAutoConfiguration</code>自动配置类，Spring Boot给我们已经自动配置好了线程池，<code>TaskExecutionProperties</code>提供了相关的属性配置。在Spring Boot下我们只需要在配置类上<code>@EnableAsync</code>，在“application.yml”上配置即可：</p>
<pre><code class="lang-yaml"><span class="hljs-attr">spring:</span>
  <span class="hljs-attr">task:</span>
    <span class="hljs-attr">execution:</span>
      <span class="hljs-attr">pool:</span>
        <span class="hljs-attr">core-size:</span> <span class="hljs-number">5</span>
        <span class="hljs-attr">max-size:</span> <span class="hljs-number">10</span>
        <span class="hljs-attr">queue-capacity:</span> <span class="hljs-number">50</span>
      <span class="hljs-attr">thread-name-prefix:</span> <span class="hljs-string">poolThread-</span>
</code></pre>
<h2 id="heading-4">4、多线程演示</h2>
<p>新建一个使用异步方法的类和方法：</p>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AsyncService</span> </span>{
    <span class="hljs-meta">@Async</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doSomething</span><span class="hljs-params">(Integer i)</span></span>{
        log.info(<span class="hljs-string">"当前是循环："</span> + i);
    }
}
</code></pre>
<p>在Spring Boot入口类调用：</p>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-meta">@EnableAsync</span> 
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SpringAsyncApplication</span> </span>{

   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
      SpringApplication.run(SpringAsyncApplication.class, args);
   }

   <span class="hljs-meta">@Bean</span>
   <span class="hljs-function">CommandLineRunner <span class="hljs-title">commandLineRunner</span><span class="hljs-params">(AsyncService asyncService)</span></span>{
      <span class="hljs-keyword">return</span> p -&gt; {
         <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span> ; i &lt; <span class="hljs-number">10</span> ; i ++){
            asyncService.doSomething(i);
         }
      };
   }
}
</code></pre>
<p>运行结果：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658970841033/xAF_CkGso.png" alt="image.png" />
从图中可以看出我们的核心线程数量是5，你可以按照上节的“JVM创建线程的规则”来调整核心线程、最大线程、队列数量的来尝试一下观察控制台输出的结果。</p>
<h2 id="heading-5">5、多线程异步结果演示</h2>
<p>我们有时候会在单一的请求中调用多个方法，在同步的方法里，我们都是顺序执行，执行完一个再执行下一个。我们可以通过Spring的<code>AsyncResult</code>让多个方法并发执行并聚合他们的结果，并提高性能。</p>
<p>我们先看一下，在同步的情况下是什么样的：</p>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Service1</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Integer <span class="hljs-title">doSomething</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> InterruptedException </span>{
        Thread.sleep(<span class="hljs-number">2000</span>);
        log.info(<span class="hljs-string">"在Service1中"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Service2</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Integer <span class="hljs-title">doSomething</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> InterruptedException </span>{
        Thread.sleep(<span class="hljs-number">2000</span>);
          log.info(<span class="hljs-string">"在Service2中"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> ;
    }
}
</code></pre>
<pre><code class="lang-java">    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function">CommandLineRunner <span class="hljs-title">commandLineRunner</span><span class="hljs-params">(Service1 service1, Service2 service2)</span></span>{
        <span class="hljs-keyword">return</span> p -&gt; {
            <span class="hljs-keyword">long</span> start = System.currentTimeMillis();
            Integer first = service1.doSomething();
            Integer second = service2.doSomething();
            Integer sum = first + second;
            <span class="hljs-keyword">long</span> end = System.currentTimeMillis();
            Long cost = end - start;
            log.info(<span class="hljs-string">"结果为:"</span> + sum + <span class="hljs-string">",耗时"</span> + cost);
        };
    }
</code></pre>
<p>示例中，一个线程顺序执行两个方法，执行结果为：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658970983300/YbsBj64t4.png" alt="image.png" /></p>
<p>我们现在使用<code>@Async</code>使方法变成异步，且使用<code>AsyncResult</code>包装异步结果返回。</p>
<p><code>CompletableFuture</code>是Java8引入的，以提供一种编写异步、非阻塞和多线程代码的简单方法。我们使用<code>AsyncResult</code>包装返回值，并用它的<code>.completable()</code>方法获得<code>CompletableFuture</code>对象。</p>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Service1</span> </span>{
    <span class="hljs-meta">@Async</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> CompletableFuture&lt;Integer&gt; <span class="hljs-title">doSomething</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> InterruptedException </span>{
        Thread.sleep(<span class="hljs-number">2000</span>);
        log.info(<span class="hljs-string">"在Service1中"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AsyncResult&lt;Integer&gt;(<span class="hljs-number">1</span>).completable();
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Service2</span> </span>{
    <span class="hljs-meta">@Async</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> CompletableFuture&lt;Integer&gt; <span class="hljs-title">doSomething</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> InterruptedException </span>{
        Thread.sleep(<span class="hljs-number">2000</span>);
        log.info(<span class="hljs-string">"在Service2中"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AsyncResult&lt;Integer&gt;(<span class="hljs-number">2</span>).completable();
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-meta">@Bean</span>
<span class="hljs-function">CommandLineRunner <span class="hljs-title">commandLineRunner</span><span class="hljs-params">(Service1 service1, Service2 service2)</span></span>{
   <span class="hljs-keyword">return</span> p -&gt; {
      <span class="hljs-keyword">long</span> start = System.currentTimeMillis();
      CompletableFuture&lt;Integer&gt; firstData = service1.doSomething();
      CompletableFuture&lt;Integer&gt; secondData = service2.doSomething();
      CompletableFuture&lt;Integer&gt; mergeResult = firstData.thenCompose(
                                          firstValue -&gt; secondData.thenApply(
                                                secondValue -&gt; firstValue + secondValue
                                          )
                                    );
      <span class="hljs-keyword">long</span> end = System.currentTimeMillis();
      Long cost = end - start;
      log.info(<span class="hljs-string">"结果为:"</span> + mergeResult.get() + <span class="hljs-string">",耗时"</span> + cost);
   };
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658971515202/XLbD6P7Wu.png" alt="image.png" /></p>
]]></content:encoded></item><item><title><![CDATA[程序员金句一箩筐，也许每句都是你现在最需要]]></title><description><![CDATA[Don't complain about legacy code. You basically write it every day.

不要抱怨遗留代码，你几乎每天都在写。
2.Stop complaining about documentation. you're a programmer. read the source code.
不要抱怨文档。你是个程序员，去读源码。
3.Change is normal. Don't get too attached to code, or to a...]]></description><link>https://wisely.top/quotes-for-programmer</link><guid isPermaLink="true">https://wisely.top/quotes-for-programmer</guid><category><![CDATA[quote]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Wed, 27 Jul 2022 03:06:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658891248088/dLQfh_beg.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<ol>
<li>Don't complain about legacy code. You basically write it every day.</li>
</ol>
<p>不要抱怨遗留代码，你几乎每天都在写。</p>
<p>2.Stop complaining about documentation. you're a programmer. read the source code.</p>
<p>不要抱怨文档。你是个程序员，去读源码。</p>
<p>3.Change is normal. Don't get too attached to code, or to a specific way to do something.</p>
<p>变化是正常的。 不要太执着于代码，或者做某事的特定方式</p>
<p>4.When writing code, always write it like someone else will read it and can’t ask you what it does.</p>
<p>写代码时，始终想着以后有人要阅读这些代码，而他却不能问你这些代码是干什么的。</p>
<p>5.“This is not a research farm.” A good implementation that’s completed is far better than an incomplete solution that tries to achieve perfection.</p>
<p>“这不是研究农场。” 一个已经完成的好的实现远比一个试图达到完美的不完整的解决方案要好得多。</p>
<p>6.The primary job of any software engineer is delivering value</p>
<p>任何软件工程师的主要工作是交付价值</p>
<p>7.The better you can work in different styles and be consistent in their problem domains, the less you’ll get stuck at technical barriers.</p>
<p>你越能以不同的风格工作并在他们的“问题领域”保持一致，你就越不会陷入技术障碍（解决方案领域）。</p>
<p>8.Don’t fixate - stay flexible about the least essential. These things can often change without meaningful impact.</p>
<p>不要固执——对最不重要的事情保持灵活。 这些事情经常改变但是并没有什么有意义的影响</p>
<ol>
<li>Always provide good documentation and comments to explain context and why.</li>
</ol>
<p>总是提供良好的文档来解释上下文和原因。</p>
<p>10.If you don't know what to call it, you don't know what it's for.</p>
<p>如果你不知道怎么称呼它，你就不知道它是干什么用的。</p>
<p>11.There's <em>always</em> a simpler way to think about solving a problem, strive for simplicity, because code is a liability, not an asset</p>
<p><em>总是</em>有一种更简单的方式来思考解决问题，力求简单，因为代码是一种责任，而不是一种资产</p>
<p>12.Everything is a tradeoff. Pick one based on context</p>
<p>一切都是权衡。 根据上下文选择一个方案</p>
<p>13.The first step of any project is to grossly underestimate its complexity and difficulty.</p>
<p>任何项目的第一步都是严重低估其复杂性和难度。</p>
<p>14.Don't fanatic with tech stack (language, framework, db, etc)</p>
<p>不要热衷于技术栈（语言、框架、数据库等）</p>
<p>15.Don't impress me with some complicated techniques, impress me by making the shit work.</p>
<p>不要用一些复杂的技术来取悦我，要让工作运行起来取悦我</p>
<p>16.When you start as a developer you think that the most important part of your job is the technology. Writing code.</p>
<p>Years passes and you realize that is writing good, readable code.</p>
<p>And after many years, that is in communication. Understand and be understood.</p>
<p>当你开始作为一名开发人员时，你认为你工作中最重要的部分是技术 - 编写代码。</p>
<p>几年过去了，你意识到是编写好的、可读的代码。</p>
<p>多年后，意识到沟通才是最重要的。 理解和被理解。</p>
<p>17.Languages are just tools, learn the principles of software development and picking up new tools will be easier.</p>
<p>语言只是工具，学习软件开发原理，学习新工具会更容易</p>
<p>18.Just because you can, doesn't mean you should</p>
<p>仅仅因为你可以，并不意味着你应该</p>
<p>19.Don't be afraid to ask questions, it's not a weakness to admit you don't understand something.</p>
<p>不要害怕提出问题，承认自己不懂并不是一种弱点</p>
<p>20.Don’t implement every customer request. Most of them are useless.</p>
<p>不要实现每个客户的要求。 他们中的大多数都是无用的</p>
<p>21.Don't overdo and overdo background stuff. If it works as it's supposed to, nobody cares.</p>
<p>22.Explain to me like I'm 5</p>
<p>把我当5岁孩子一样和我解释</p>
<p>23.There's no such thing as a dumb question</p>
<p>没有愚蠢的问题</p>
<p>24.A working solution is much better than a perfect one.</p>
<p>一个可行的解决方案比一个完美的解决方案要好得多</p>
<p>25.Don't ever cut and paste code if you don't understand what it does. Only add code to projects that you are confident you know what it does.</p>
<p>如果您不了解代码的作用，请不要剪切和粘贴代码。 仅将你有信心知道作用的代码添加到项目中</p>
<p>26.If your solution is too complicated, there is a simpler one</p>
<p>如果你的解决方案太复杂，有一个更简单的</p>
<p>27.Don’t just fix the bug. Fix its root cause.</p>
<p>不要只修复bug，修复问题的根源</p>
<p>28.The answer will come to you on the toilet.</p>
<p>答案会在你上厕所的时候浮现</p>
<p>29.Self-documenting code is the best documentation.</p>
<p>自注释的代码是最好的文档</p>
<p>30.Keep it simple, make it work, optimize later.</p>
<p>保持简单，让它工作，以后再优化</p>
<p>31.Type it, don't do copy and paste.</p>
<p>敲代码，不要复制粘贴</p>
<p>32.Don't write clever code.</p>
<p>不要写聪明的代码</p>
<p>33.Don’t push to production on Fridays.</p>
<p>不要在周五上线</p>
<p>34.Separation of concerns.</p>
<p>关注隔离</p>
<p>35.Divide and conquer</p>
<p>分而治之</p>
<p>36.Understand what customer really needs, not what they want.</p>
<p>了解客户真正需要什么，而不是他们想要什么</p>
]]></content:encoded></item><item><title><![CDATA[软件开发中的“最”原则]]></title><description><![CDATA[Principle of least astonishment（最小惊讶原则）

又称最小意外原则，适用于用户界面和软件设计。 它建议系统的组件（代码）应该以大多数用户期望的方式运行。 该行为不应该使用户感到惊讶或诧异，根据操作的名称和其他线索，某项操作的结果应该是明显的、一致的和可预测的。 以下是该原则的正式陈述：“如果一个必要的特征具有很高的让人惊讶的因素，则可能需要重新设计该特征。”
Principle of least effort（用小用力原则）

又称最小阻力路径（path of l...]]></description><link>https://wisely.top/principles-of-most</link><guid isPermaLink="true">https://wisely.top/principles-of-most</guid><category><![CDATA[design principles]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Wed, 27 Jul 2022 03:03:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890832084/yYEmWXqLf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-principle-of-least-astonishment">Principle of least astonishment（最小惊讶原则）</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890854861/Hm1NGEuFC.png" alt="image.png" />
又称最小意外原则，适用于用户界面和软件设计。 它建议系统的组件（代码）应该以大多数用户期望的方式运行。 该行为不应该使用户感到惊讶或诧异，根据操作的名称和其他线索，某项操作的结果应该是明显的、一致的和可预测的。 以下是该原则的正式陈述：“如果一个必要的特征具有很高的让人惊讶的因素，则可能需要重新设计该特征。”</p>
<h2 id="heading-principle-of-least-effort">Principle of least effort（用小用力原则）</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890879222/1Gn64eIOX.png" alt="image.png" /></p>
<p>又称最小阻力路径（path of least resistance），本质上是说人们会通过最少的工作来完成某件事。人们（程序员）将使用最熟悉和易于使用的工具、技术来解决问题。</p>
<h2 id="heading-principle-of-least-knowledge">Principle of Least Knowledge（最小可知原则）</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890902400/TiWmi5Ikm.png" alt="image.png" /></p>
<p>又称迪米特法则（Law of Demeter），根据“信息隐藏”原则，给定对象（类、方法）应尽可能少地假设其他任何事物（包括其子组件）的结构或属性。 它可以被视为最小特权原则（Principle of Lleast privilege）的推论，该原则规定一个模块只拥有其合法目的所必需的信息和资源。</p>
<h2 id="heading-the-last-responsible-moment">The Last Responsible Moment（最后责任时刻）</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890926294/M24nbCe6Q.png" alt="image.png" /></p>
<p>不过早做出仓促的决定，而是推迟承诺且保证重要和不可逆的决策的选项开放，直到不做出决定的成本变得大于做出决定的成本。越晚地做出决策（框架、技术选型），你就有更多的信息来辅助决策。</p>
]]></content:encoded></item><item><title><![CDATA[Spring/Spring Boot编译工具从Maven迁移到了Gradle]]></title><description><![CDATA[如果您正在考虑从 Maven 迁移到 Gradle，我希望了解更多有关 Spring Boot 团队的经验是有用的。 如果你是一个快乐的Maven 用户，请继续使用和支持适合你的工具。

原文地址：https://spring.io/blog/2020/06/08/migrating-spring-boot-s-build-to-gradle

我们在 2.3.0.M1 中对 Spring Boot 进行了相当大的改变。 这是使用 Gradle 而不是 Maven 构建的项目的第一个版本。 关于...]]></description><link>https://wisely.top/spring-and-spring-boot-move-from-maven-to-gradle</link><guid isPermaLink="true">https://wisely.top/spring-and-spring-boot-move-from-maven-to-gradle</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><category><![CDATA[maven]]></category><category><![CDATA[gradle]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Wed, 27 Jul 2022 02:59:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890506344/xJxO21RV8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>如果您正在考虑从 Maven 迁移到 Gradle，我希望了解更多有关 Spring Boot 团队的经验是有用的。 如果你是一个快乐的Maven 用户，请继续使用和支持适合你的工具。</p>
</blockquote>
<p>原文地址：<a target="_blank" href="https://spring.io/blog/2020/06/08/migrating-spring-boot-s-build-to-gradle">https://spring.io/blog/2020/06/08/migrating-spring-boot-s-build-to-gradle</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890528134/nfTFnbICH.png" alt="image.png" /></p>
<p>我们在 2.3.0.M1 中对 Spring Boot 进行了相当大的改变。 这是使用 Gradle 而不是 Maven 构建的项目的第一个版本。 关于迁移的 Twitter 上的帖子 有很多人问我们为什么切换以及我们看到的好处（如果有的话）。 这篇博文旨在回答这些问题。</p>
<p>Spring 产品套件中的每个项目都以相当自主的方式运行。 我们力求在用户最容易看到的地方保持一致性--例如 API 设计，但选择最能满足项目需求的工具就不太明显。 一个例子是构建系统。 对构建系统的改变会影响那些为项目做出贡献的人，但如果我们做对了，它对用户是没有影响的。 这导致混合了基于 Maven 和 Gradle 的构建。 例如，自 2012 年 3.2.0.M1 以来，Spring Framework就一直使用 Gradle 构建；而 Spring Boot一年后诞生的，Spring Cloud也在此后不久开始，两者当时都使用基于 Maven 的构建。 与 Spring Boot 不同的是，Spring Cloud目前没有切换的计划，因为Maven满足他们的需求。 简而言之，如果您从这篇博文中只得到一件事，那就是您应该选择最能满足您项目需求的任何工具。</p>
<h2 id="heading-5oir5lus5li65lua5lmi6kab5yih5o2ipw">我们为什么要切换?</h2>
<p>Spring Boot 团队考虑切换到Gradle的主要原因是减少构建项目所需的时间。 在进行测试修改时，我们对反馈循环的长度感到沮丧。 等待构建完成所花费的时间增加了修复错误和实现新功能所花费的时间。 我们在其他Spring 项目中看到了Gradle 的增量和并行构建以及在第三方项目中的 Gradle 构建缓存的好处。 我们希望我们可以在 Spring Boot 的构建中获得类似的好处。</p>
<p>我们过去曾尝试利用 Maven 对并行构建的支持。 由于 Spring Boot 构建的复杂性，尤其是对 Invoker 插件的使用，我们的尝试失败了。 我们通过在CI（持续集成）上将构建分成四个部分来解决这个问题。 该项目的主要核心是先构建的，然后并行构建三个独立的部分。 这种安排虽然有所帮助，但CI构建任务仍然需要一个小时或更长时间。 此外，由于拆分结构是特定于 CI 构建的，它并没有使开发人员的本地构建更快。</p>
<p>Gradle 有一个构建结构的广泛模型，了解每个任务的输入和输出及其相互依赖关系。 这种建模的承诺是它允许任务并行运行，同时也可以增量、缓存或完全避免。 换句话说，Gradle 旨在最大限度地减少构建任何给定更改所需的工作量，并行执行必要的工作。 如果我们坚持并广泛重组 Spring Boot 的构建，那么使用 Maven 并行构建可能是可行的。 而且，如果我们使用 Gradle Enterprise 的Maven支持，我们也可以享受构建缓存和避免的好处。 然而，为了充分享受这四个方面的好处，我们觉得我们必须尝试切换到 Gradle。</p>
<h2 id="heading-5oir5lus5oco5lmi5yih5o2ipw">我们怎么切换?</h2>
<p>我们看到的对 Gradle 的一种批评是，它导致构建比基于 Maven 的同类产品更难维护和理解。 Gradle的灵活性允许以微妙不同的方式完成任务，即使是在同一构建中的模块之间也是如此。 如果要成功切换，我们需要避免这种情况发生。 我们已经发布了四个Spring Boot 2.3 里程碑（候选版本和 Gradle 的最终版本），看起来已经成功了。 核心团队或任何其他贡献者都没有看到任何重大的构建问题。</p>
<p>Spring Boot 的一个关键特性是“约定优于配置”，我们也将这种方法应用于构建。遵循“ 避免在 build.gradle 文件中包含命令式逻辑 ”的建议，我们编写了几个可以找到的小插件在项目的 <a target="_blank" href="https://github.com/spring-projects/spring-boot/tree/d4c7315369e7e9dce6eb1c77e5f23d1e670247c8/buildSrc">buildSrc</a>中。例如我们有一个starter plugin 应用于每个 Spring Boot 启动模块，确保它们都被一致地配置、构建和发布。我们还有一个约定插件对正在应用的其他插件做出反应，并配置诸如源代码编码、JUnit 平台的使用以及使用<code>-parameters</code>编译等内容。</p>
<p>这种方法导致 <code>build.gradle</code> 文件几乎完全是声明性的。 尽管我们编写了许多插件来应用我们的约定并填补Gradle 生态系统中的空白， 但迁移到 Gradle 的提交 却从代码库中删除了近 9500 行。</p>
<h2 id="heading-5yih5o2i5pyj5aw95ase5zcxpw">切换有好处吗?</h2>
<p>在减少项目的构建时间方面，将构建迁移到 Gradle 无疑是成功的。 如上所述，在 CI 和开发人员自己的机器上，一个完整的基于 Maven 的构建需要一个小时或更长时间。 在过去的四个星期里，使用 Gradle平均成功构建时间为 9 分 22 秒，如下面的屏幕截图所示：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890628955/oZ1XDZRgj.png" alt="image.png" /></p>
<p>我们从 JDK 8 CI 构建发布快照。 专注于那些，它在过去 4 周内成功了 183 次 与 平均构建 时间 19 分 37 秒。 查看成功的本地构建，我们可以看到:</p>
<p>过去 4 周内成功构建了 273 个</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890636919/88Yl7zghcq.png" alt="image.png" /></p>
<p>平均构建时间为 2 分 30 秒</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890644419/1jXf6YIbV.png" alt="image.png" /></p>
<p>Gradle 吸引我们的另一个好处是 我很享受 在为 Testcontainers 做贡献时的体验。 我们希望 Spring Boot 的贡献者能够尽快克隆和构建项目。 感谢远程构建缓存，可以 3 分钟内构建完成，这包括下载大量依赖项所花费的时间。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890655909/AG9uvWWcy.png" alt="image.png" /></p>
<p>如果您对构建性能的更多细节感兴趣，可以在我们的公共 Gradle Enterprise 实例 上获得更多数据。</p>
<p>除了性能改进之外，我们还开始研究其他一些可用的数据。例如，我们已经意识到我们有一些不稳定的测试一段时间了。由于它们，构建失败的频率超出了我们的预期，我们现在可以在 Tests dashboard中看到这一点。我们已经开始使用 Gradle 的易碎测试缓解来识别 CI 上发生的任何易碎测试帮助我们了解我们是否已成功解决或解决问题。</p>
<h2 id="heading-57ut6k66">结论</h2>
<p>我们对迁移的进展以及我们所看到的构建时间的减少感到非常满意。 CI 构建现在平均需要大约 20 分钟，比以前快 3-4 倍。 本地构建平均需要 2 分 30 秒，比以前快 20-30 倍。</p>
<p>我想借此机会感谢 Gradle 团队在迁移过程中提供的帮助，并慷慨地为我们提供了 Gradle Enterprise 许可证以用于我们的开源项目。 我们已经将它与 Spring Framework、Spring Security 和 Spring Boot 一起使用，其他团队计划开始将它用于基于 Gradle 和 Maven 的构建。</p>
<p>我还要感谢我们正在使用的各种第三方插件的维护者。 他们提出了建议的更改并合并了拉取请求，以改进对增量构建和缓存的支持。 没有它们，我们将无法实现我们所看到的构建时间的减少。</p>
<p>如果您正在考虑从 Maven 迁移到 Gradle，我希望了解更多有关 Spring Boot 团队的经验是有用的。 如果你是一个快乐的 Maven 用户，请继续使用和支持适合你的工具。</p>
]]></content:encoded></item><item><title><![CDATA[准备迎接Spring Boot 3.0]]></title><description><![CDATA[原文地址：Preparing for Spring Boot 3.0

Spring Boot 2.0 是 2.x 线的第一个版本，于 2018 年 2 月 28 日发布。我们刚刚发布了 Spring Boot 2.7，这意味着到目前为止，我们已经将 2.x 线维护了超过 4 年。 在此期间，我们总共发布了 95 个不同的版本！
整个 Spring 团队，以及我们社区中的许多贡献者，现在都在为下一代 Spring 做准备。 我们计划在 2022 年 11 月发布 Spring Boot 3.0。...]]></description><link>https://wisely.top/preparing-spring-boot-3</link><guid isPermaLink="true">https://wisely.top/preparing-spring-boot-3</guid><category><![CDATA[Springboot]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Wed, 27 Jul 2022 02:54:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890110754/P9xJrs0ql.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>原文地址：<a target="_blank" href="https://spring.io/blog/2022/05/24/preparing-for-spring-boot-3-0">Preparing for Spring Boot 3.0</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890167684/3WHBj1w8G.png" alt="image.png" />
Spring Boot 2.0 是 2.x 线的第一个版本，于 2018 年 2 月 28 日发布。我们刚刚发布了 Spring Boot 2.7，这意味着到目前为止，我们已经将 2.x 线维护了超过 4 年。 在此期间，我们总共发布了 95 个不同的版本！</p>
<p>整个 Spring 团队，以及我们社区中的许多贡献者，现在都在为下一代 Spring 做准备。 我们计划在 2022 年 11 月发布 Spring Boot 3.0。下一个主要版本将基于 Spring Framework 6.0，并且需要 Java 17 或更高版本。 它也将是第一个使用 Jakarta EE 9 API (<code>jakarta.*</code>) 而不是 EE 8 (<code>javax.*</code>) 的 Spring Boot 版本。</p>
<p>接下来的六个月提供了一个理想的机会，为自己的项目准备这个主要的版本。 在这篇博文中，我们将为您介绍现在可以做的一些事情，以便未来的迁移工作尽可能轻松。</p>
<h2 id="heading-java-17">升级到Java 17</h2>
<p>Spring Boot 3.0 将需要 Java 17，但您无需等到该版本升级到最新的 LTS Java 版本。 任何最近的 Spring Boot 2.x 版本都可以很好地与 Java 17 配合使用。您还可以在自己的代码库中使用 Java 17 功能（例如：record）。</p>
<p>如果可能，我们强烈建议您立即升级 JDK。</p>
<h2 id="heading-spring-boot-27x">升级到最新的Spring Boot 2.7.x</h2>
<p>如果您当前使用的是旧版本的 Spring Boot 2.x，我们强烈建议您升级到 Spring Boot 2.7。 当 Spring Boot 3.0 发布时，我们将提供迁移指南，但它假定您是从 Spring Boot 2.7 而不是早期版本迁移。</p>
<p>我们的版本说明中一直会提供升级指南。 例如，如果您要从 Spring Boot 2.6 升级到 Spring Boot 2.7，就可以按照升级指南操作（<a target="_blank" href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes#upgrading-from-spring-boot-26">https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes#upgrading-from-spring-boot-26</a>）。</p>
<p>如果您从 Spring Boot 2.5 或更早版本升级，我们不建议跳过版本。 分步升级（例如 2.5 → 2.6 → 2.7）通常比尝试直接从 2.5 → 2.7 升级更容易。</p>
<h2 id="heading-5qoa5pl5a55bey5bqf5byd5luj56cb55qe6lcd55so">检查对已废弃代码的调用</h2>
<p>随着 Spring Boot 的发展，我们经常会弃用方法或类并提供替代品。 我们通常会提供 12 个月的重叠期，之后会删除不推荐使用的代码。 您可以在我们的 wiki（<a target="_blank" href="https://github.com/spring-projects/spring-boot/wiki/Deprecations">https://github.com/spring-projects/spring-boot/wiki/Deprecations</a>）找到该政策的详细信息。</p>
<p>Spring Boot 3.0 将删除所有已弃用的代码，因此我们建议您检查现有代码是否已不依赖任何已弃用的方法。 如果提示了弃用警告，值得考虑使用 -Werror Java 编译器选项来使构建失败。</p>
<h2 id="heading-applicationproperties-applicationyaml">从旧版 <code>application.properties</code> 和 <code>application.yaml</code> 处理迁移</h2>
<p>Spring Boot 2.4 改变了<code>application.properties</code> 和 <code>application.yaml</code> 文件的加载方式。 大多数用户都没有注意到这个更改，但一些项目可能已经将<code>spring.config.use-legacy-processing</code> 属性设置为 true 以旧行为运行。</p>
<p>旧版本的这项支持不会出现在 Spring Boot 3.0 中，因此您应该检查您的项目是否未设置<code>spring.config.use-legacy-processing</code>。</p>
<h2 id="heading-spring-mvcpathpatternparser">使用Spring MVC的<code>PathPatternParser</code></h2>
<p>Spring MVC 提供了两种解析模式的方法。 从 Spring Boot 2.6 开始，默认使用 <code>PathPatternParser</code>。</p>
<p>一些应用可能通过设置
<code>spring.mvc.pathmatch.matching-strategy</code> 属性手动切换回 AntPathMatcher 实现。 尽管这将在 Spring Boot 3.0版本继续工作，但我们建议尽量使用 <code>PathPatternParser</code>，因为它提供了更好的性能。</p>
<h2 id="heading-jakarta-ee-9">检查第三方项目是否有 Jakarta EE 9 兼容版本</h2>
<p>Jakarta EE 9 是一个新的顶级 jakarta 包，取代了 EE 8 的 javax 顶级包。 例如，Jakarta EE 8 中的 Servlet 规范使用 <code>javax.servlet</code> 包，但在 EE 9 中已更改为 <code>jakarta.servlet</code>。</p>
<p>一般来说，不可能在同一个项目中混合使用 Java EE 和 Jakarta EE API。 您需要确保您自己的代码以及所有第三方库都使用 <code>jakarta.*</code>包导入。</p>
<p>好消息是大多数维护良好的库都在生产 Jakarta EE 9 兼容的变体。 例如 Hibernate、Thymeleaf、Tomcat、Jetty 和 Undertow 都已经这样做了。</p>
<p>我们建议您花一些时间检查您使用的任何与 Jakarta EE 集成的第三方库，并检查它们是否具有兼容 EE 9 的变体。 我们发现的最常见是导入 Servlet API 的项目。</p>
<h2 id="heading-spring">检查第三方项目是否更新了 Spring 兼容版本</h2>
<p>Spring Framework 6.0 不会与上一代二进制兼容。 如果您正在使用提供Spring集成的第三方jar，您应该检查他们是否正在计划与 Spring Framework 6 兼容的版本。</p>
<h2 id="heading-spring-boot">试用Spring Boot里程碑版本</h2>
<p>尽管我们不推荐将其用于生产，但您可以立即尝试 Spring Boot 3.0 里程碑版本，看看迁移项目的难度会有多难。 在代码的分支上尝试里程碑版本是发现任何潜在问题的好方法。</p>
<p>我们总是对反馈感兴趣，并且在我们发布 GA 版本之前发现错误时会让我们非常高兴。</p>
<p>您可以在
<a target="_blank" href="github.com/spring-projects/spring-boot/issues">github.com/spring-projects/spring-boot/issues</a>提出问题（确保告诉我们您正在使用的 Spring Boot 版本）。</p>
<h2 id="heading-6icd6jmr5zwg5lia5psv5oyb">考虑商业支持</h2>
<p>Spring Boot 2.7 是 2.x 系列中的最后一个计划版本。 我们已将此版本的开源支持再延长 6 个月，直至 2023 年 11 月。</p>
<p>此外，对 Spring Boot 2.7 的商业支持也已延长，有效期至 2025 年 2 月。</p>
<p>您可以在
<a target="_blank" href="spring.io/projects/spring-boot">spring.io/projects/spring-boot</a> 找到项目支持详细信息。 有关商业支持的详细信息，请访问 <a target="_blank" href="tanzu.vmware.com/spring-runtime">tanzu.vmware.com/spring-runtime</a>。</p>
<p>由商业支持请求触发的任何发布都将始终作为开源发布，因此商业客户也可以帮助开源社区。</p>
]]></content:encoded></item><item><title><![CDATA[Gradle大战Maven，胜负已分？]]></title><description><![CDATA["I can’t understand why people are frightened of new ideas. I’m frightened of the old ones." — John Cage
"我不明白为什么人们害怕新想法。 我害怕那些旧的。" - 约翰凯奇。
勇敢迈出舒适区，去冒险吧！

Gradle渐渐地火了起来，但是大部分人还在使用Maven，本文将对Gradle和Maven进行全面的比较，大家可以据此作为选型比较的依据。
Maven和Gradle都属于“构建工具”，都是...]]></description><link>https://wisely.top/maven-vs-gradle</link><guid isPermaLink="true">https://wisely.top/maven-vs-gradle</guid><category><![CDATA[maven]]></category><category><![CDATA[gradle]]></category><dc:creator><![CDATA[yunfei wang]]></dc:creator><pubDate>Wed, 27 Jul 2022 02:46:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658889425953/qAhqIm8Jb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>"I can’t understand why people are frightened of new ideas. I’m frightened of the old ones." — John Cage
"我不明白为什么人们害怕新想法。 我害怕那些旧的。" - 约翰凯奇。
勇敢迈出舒适区，去冒险吧！</p>
</blockquote>
<p>Gradle渐渐地火了起来，但是大部分人还在使用Maven，本文将对Gradle和Maven进行全面的比较，大家可以据此作为选型比较的依据。</p>
<p>Maven和Gradle都属于“构建工具”，都是用来自动化工作，并把我们的源码编译成我们要发布的构件而存在。</p>
<h2 id="heading-5y6g5yy">历史</h2>
<p>maven初始版本诞生于2004年，Gradle诞生于2008年，Maven诞生时间更早，但二者都拥有悠久的历史，它们都拥有优秀成熟的生态。</p>
<ul>
<li>maven初始版本诞生于2004年</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658889516553/0RTSm2yRn.png" alt="image.png" /></p>
<ul>
<li>gradle初始版本诞生于2008年</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658889538190/mILBILx7G.png" alt="image.png" /></p>
<h2 id="heading-5ocn6io9">性能</h2>
<p>Gradle引入了 Maven中缺少的几个性能优化，以提高构建性能。这也是Spring、Spring Boot源码使用Gradle替换Maven的主要原因。</p>
<p>Gradle 构建缓存在本地重用Gradle任务的输出，并在机器之间共享任务输出。 在许多情况下，这将加快平均构建时间。</p>
<p>构建缓存在分支之间切换时也非常有用，因为先前构建的输出被保留并且不必重新创建。 性能节省与上面的缓存构建相当，在测试项目中，Gradle 比 Maven 快 17 到 100 倍。</p>
<p>在小型的多项目编译中，Gradle 在干净构建下速度快 2-3 倍，增量更改速度快 7 倍，缓存 Gradle任务输出时速度快 14 倍。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658889698954/bExSkrm6W.gif" alt="2e942c75f89847aca3c10ace939fe4f8.gif" /></p>
<p>在中型的多项目编译中，Gradle 在干净构建下速度快4-5 倍，增量更改速度快 40 倍，缓存 Gradle任务输出时速度快 13 倍。</p>
<p>在大型大型单体式项目中，Gradle在干净构建下速度快 2-3 倍，增量更改速度快 7 倍，缓存Gradle任务输出时速度快 3 倍。</p>
<h2 id="heading-5luj56cb">代码</h2>
<p>Maven使用的是基于xml的pom.xml，Gradle使用基于Groovy语言的build.gradle。我们比较一下两个相同功能的Spring Boot程序，分别使用Maven和Gradle的区别。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658889752059/UgKVyhZ9Q.png" alt="image.png" /></p>
<p>由图可以看出，相同情况下，build.gradle是33行代码，pom.xml是69行代码。当然这主要的原因是xml本身就是一门很啰嗦的表意语言。也因此，用xml作为配置、协议也越来越少。</p>
<p>由图也可以看出Gradle比Maven易读性更强且更易维护！</p>
<h2 id="heading-5yqf6io9">功能</h2>
<p>从表中可以看出，Gradle的功能是比Maven要更丰富的。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658889965380/m5ka6plwa.png" alt="image.png" /></p>
<h2 id="heading-5rwb6kgm5bqm">流行度</h2>
<p>从各类统计数据来看，Maven使用的流行度、市场占有率是有绝对的领先优势的。但是这也不能说明太多的问题，当年的ie、xp系统、诺基亚手机也都是市场的霸主。</p>
<p>个别的统计有显示Gradle有领先的结果，但这不能说明Gradle真正领先。如由OpenLogic发布的《2022年度开源报告显示》：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658889866540/QjDEqW10e.png" alt="image.png" />
结论
由上述比较，我们发现Gradle有明显于Maven的优势。我提出下面的建议：</p>
<p>1、新项目尝试使用Gradle来构建！</p>
<p>2、尝试迁移Maven的老项目将其使用Gradle构建。（如：Spring/Spring Boot编译工具从Maven迁移到了Gradle）</p>
<p>不愿迁移的理由很多：</p>
<p>1、对于Maven很熟悉了，不用迁移到新的工具！</p>
<p>2、又要学习一个新东西，卷不动了！</p>
<p>我觉得无论作为什么样的职业，对新的东西要保持敏锐的感知和学习，不要停留在自己舒适的区域，无论Gradle有没有那么优秀，或者有没有那么多问题，我们要坚持尝试一下。</p>
<p>最后送给大家一句话：</p>
<p>"I can’t understand why people are frightened of new ideas. I’m frightened of the old ones." — John Cage
"我不明白为什么人们害怕新想法。 我害怕那些旧的。" - 约翰凯奇。</p>
<p>勇敢迈出舒适区，去冒险吧！</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658889892621/QRFOYv151.png" alt="image.png" /></p>
]]></content:encoded></item></channel></rss>