SpringBoot 3 new features

Table of contents

    • 1. GraalVM
    • 1.1 Generate local executable application
  • 1.2 Generate docker image
  • 2. Support virtual threads
    • 2.1 Stress test when virtual thread is not enabled
  • 2.2 Stress test when virtual thread is enabled
  • 3. HTTP Interface

1. GraalVM

  • Using GraalVM to compile SpringBoot applications into local executable image files can significantly improve startup speed, peak performance, and reduce memory applications.
  • Traditional applications are compiled into bytecode, then interpreted by the JVM and finally compiled into machine code for running. Spring Native is compiled into machine code in advance through AOT, and is directly statically compiled into an executable file at runtime, such as windows. .exe file on, does not rely on JVM. GraalVM's just-in-time and AOT compilers can significantly improve application performance.
  • AOT: Ahead-of-Time Compilation, precompilation was proposed as an experimental feature in Java9. Compile Java classes into native code, reducing startup time and memory usage of Java applications.

1.1 Generate local executable application

  • Plug-ins used by maven
<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
</plugin> 
  • Generate local application commands
mvn -Pnative native:compile 
  • gradle requires the following plugins
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.2'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.graalvm.buildtools.native' version '0.9.28'
} 
  • Generate local application commands
gradle nativeCompile 
  • Or click nativeCompile under build in IDEA

Insert image description here

  • Finally, a local executable file will be generated under build/native/nativeCompile, which can be run by double-clicking it.

Insert image description here

  • Startup speed is only milliseconds

Insert image description here

  • Reference https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html#native-image.developing-your-first-application.native-build-tools.maven

1.2 Generate docker image

  • Commands used by maven
mvn -Pnative spring-boot:build-image 
  • Commands used by gradle
gradle bootBuildImage 
  • Or click bootBuildImage under build in IDEA
  • Because other dependencies will be pulled through docker, you need to start docker at this time. Through the docker client, you will find that there are 3 more images. The created time displayed later is wrong, so you don’t need to care.
paketobuildpacks/run-jammy-tiny
paketobuildpacks/builder-jammy-tiny
# tptpbfysrtIt will be different every time
pack.local/builder/tptpbfysrt 

Insert image description here

  • Then it will automatically use pack.local/builder/... to start a container to create the image, which will download a lot of things. If the execution fails, just click start again to run it. The downloaded files will not be re-downloaded. Don't Re-execute the gradle bootBuildImage command, otherwise the process of downloading dependencies will be executed from scratch, which is very time-consuming.

Insert image description here

  • After successful execution, a newly generated image file will appear in images.

Insert image description here

  • Start using the following command
# myproject:0.0.1-SNAPSHOTChange to your own project name and version
docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT 

Insert image description here

  • Reference https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html#native-image.developing-your-first-application.buildpacks.maven

2. Support virtual threads

  • Spring Boot3.2 starts virtual threads, you need to use JDK21 and set the following properties
spring.threads.virtual.enabled=true 
  • When virtual threads are enabled, Tomcat and Jetty will use virtual threads to handle requests. This means that application code that handles network requests (such as methods in a Controller) will run on a virtual thread. The following is the code for Tomcat to enable virtual threads, in tomcat-embed-core-10.1.19.jar!\org\apache\tomcat\util\net\AbstractEndpoint.class
 public void createExecutor() {
        internalExecutor = true;
        if (getUseVirtualThreads()) {
            executor = new VirtualThreadExecutor(getName() + "-virt-");
        } else {
            TaskQueue taskqueue = new TaskQueue();
            TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
            executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
            taskqueue.setParent( (ThreadPoolExecutor) executor);
        }
    } 
  • Create a controller

Insert image description here

  • Start the program, call the hello interface, and view the log printing
2024-02-24T16:41:15.778+08:00  INFO 14252 --- [omcat-handler-0] com.example.demo.Controller : [Controller][hello] VirtualThread[#46,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1 

Insert image description here

  • It can be seen from VirtualThread[#46,tomcat-handler-0] that a virtual thread is used
  • Close the virtual thread, call the interface again, and view the log printout
2024-02-24T16:43:14.715+08:00  INFO 15844 --- [nio-8080-exec-1] com.example.demo.Controller : [Controller][hello] Thread[#39,http-nio-8080-exec-1,5,main] 

Insert image description here

  • Through Thread[#39,http-nio-8080-exec-1,5,main] we can see that the platform thread is used

2.1 Stress test when virtual thread is not enabled

  • You need to install the pressure measurement tool plow in advance
# 100000requests,500concurrent
plow http://localhost:8080/hello -c 500 -n 100000 
  • An average of 3889.768 requests are processed per second, and the delay is mainly concentrated around 115 to 140ms.

Insert image description here

2.2 Stress test when virtual thread is enabled

  • An average of 9101.021 requests are processed per second, and the delay is mainly concentrated at 51.488ms.

Insert image description here

3. HTTP Interface

  • Define the HTTP service as an interface with the @HttpExchange method and pass such an interface to the HttpServiceProxyFactory to create a proxy that performs requests through an HTTP client (such as RestClient or WebClient). Similar to Feign, it uses a declarative approach to access HTTP services. You can refer to https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface

  • Create an http interface

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;

@HttpExchange("/api")
public interface HelloClient {

    @GetExchange("/hello")
    String hello(@RequestParam String msg);

} 
  • Inject a declarative client. By injecting the client with the target interface baseUrl into the HttpServiceProxyFactory, you can use RestClient, WebClient, or RestTemplate. RestClient is used here.
import com.example.springboot3.client.HelloClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

@Configuration
public class AppConfig {

    @Bean
    public HelloClient toClient() {
        RestClient restClient = RestClient.builder().baseUrl("http://localhost:80/").build();
        HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory.builderFor(RestClientAdapter.create(restClient)).build();
        return httpServiceProxyFactory.createClient(HelloClient.class);
    }

} 
  • Controller
import com.example.springboot3.client.HelloClient;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Resource
    private HelloClient client;

    @GetMapping("hello")
    public String hello(@RequestParam String msg) {
        return client.hello(msg);
    }

} 
  • More specific modifications can be found at https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes