1. Overview
1.概述
Helidon is the new Java microservice framework that has been open sourced recently by Oracle. It was used internally in Oracle projects under the name J4C (Java for Cloud).
Helidon是最近由Oracle开源的新的Java微服务框架。它曾以J4C(Java for Cloud)的名义在甲骨文项目内部使用。
In this tutorial, we’ll cover the main concepts of the framework and then we’ll move to build and run a Helidon based microservice.
在本教程中,我们将介绍该框架的主要概念,然后我们将转向构建和运行一个基于Helidon的微服务。
2. Programming Model
2.编程模型
Currently, the framework supports two programming models for writing microservices: Helidon SE and Helidon MP.
目前,该框架支持两种编写微服务的编程模型。Helidon SE和Helidon MP。
While Helidon SE is designed to be a microframework that supports the reactive programming model, Helidon MP, on the other hand, is an Eclipse MicroProfile runtime that allows the Jakarta EE community to run microservices in a portable way.
Helidon SE被设计成一个支持反应式编程模型的微框架,而Helidon MP则是一个Eclipse MicroProfile运行时,允许Jakarta EE社区以可移植方式运行微服务。
In both cases, a Helidon microservice is a Java SE application that starts a tinny HTTP server from the main method.
在这两种情况下,一个Helidon微服务是一个Java SE应用程序,它从main方法中启动一个tinny HTTP服务器。
3. Helidon SE
3.Helidon SE
In this section, we’ll discover in more details the main components of Helidon SE: WebServer, Config, and Security.
在本节中,我们将更详细地了解Helidon SE的主要组件:WebServer、Config和Security。
3.1. Setting up the WebServer
3.1.设置Web服务器
To get started with the WebServer API, we need to add the required Maven dependency to the pom.xml file:
要开始使用WebServer API,我们需要在pom.xml文件中添加必要的Maven依赖。
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver</artifactId>
<version>0.10.4</version>
</dependency>
To have a simple web application, we can use one of the following builder methods: WebServer.create(serverConfig, routing) or just WebServer.create(routing). The last one takes a default server configuration allowing the server to run on a random port.
为了拥有一个简单的Web应用程序,我们可以使用以下构建方法之一。WebServer.create(serverConfig, routing)或者直接使用WebServer.create(routing)。最后一个方法采用默认的服务器配置,允许服务器在一个随机的端口上运行。
Here’s a simple Web application that runs on a predefined port. We’ve also registered a simple handler that will respond with greeting message for any HTTP request with ‘/greet’ path and GET Method:
这里有一个简单的Web应用程序,在一个预定义的端口上运行。我们还注册了一个简单的处理程序,它将对任何带有’/greet’路径和GET方法的HTTP请求做出问候信息的回应。
public static void main(String... args) throws Exception {
ServerConfiguration serverConfig = ServerConfiguration.builder()
.port(9001).build();
Routing routing = Routing.builder()
.get("/greet", (request, response) -> response.send("Hello World !")).build();
WebServer.create(serverConfig, routing)
.start()
.thenAccept(ws ->
System.out.println("Server started at: http://localhost:" + ws.port())
);
}
The last line is to start the server and wait for serving HTTP requests. But if we run this sample code in the main method, we’ll get the error:
最后一行是启动服务器并等待服务HTTP请求。但如果我们在main方法中运行这段示例代码,我们会得到错误。
Exception in thread "main" java.lang.IllegalStateException:
No implementation found for SPI: io.helidon.webserver.spi.WebServerFactory
The WebServer is Actually an SPI, and we need to provide a runtime implementation. Currently, Helidon provides the NettyWebServer implementation which is based on Netty Core.
WebServer实际上是一个SPI,我们需要提供一个运行时的实现。目前,Helidon提供了NettyWebServer实现,它基于Netty Core。
Here’s the Maven dependency for this implementation:
下面是该实现的Maven依赖性。
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver-netty</artifactId>
<version>0.10.4</version>
<scope>runtime</scope>
</dependency>
Now, we can run the main application and check that it works by invoking the configured endpoint:
现在,我们可以运行主应用程序,并通过调用配置的端点来检查它是否工作。
http://localhost:9001/greet
In this example, we configured both the port and the path using the builder pattern.
在这个例子中,我们使用构建器模式配置了端口和路径。
Helidon SE also allows using a config pattern where the configuration data is provided by the Config API. This the subject of the next section.
Helidon SE也允许使用一个配置模式,其中配置数据由Config API提供。这是下一节的主题。
3.2. The Config API
3.2.Config API
The Config API provides tools for reading configuration data from a configuration source.
Config API提供了从配置源读取配置数据的工具。
Helidon SE provides implementations for many configuration sources. The default implementation is provided by helidon-config where the configuration source is an application.properties file located under the classpath:
Helidon SE为许多配置源提供了实现。默认实现是由helidon-config提供的,其中配置源是位于classpath下的application.properties文件。
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config</artifactId>
<version>0.10.4</version>
</dependency>
To read the configuration data, we just need to use the default builder which by default takes the configuration data from application.properties:
要读取配置数据,我们只需要使用默认的构建器,它默认从application.properties:获取配置数据。
Config config = Config.builder().build();
Let’s create an application.properties file under the src/main/resource directory with the following content:
让我们在src/main/resource目录下创建一个application.properties文件,内容如下。
server.port=9080
web.debug=true
web.page-size=15
user.home=C:/Users/app
To read the values we can use the Config.get() method followed by a convenient casting to the corresponding Java types:
为了读取这些值,我们可以使用Config.get()方法,然后再方便地转换为相应的Java类型。
int port = config.get("server.port").asInt();
int pageSize = config.get("web.page-size").asInt();
boolean debug = config.get("web.debug").asBoolean();
String userHome = config.get("user.home").asString();
In fact, the default builder loads the first found file in this priority order: application.yaml, application.conf, application.json, and application.properties. The last three format needs an extra related config dependency. For example, to use the YAML format, we need to add the related YAML config dependency:
事实上,默认的构建器会按照这个优先顺序加载第一个找到的文件。application.yaml, application.conf, application.json, and application.properties. 最后三种格式需要额外的相关配置依赖。例如,要使用YAML格式,我们需要添加相关的YAML config依赖。
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-yaml</artifactId>
<version>0.10.4</version>
</dependency>
And then, we add an application.yml:
然后,我们添加一个application.yml。
server:
port: 9080
web:
debug: true
page-size: 15
user:
home: C:/Users/app
Similarly, to use the CONF, which is a JSON simplified format, or JSON formats, we need to add the helidon-config-hocon dependency.
同样地,为了使用CONF,也就是JSON简化格式,或者JSON格式,我们需要添加helidon-config-hocon依赖项。
Note that the configuration data in these files can be overridden by environment variables and Java System properties.
请注意,这些文件中的配置数据可以被环境变量和Java系统属性所覆盖。
We can also control the default builder behavior by disabling Environment variable and System properties or by specifying explicitly the configuration source:
我们还可以通过禁用环境变量和系统属性或明确指定配置源来控制默认构建器行为。
ConfigSource configSource = ConfigSources.classpath("application.yaml").build();
Config config = Config.builder()
.disableSystemPropertiesSource()
.disableEnvironmentVariablesSource()
.sources(configSource)
.build();
In addition to reading configuration data from the classpath, we can also use two external sources configurations, that is, the git and the etcd configs. For this, we need the helidon-config-git and helidon-git-etcd dependencies.
除了从classpath读取配置数据外,我们还可以使用两个外部源配置,即git和etcd配置。为此,我们需要helidon-config-git和helidon-git-etcd依赖项。
Finally, if all of these configuration sources don’t satisfy our need, Helidon allows us to provide an implementation for our configuration source. For example, we can provide an implementation that can read the configuration data from a database.
最后,如果所有这些配置源不能满足我们的需要,Helidon允许我们为我们的配置源提供一个实现。例如,我们可以提供一个可以从数据库中读取配置数据的实现。
3.3. The Routing API
3.3.Routing API
The Routing API provides the mechanism by which we bind HTTP requests to Java methods. We can accomplish this by using the request method and path as matching criteria or the RequestPredicate object for using more criteria.
Routing API提供了我们将HTTP请求绑定到Java方法的机制。我们可以通过使用请求方法和路径作为匹配标准,或者使用RequestPredicate对象来使用更多标准来实现这一目的。
So, to configure a route, we can just use the HTTP method as criteria:
因此,要配置一个路由,我们可以直接使用HTTP方法作为标准。
Routing routing = Routing.builder()
.get((request, response) -> {} );
Or we can combine the HTTP method with the request path:
或者我们可以把HTTP方法和请求路径结合起来。
Routing routing = Routing.builder()
.get("/path", (request, response) -> {} );
We can also use the RequestPredicate for more control. For example, we can check for an existing header or for the content type:
我们还可以使用RequestPredicate进行更多的控制。例如,我们可以检查现有的标头或内容类型。
Routing routing = Routing.builder()
.post("/save",
RequestPredicate.whenRequest()
.containsHeader("header1")
.containsCookie("cookie1")
.accepts(MediaType.APPLICATION_JSON)
.containsQueryParameter("param1")
.hasContentType("application/json")
.thenApply((request, response) -> { })
.otherwise((request, response) -> { }))
.build();
Until now, we have provided handlers in the functional style. We can also use the Service class which allows writing handlers in a more sophisticated manner.
到目前为止,我们已经以函数式的方式提供了处理程序。我们还可以使用Service类,它允许以更复杂的方式编写处理程序。
So, let’s first create a model for the object we’re working with, the Book class:
因此,让我们首先为我们正在处理的对象创建一个模型,即Book类。
public class Book {
private String id;
private String name;
private String author;
private Integer pages;
// ...
}
We can create REST Services for the Book class by implementing the Service.update() method. This allows configuring the subpaths of the same resource:
我们可以通过实现Service.update()方法来为Book类创建REST服务。这允许配置同一资源的子路径:。
public class BookResource implements Service {
private BookManager bookManager = new BookManager();
@Override
public void update(Routing.Rules rules) {
rules
.get("/", this::books)
.get("/{id}", this::bookById);
}
private void bookById(ServerRequest serverRequest, ServerResponse serverResponse) {
String id = serverRequest.path().param("id");
Book book = bookManager.get(id);
JsonObject jsonObject = from(book);
serverResponse.send(jsonObject);
}
private void books(ServerRequest serverRequest, ServerResponse serverResponse) {
List<Book> books = bookManager.getAll();
JsonArray jsonArray = from(books);
serverResponse.send(jsonArray);
}
//...
}
We’ve also configured the Media Type as JSON, so we need the helidon-webserver-json dependency for this purpose:
我们还将媒体类型配置为JSON,因此我们需要helidon-webserver-json依赖来实现这一目的。
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver-json</artifactId>
<version>0.10.4</version>
</dependency>
Finally, we use the register() method of the Routing builder to bind the root path to the resource. In this case, Paths configured by the service are prefixed by the root path:
最后,我们使用Routing构建器的register()方法将根路径绑定到资源上。在这种情况下,由服务配置的Paths以根路径为前缀。
Routing routing = Routing.builder()
.register(JsonSupport.get())
.register("/books", new BookResource())
.build();
We can now start the server and check the endpoints:
我们现在可以启动服务器并检查端点。
http://localhost:9080/books
http://localhost:9080/books/0001-201810
3.4. Security
3.4.安全
In this section, we’re going to secure our resources using the Security module.
在本节中,我们将使用安全模块来保护我们的资源。
Let’s start by declaring all the necessary dependencies:
让我们从声明所有必要的依赖性开始。
<dependency>
<groupId>io.helidon.security</groupId>
<artifactId>helidon-security</artifactId>
<version>0.10.4</version>
</dependency>
<dependency>
<groupId>io.helidon.security</groupId>
<artifactId>helidon-security-provider-http-auth</artifactId>
<version>0.10.4</version>
</dependency>
<dependency>
<groupId>io.helidon.security</groupId>
<artifactId>helidon-security-integration-webserver</artifactId>
<version>0.10.4</version>
</dependency>
The helidon-security, helidon-security-provider-http-auth, and helidon-security-integration-webserver dependencies are available from Maven Central.
helidon-security、helidon-security-provider-http-auth和helidon-security-integration-webserver>依赖项可以从Maven Central获得。
The security module offers many providers for authentication and authorization. For this example, we’ll use the HTTP basic authentication provider as it’s fairly simple, but the process for other providers is almost the same.
安全模块提供了许多认证和授权的提供者。在这个例子中,我们将使用HTTP基本认证提供者,因为它相当简单,但其他提供者的过程几乎相同。
The first thing to do is create a Security instance. We can do it either programmatically for simplicity:
首先要做的是创建一个Security实例。为了简单起见,我们可以通过编程来完成。
Map<String, MyUser> users = //...
UserStore store = user -> Optional.ofNullable(users.get(user));
HttpBasicAuthProvider httpBasicAuthProvider = HttpBasicAuthProvider.builder()
.realm("myRealm")
.subjectType(SubjectType.USER)
.userStore(store)
.build();
Security security = Security.builder()
.addAuthenticationProvider(httpBasicAuthProvider)
.build();
Or we can use a configuration approach.
或者我们可以使用配置方法。
In this case, we’ll declare all the security configuration in the application.yml file which we load through the Config API:
在这种情况下,我们将在application.yml文件中声明所有的安全配置,我们通过Config API加载。
#Config 4 Security ==> Mapped to Security Object
security:
providers:
- http-basic-auth:
realm: "helidon"
principal-type: USER # Can be USER or SERVICE, default is USER
users:
- login: "user"
password: "user"
roles: ["ROLE_USER"]
- login: "admin"
password: "admin"
roles: ["ROLE_USER", "ROLE_ADMIN"]
#Config 4 Security Web Server Integration ==> Mapped to WebSecurity Object
web-server:
securityDefaults:
authenticate: true
paths:
- path: "/user"
methods: ["get"]
roles-allowed: ["ROLE_USER", "ROLE_ADMIN"]
- path: "/admin"
methods: ["get"]
roles-allowed: ["ROLE_ADMIN"]
And to load it, we need just to create a Config object and then we invoke the Security.fromConfig() method:
为了加载它,我们只需要创建一个Config对象,然后我们调用Security.fromConfig()方法。
Config config = Config.create();
Security security = Security.fromConfig(config);
Once we have the Security instance, we first need to register it with the WebServer using the WebSecurity.from() method:
一旦我们有了Security实例,我们首先需要使用WebSecurity.from()方法在WebServer上注册它:。
Routing routing = Routing.builder()
.register(WebSecurity.from(security).securityDefaults(WebSecurity.authenticate()))
.build();
We can also create a WebSecurity instance directly using the config approach by which we load both the security and the web server configuration:
我们也可以直接使用config方法创建一个WebSecurity实例,通过这种方法,我们同时加载安全和Web服务器配置。
Routing routing = Routing.builder()
.register(WebSecurity.from(config))
.build();
We can now add some handlers for the /user and /admin paths, start the server and try to access them:
我们现在可以为/user和/admin路径添加一些处理程序,启动服务器并尝试访问它们。
Routing routing = Routing.builder()
.register(WebSecurity.from(config))
.get("/user", (request, response) -> response.send("Hello, I'm Helidon SE"))
.get("/admin", (request, response) -> response.send("Hello, I'm Helidon SE"))
.build();
4. Helidon MP
4.赫利顿MP
Helidon MP is an implementation of Eclipse MicroProfile and also provides a runtime for running MicroProfile based microservices.
Helidon MP是Eclipse MicroProfile的一个实现,也为运行基于MicroProfile的微服务提供了一个运行时。
As we already have an article about Eclipse MicroProfile, we’ll check out that source code and modify it to run on Helidon MP.
由于我们已经有一篇关于Eclipse MicroProfile的文章,我们将查看该源代码并将其修改为在Helidon MP上运行。
After checking out the code, we’ll remove all dependencies and plugins and add the Helidon MP dependencies to the POM file:
在检查完代码后,我们将删除所有的依赖和插件,并在POM文件中添加Helidon MP的依赖。
<dependency>
<groupId>io.helidon.microprofile.bundles</groupId>
<artifactId>helidon-microprofile-1.2</artifactId>
<version>0.10.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
<version>2.26</version>
</dependency>
The helidon-microprofile-1.2 and jersey-media-json-binding dependencies are available from Maven Central.
helidon-microprofile-1.2和jersey-media-json-binding依赖项可以从Maven Central获得。
Next, we’ll add the beans.xml file under the src/main/resource/META-INF directory with this content:
接下来,我们将在src/main/resource/META-INF目录下添加beans.xml文件,并加入该内容。
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
version="2.0" bean-discovery-mode="annotated">
</beans>
In the LibraryApplication class, override getClasses() method so that the server won’t scan for resources:
在LibraryApplication类中,覆盖getClasses()方法,这样服务器就不会扫描资源。
@Override
public Set<Class<?>> getClasses() {
return CollectionsHelper.setOf(BookEndpoint.class);
}
Finally, create a main method and add this code snippet:
最后,创建一个main方法并添加这个代码段。
public static void main(String... args) {
Server server = Server.builder()
.addApplication(LibraryApplication.class)
.port(9080)
.build();
server.start();
}
And that’s it. We’ll now be able to invoke all the book resources.
就这样了。我们现在就可以调用所有的图书资源了。
5. Conclusion
5.结论
In this article, we’ve explored the main components of Helidon, also showing how to set up either Helidon SE and MP. As Helidon MP is just an Eclipse MicroProfile runtime, we can run any existing MicroProfile based microservice using it.
在这篇文章中,我们已经探讨了Helidon的主要组件,还展示了如何设置Helidon SE和MP。由于Helidon MP只是一个Eclipse MicroProfile运行时,我们可以使用它运行任何现有的基于MicroProfile的微服务。
As always, the code of all examples above can be found over on GitHub.
像往常一样,上述所有例子的代码都可以在GitHub上找到over。