ActiveJ:轻量级 Java Web 框架
1. 概述
ActiveJ 是一个用于高性能应用的轻量级 Java 框架。我们可以使用它来创建具有快速启动和小内存占用的极简和模块化应用。它提供异步 I/O、依赖注入、高效序列化和响应式编程支持等特性。
本文中,我们将讨论 ActiveJ 的主要功能,包括它的 Inspect 模块、强大的事件循环和高级网络功能。
2. Inject(注入)
我们将 ActiveJ Inject 开始。它是一个轻量的、经性能优化的依赖注入库,我们可以使用它设置 bean 之间的依赖。
2.1. 依赖
添加 Active Inject 依赖到项目中:
<dependency>
<groupId>io.activej</groupId>
<artifactId>activej-inject</artifactId>
<version>6.0-rc2</version>
</dependency>
2.2. 使用模块进行依赖注入
我们创建一个 repository bean:
public class PersonRepository {
private final DataSource dataSource;
public PersonRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
}
在 PersonRepository
中,我们指定了对 DataSource
实例的依赖。接下来,我们创建一个 Service 类:
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
}
在 PersonService
类中,我们指定了 PersonRepository
依赖。
下一步,我们创建了一个 PersonModule
类,在这里我们可以配置 bean 之间的所有关联:
public class PersonModule extends AbstractModule {
@Provides
PersonService personService(PersonRepository personRepository) {
return new PersonService(personRepository);
}
@Provides
PersonRepository personRepository(DataSource dataSource) {
return new PersonRepository(dataSource);
}
@Provides
DataSource dataSource() {
return new DataSource() {
//DataSource methods
};
}
}
我们继承了 AbstractModule
类。此外,我们提供了 PersonService
、PersonRepository
和 DataSource
的 Bean,它们之间存在关联。
让我们来测试依赖注入行为:
public class ActiveJTest {
@Test
void givenPersonModule_whenGetTheServiceBean_thenAllTheDependenciesShouldBePresent() {
PersonModule personModule = new PersonModule();
PersonService personService = Injector.of(personModule).getInstance(PersonService.class);
assertNotNull(personService);
PersonRepository personRepository = personService.getPersonRepository();
assertNotNull(personRepository);
DataSource dataSource = personRepository.getDataSource();
assertNotNull(dataSource);
}
}
我们已经创建了 PersonModule
类的一个实例。然后,使用 Injector
我们获得了 PersonService
实例。正如我们所看到的,所有依赖项都已填充。
3. 异步 I/O
ActiveJ 异步 I/O 提供了用于高效编写异步流的组件。我们将研究 Promises 和事件循环等关键元素,以便更好地理解此特性。
3.1. 依赖
让我们添加 activej-promise
依赖:
<dependency>
<groupId>io.activej</groupId>
<artifactId>activej-promise</artifactId>
<version>6.0-rc2</version>
</dependency>
3.2. Promises
我们来创建将要用到的模型:
public record Person(String name, String description) {
}
现在,让我们添加一些逻辑到 PersonRepository
类:
public Promise<Person> findPerson(String name) {
return Promises
.delay(Duration.ofMillis(100), new Person(name, name + " description"));
}
我已经添加了 findPerson()
方法,它模拟了按名称搜索的过程。我们使用 Promises.delay()
来生成 Person
类的一个实例,但有一些延迟。其结果将用 Promise
实例包装。
接下来,让我们在服务层创建另一个模型:
public record VerifiedPerson(String name, String description, String notes, String result) {
}
在 VerifiedPerson
中,我们有一个 person 的信息以及一个验证结果。
让我们在 PersonService
类中添加服务的业务逻辑:
private Promise<String> findPersonNotes(String name) {
return Promise.of(name + " notes");
}
在 findPersonNotes()
方法中,我们模拟了人物笔记(person notes)的获取过程。结果也由 Promise
使用 Promise.of()
方法包装。
下一步,让我们添加验证方法:
private VerifiedPerson verify(VerifiedPerson person) {
if(person.description().startsWith("Good")) {
return new VerifiedPerson(person.name(), person.description(), person.notes(), "SUCCESS");
}
return new VerifiedPerson(person.name(), person.description(), person.notes(), "FAIL");
}
在这里,我们模拟了验证过程,根据 person 的 description 添加了验证结果。
现在,我们最终确定流程并合并所有操作:
public Promise<VerifiedPerson> findAndVerifyPerson(String name) {
return personRepository.findPerson(name)
.combine(findPersonNotes(name),
(person, notes) -> new VerifiedPerson(person.name(), person.description(), notes, null))
.map(person -> verify(person));
}
在最终的方法中,我们使用 Promiese
类的 combine()
方法联合(combine)了该 repository 的 findPersion()
和服务的 findPersonNotes()
方法。然后验证准备的模型并返回 Promise
的最终结果。
3.3. 事件循环(Event Loop)
ActiveJ Eventloop 在事件循环和线程中异步执行代码。让我们看看如何使用它来运行基于 Promise 的流:
@Test
void givenEventloop_whenCallFindAndVerifyPerson_thenExpectedVerificationResultShouldBePresent() {
PersonModule personModule = new PersonModule();
PersonService personService = Injector.of(personModule).getInstance(PersonService.class);
Eventloop eventloop = Eventloop.create();
eventloop.run();
personService.findAndVerifyPerson("Good person")
.whenResult(verifiedPerson -> assertEquals("SUCCESS", verifiedPerson.result()));
}
我们从 PersonModule
中得到了 PersonService
。然后使用 Eventloop.create()
方法创建了 Eventloop
实例,并使用 run
方法启用它。接下来,我们调用了 findAndVerfyPerson()
方法。如果 run()
方法缺失,此处将会出现异常:
IllegalStateException: No reactor in current thread
4. HTTP 服务器
我们将看到的另一个 ActiveJ 的出色的特性是,异步 HTTP 服务的高性能。
4.1. 依赖
首先,让我们添加 ActiveJ HTTP 依赖:
<dependency>
<groupId>io.activej</groupId>
<artifactId>activej-http</artifactId>
<version>6.0-rc2</version>
</dependency>
然后添加 Jackson Databind 依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
4.2. REST 端点示例
接下来,让我们通过 HTTP 端点暴露 VerifiedPerson
流程。我们从 PersonController
类开始:
public class PersonController implements AsyncServlet {
private final PersonService personService;
private final ObjectMapper objectMapper;
public PersonController(PersonService personService) {
this.personService = personService;
this.objectMapper = new ObjectMapper();
}
@Override
public Promise<HttpResponse> serve(HttpRequest httpRequest) {
return personService.findAndVerifyPerson(httpRequest.getQueryParameter("name"))
.map((p) -> HttpResponse.ok200().withJson(objectMapper.writeValueAsString(p)).build())
.mapException(e -> e);
}
}
此处我们实现了 AsyncServlet
实例并重写了 serve()
方法。在该方法中,我们获取了 PersonService
类的 findAndVerifyPerson()
方法的结果。然后将其映射到 JSON 并将其作为 HTTP 响应体使用 200 响应码返回。异常情况单独映射并默认返回 500 状态码。
让我们将此控制器添加到注入的上下文中:
public class PersonModule extends AbstractModule {
@Provides
PersonController personController(PersonService personService) {
return new PersonController(personService);
}
//Other beans
}
我们已经将 PersonController
添加到 PersonModule
中,并配置了它的依赖。
最后,让我们引入 HTTP 服务器码:
public class ActiveJIntegrationTest {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static HttpServer server;
private static HttpClient client;
private static int port;
private static Eventloop eventloop;
@BeforeAll
static void setUp() throws Exception {
eventloop = Eventloop.create();
PersonModule personModule = new PersonModule();
PersonController personController = Injector.of(personModule).getInstance(PersonController.class);
RoutingServlet servlet = RoutingServlet.builder(eventloop)
.with(HttpMethod.GET,"/person", personController)
.build();
server = HttpServer.builder(eventloop, servlet)
.withListenPort(8080)
.build();
server.listen();
port = server.getListenAddresses().get(0).getPort();
InetAddress dnsServerAddress = InetAddress.getByName("8.8.8.8");
DnsClient dnsClient = DnsClient.builder(eventloop, dnsServerAddress).build();
client = HttpClient.builder(eventloop, dnsClient).build();
}
@AfterAll
static void tearDown() {
if (server != null) {
server.close();
}
}
}
我们设置好了 HttpServer
实例。然后使用 RoutingServlet
添加了 HTTP映射。最后,我们使用 listen()
方法启用了这个服务,并准备了一个异步 HTTP 客户端。
现在,我们来调用我们的端点并检查其结果:
@Test
void givenHttpServer_whenCallPersonEndpoint_thenExpectedVerificationResultShouldPresentInResponse() {
HttpRequest request = HttpRequest.get("http://localhost:" + port + "/person?name=my-name").build();
client.request(request)
.whenResult(response -> {
assertEquals(response.getCode(), 200);
response.loadBody()
.whenResult(body -> {
try {
VerifiedPerson responseData = objectMapper.readValue(body.getArray(),
VerifiedPerson.class);
assertEquals(responseData.result(), "FAIL");
eventloop.breakEventloop();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
});
eventloop.run();
}
我们调用了该端点并异步获得预期结果。由于我们想在测试后停止服务器,我们调用了 Eventloop
的 breakEventloop()
方法来停止执行。
5. 小结
本文中,我们了解了 ActiveJ 框架的关键特性。有了它们,我们已经可以构建高效和轻量级的 web 应用。然而,这个框架提供了更多。我们可以将其用于数据处理、分布式系统和许多其他情况。它的模块化特性有助于我们避免过载的项目,只包含必要的组件。