作为一个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
暂无评论