
作为一个Java开发人员,进程有时会遭受更长的启动时间和相对较高的内存使用率。 我们将寻找一个令人兴奋的解决方案。
在本文中,我们将介绍一个简单的Spring Boot应用程序,然后使用GraalVM将其转换为本机映像。
如果你不知道GraalVM,别担心。我们一会儿再讨论这个问题。现在,请记住,它是一个提供提前编译(AOT)的JVM,与运行时的即时编译相结合。
我们先来讨论一下今天要编写的Spring Boot应用程序(我们将在这里使用Maven,但您也可以使用Gradle)。
应用程序将公开两个REST端点,这两个端点都将接受httpget请求。
- 第一个端点应该提供与Github用户(/users/{githubUserName})对应的信息。
- 第二个端点应该提供与Github存储库的贡献者相对应的信息(/contributors/{githubOrgName}/{githubRepoName})。
GithubController可以定义如下-
@RestController
public class GithubController {
  private final GithubClient githubClient;
  @Autowired
  public GithubController(GithubClient githubClient) {
    this.githubClient = githubClient;
  }
  @GetMapping("/users/{userName}")
  public ResponseEntity<User> getUser(@PathVariable String userName) {
    ResponseEntity<User> user = this.githubClient.fetchUser(userName);
    return ResponseEntity.ok(user.getBody());
  }
  @GetMapping("/contributors/{orgName}/{projectName}")
  public ResponseEntity<User[]> getProjectContributors(@PathVariable String orgName,
                                                       @PathVariable String projectName) {
    ResponseEntity<User[]> contributors = this.githubClient.fetchContributors(orgName, projectName);
    return ResponseEntity.ok(contributors.getBody());
  }
}我们在控制器中自动连接了一个类GithubClient,并使用它获取用户或参与者的详细信息。
@Component
public class GithubClient {
  private final RestTemplate restTemplate;
  private static final String USERS_URL = "https://api.github.com/users/{userName}";
  private static final String CONTRIBUTORS_URL = "https://api.github.com/repos/{owner}/{repo}/contributors";
  @Autowired
  public GithubClient(RestTemplateBuilder restTemplateBuilder) {
    this.restTemplate = restTemplateBuilder.build();
  }
  public ResponseEntity<User> fetchUser(String userName) {
    return this.restTemplate.getForEntity(USERS_URL, User.class, userName);
  }
  public ResponseEntity<User[]> fetchContributors(String orgName, String repoName) {
    return this.restTemplate.getForEntity(CONTRIBUTORS_URL, User[].class, orgName, repoName);
  }
}这里,我们使用RestTemplate来调用Github APIs。
现在,让我们看一下User DTO类,它将被用作REST响应。
public class User {
  private final String login;
  private final String avatarUrl;
  private final String name;
  private final String company;
  private final String blogUrl;
  private final Integer numPublicRepos;
  private final Integer numContributions;
  private final String htmlUrl;
  @JsonCreator
  public User(@JsonProperty("login") String login,
              @JsonProperty("avatar_url") String avatarUrl,
              @JsonProperty("name") String name,
              @JsonProperty("company") String company,
              @JsonProperty("blog") String blogUrl,
              @JsonProperty("public_repos") Integer numPublicRepos,
              @JsonProperty("contributions") Integer numContributions,
              @JsonProperty("html_url") String htmlUrl) {
    this.login = login;
    this.avatarUrl = avatarUrl;
    this.name = name;
    this.company = company;
    this.blogUrl = blogUrl;
    this.numPublicRepos = numPublicRepos;
    this.numContributions = numContributions;
    this.htmlUrl = htmlUrl;
  }
  
  public String getLogin() {
    return login;
  }
  public String getAvatarUrl() {
    return avatarUrl;
  }
  // other getters
  // ....
}这对于完成任务来说已经足够了,但是让我们为我们的应用程序添加更多的香料。
Github API规定了一个速率限制,因此一个给定的IP在一小时内不能发出超过60个请求。我们可以使用Github身份验证令牌来提高这个速率限制。
让我们将GithubProperties定义为Spring Boot ConfigurationProperties。
@ConfigurationProperties("github")
@Validated
public class GithubProperties {
  /**
   * Github API token ("user:token")
   */
  @Pattern(regexp = "\\w+:\\w+")
  private String token;
  public String getToken() {
    return token;
  }
  
  public void setToken(String token) {
    this.token = token;
  }
}因此,现在我们可以在application.properties/application.yaml中添加一个名为github.token的属性。自@Validated和@Pattern在应用程序启动时使用模式注释-将确保属性遵循所提到的正则表达式。
现在,在尝试调用Github API时如何使用这个令牌?好吧,让我们实现一个Spring RestTemplate拦截器。
public class GithubAppTokenInterceptor implements ClientHttpRequestInterceptor {
  private final String token;
  public GithubAppTokenInterceptor(String token) {
    this.token = token;
  }
  @Override
  public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
                                      ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
    if (StringUtils.hasText(this.token)) {
      byte[] basicAuthValue = this.token.getBytes(StandardCharsets.UTF_8);
      httpRequest.getHeaders().set(HttpHeaders.AUTHORIZATION,
                                   "Basic " + Base64Utils.encodeToString(basicAuthValue));
    }
    return clientHttpRequestExecution.execute(httpRequest, bytes);
  }
}在这里,GithubAppTokenInterceptor的intercept函数使用提供的令牌的编码值添加授权请求头。
public class RateLimitHeaderInterceptor implements ClientHttpRequestInterceptor {
  Logger log = LoggerFactory.getLogger(RateLimitHeaderInterceptor.class);
  @Override
  public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
                                      ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
    ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
    final String remainingRateLimit = response.getHeaders().getFirst("X-RateLimit-Remaining");
    assert remainingRateLimit != null;
    if (Integer.parseInt(remainingRateLimit) > 0) {
      log.info("Remaining Rate Limit: - {}", remainingRateLimit);
    }
    else {
      log.warn("API Rate limit has exceeded");
    }
    return response;
  }
}RateLimitHeaderInterceptor的截取函数提取X-RateLimit-Remaining响应头,并提供日志语句以提供有关剩余速率限制的信息。
为了使用这两个拦截器,我们只需要在初始化restemplate对象时提供这些拦截器
@Autowired
  public GithubClient(RestTemplateBuilder restTemplateBuilder, GithubProperties githubProperties) {
    this.restTemplate =
        restTemplateBuilder.additionalInterceptors(new GithubAppTokenInterceptor(githubProperties.getToken()))
                           .additionalInterceptors(new RateLimitHeaderInterceptor())
                           .build();
  }最后一段代码是应用程序的入口点。
@SpringBootApplication
@EnableConfigurationProperties(GithubProperties.class)
public class RestApplication {
  public static void main(String[] args) {
    SpringApplication.run(RestApplication.class, args);
  }
}我们目前讨论的代码可以在这里找到:https://github.com/shivamgarg7276/graal-spring-native/tree/main/spring-boot-without-aot
现在,使用mvn clean install构建。然后运行mvn spring-boot:run command 应用程序将从端口8080启动。


请求:http://localhost:8080/users/shivamgarg7276 返回:
{
    "login": "shivamgarg7276",
    "name": "Shivam Garg",
    "company": "Nutanix",
    "avatarUrl": "https://avatars.githubusercontent.com/u/49524850?v=4",
    "blogUrl": "https://www.linkedin.com/in/shivam-garg-067b46141/",
    "numPublicRepos": 1,
    "htmlUrl": "https://github.com/shivamgarg7276"
}因此,在我的机器上启动大约需要1.5秒,内存消耗大约是200MB。这就完成了任务,但我们将看到如何使用GraalVM对其进行大规模改进。
什么是GraalVM,为什么它是JAVA应用程序的未来?
GraalVM-字节码到位码
GraalVM是一个高性能的多语言编译器,有助于实现:
1. 提高应用程序吞吐量并减少延迟
2. 将应用程序编译成小型独立的本机二进制文件
3. 无缝地使用多种语言和库
它既可以用于java JIT,也可以用于AOT编译。
对于AOT编译,它使用本机映像生成器或本机映像技术将Java代码提前编译为独立的可执行文件。它处理应用程序的所有类及其依赖关系,包括来自JDK的类。它静态地分析这些数据,以确定在应用程序执行期间哪些类和方法是可访问的。
您可以使用我在这里强调的步骤在系统上安装GraalVM:
https://github.com/shivamgarg7276/graal-spring-native/blob/main/README.md
Spring Native Beta
一段时间前,Spring团队宣布了Spring Native Beta项目,以支持使用GraalVM将Spring应用程序编译为原生映像。
内存消耗有望大大降低,而且启动几乎是即时的!
现在,让我们切换设备,对原来的Spring Boot应用程序进行一些更改,将其转换为本机可执行的Spring应用程序。
在计算机上安装GraalVM后,需要在原始应用程序中执行这些更改-
添加org.springframework.experimental:spring-native-image maven依赖。
<dependency>
  <groupId>org.springframework.experimental</groupId>
  <artifactId>spring-native</artifactId>
  <version>0.9.2</version>
</dependency>添加Spring AOT插件:
<plugin>
  <groupId>org.springframework.experimental</groupId>
  <artifactId>spring-aot-maven-plugin</artifactId>
  <version>0.9.2</version>
  <executions>
    <execution>
      <id>test-generate</id>
      <goals>
        <goal>test-generate</goal>
      </goals>
    </execution>
    <execution>
      <id>generate</id>
      <goals>
        <goal>generate</goal>
      </goals>
    </execution>
  </executions>
</plugin>添加在package阶段触发插件的GraalVM本机映像配置文件
<profiles>
  <profile>
    <id>native-image</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.graalvm.nativeimage</groupId>
          <artifactId>native-image-maven-plugin</artifactId>
          <version>21.0.0.2</version>
          <configuration>
            <!-- The native image build needs to know the entry point to your application -->
            <mainClass>com.example.graal.restserver.main.RestApplicationWithAOT</mainClass>
            <buildArgs>
              <buildArg>--enable-https</buildArg>
            </buildArgs>
          </configuration>
          <executions>
            <execution>
              <goals>
                <goal>native-image</goal>
              </goals>
              <phase>package</phase>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>这里提供了mainClass参数,它指向应用程序的入口点,即用SpringBootApplication注释的类。
另外,buildArgs在使用RestTemplate调用基于https的Github url时启用了https。
到目前为止我们讨论的代码更改可以在这里找到:https://github.com/shivamgarg7276/graal-spring-native/tree/main/spring-boot-with-aot
只需使用以下命令即可构建模块:
mvn -Pnative-image clean package这将在模块的目标文件夹下创建一个包含Spring Boot应用程序的本机可执行文件。
简单地调用:
target/com.example.graal.restserver.main.restapplicationwithaot
这将在端口8081启动Spring Boo服务器。


如果你现在看到,我的机器的启动时间是0.093秒,太快了。
而且,内存消耗显示在40MB以下。
您还可以使用GraalVM+Spring Native生成一个优化的容器映像,它可以很容易地部署,并且包含一个最小的OS层。
结论
显然,您应该在您的机器上测试以比较这些数字。但是,这清楚地证明了GraalVM的真正威力,以及为什么这将是Java应用程序的未来。
正如一些业内专家所说:

我还没有讨论本地映像的许多方面,将在本文的后续部分中讨论。我们将研究一些需要高级配置设置的角落案例,以及一些其他优化。
GraalVM官网:https://www.graalvm.org/
原文链接:https://blog.devgenius.io/spring-boot-application-with-graalvm-native-image-8074034ba35f
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2016.html

 
								 
								 
								 
								 在这个努力程度如此低下的时代,还轮不到比拼天赋。静下心来,just do it
在这个努力程度如此低下的时代,还轮不到比拼天赋。静下心来,just do it

暂无评论