Spring Boot integration
Table of contents
The Spring framework provides powerful features necessary for building web applications such as dependency injection, data binding, AOP and transaction management. By integrating your Spring application with Armeria, you can serve both legacy Spring services and gRPC or Thrift services on the same port. Additionally, you can gradually migrate your services to Armeria for improved performance.
Armeria provides a simple way to integrate legacy Spring Boot applications with just a few lines of code. Furthermore, Armeria supports several internal services for monitoring and management purposes, which can be conveniently configured through beans.
Integrating with Spring MVC (with Tomcat)
You can integrate your Spring MVC with Armeria using TomcatService
.
First, add the following dependency to your application.
dependencies {
implementation platform('com.linecorp.armeria:armeria-bom:1.31.3')
...
implementation 'com.linecorp.armeria:armeria-spring-boot3-starter'
implementation 'com.linecorp.armeria:armeria-tomcat10'
}
Serve the embedded TomcatService
via Armeria by using the ArmeriaServerConfigurator
bean.
import com.linecorp.armeria.server.tomcat.TomcatService;
import com.linecorp.armeria.spring.ArmeriaServerConfigurator;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ArmeriaConfiguration {
public static Connector getConnector(ServletWebServerApplicationContext applicationContext) {
final TomcatWebServer container = (TomcatWebServer) applicationContext.getWebServer();
container.start();
return container.getTomcat().getConnector();
}
@Bean
public TomcatService tomcatService(ServletWebServerApplicationContext applicationContext) {
return TomcatService.of(getConnector(applicationContext));
}
@Bean
public ArmeriaServerConfigurator armeriaServerConfigurator(TomcatService tomcatService) {
return sb -> sb.serviceUnder("/", tomcatService);
}
}
Add the following properties to your application.yml
(or application.properties
) file.
This configuration will prevent the exposure of the embedded Tomcat service and instead expose your Armeria service.
You can explore additional configuration options in ArmeriaSettings
.
# Prevent the embedded Tomcat from opening a TCP/IP port.
server:
port: -1
armeria:
ports:
- port: 8080
protocols:
- HTTP
With this simple configuration, everything is set up. Now you can serve your legacy Spring service through Armeria. Although it may not seem like much has changed compared to before, your application is now ready to serve both RPC services (such as gRPC or Thrift) and RESTful services via Armeria.
Integrating with Spring Boot
Even if you don't utilize Spring MVC based on web servlets like Tomcat, there is still significant value in combining Spring and Armeria. You can leverage the automatic configuration and dependency injection features of Spring Boot to develop an Armeria application.
First, you need the armeria-spring-boot3-starter
dependency:
dependencies {
implementation platform('com.linecorp.armeria:armeria-bom:1.31.3')
...
implementation 'com.linecorp.armeria:armeria-spring-boot3-starter'
}
Armeria's annotated service is similar to a Spring controller.
You can declare the annotated service as a bean and inject it into the ServerBuilder
.
Here's an example:
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.server.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
// Make this annotated service a bean.
@Component
@PathPrefix("/todos")
public class TodoAnnotatedService {
private final TodoRepository todoRepository;
// Automatically injected by Spring
@Autowired
public TodoAnnotatedService(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
@Get("/:id")
public HttpResponse get(@Param Integer id) {
Todo todo = todoRepository.get(id);
if (todo == null) {
return HttpResponse.of(HttpStatus.NO_CONTENT);
}
return HttpResponse.ofJson(todo);
}
@Post
public HttpResponse create(Todo todo) {
final int result = todoRepository.create(todo);
if (result == 0) {
return HttpResponse.of(HttpStatus.INTERNAL_SERVER_ERROR);
}
return HttpResponse.of(HttpStatus.CREATED);
}
}
To inject the annotated service into the ArmeriaServerConfigurator
bean, you can follow this approach:
@Bean
public ArmeriaServerConfigurator armeriaServerConfigurator(TodoAnnotatedService todoAnnotatedService) {
return serverBuilder -> {
serverBuilder.serviceUnder("/docs", new DocService())
.decorator(LoggingService.newDecorator())
.annotatedService("/api", todoAnnotatedService);
};
}
The following configuration prevents the execution of Spring's embedded web server and instead runs Armeria.
# See https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.disable.
# The application should not run as a web application and should not start an embedded web server.
spring.main.web-application-type: none
armeria:
ports:
- port: 8080
protocols:
- http
Internal services
Armeria supports four internal services that are useful for monitoring and management purposes.
You can enable these services by including their service IDs in your application.yml
(or application.properties
) file.
Service ids are docs
, health
, metrics
, and actuator
. You can include one or multiple service IDs based on your requirements.
armeria:
ports:
- port: 8080
protocols:
- HTTP
# Add the below configuration
internal-services:
include: docs, health, metrics, actuator # you can add all of the services using `all`
port: 8090
The armeria.internal-services.port
configuration is not necessary. If the port is not specified or left as 0
,
Armeria will automatically bind the internal service to a random unused port.
It is acceptable to set the port of the internal service to be the same as one of the armeria.ports.
Documentation service
Armeria has its own documentation service that offers useful features. For instance, it allows you to test RPC protocols in a web browser console, similar to Swagger.
Add the service ID docs
to armeria.internal-services.include
configuration.
The armeria.docs-path
is not necessary, as the default path for the documentation service is /internal/docs
.
armeria:
internal-services:
include: docs
docs-path: /internal/docs
To add custom configuration to your documentation service, you can utilize the DocServiceConfigurator
bean.
@Bean
public DocServiceConfigurator docServiceConfigurator() {
return docServiceBuilder -> docServiceBuilder
.exampleRequests(TodoAnnotatedService.class, "create", "{\"id\":\"42\", \"value\":\"foo bar\"}");
}
Health check
To customize the health check operation, you can utilize the HealthChecker
bean.
Add the service ID health
to the armeria.internal-services.include
configuration.
The armeria.health-check-path
is not necessary, as the default path for the health check service is /internal/healthcheck
.
armeria:
internal-services:
include: health
health-check-path: /internal/healthcheck
You can create a HealthChecker
bean that implements your custom health check logic.
For example, the below code determines if the server is healthy using Tomcat connector state.
@Bean
public HealthChecker tomcatConnectorHealthChecker(ServletWebServerApplicationContext applicationContext) {
final Connector connector = getConnector(applicationContext);
return () -> connector.getState().isAvailable();
}
You can also add custom configuration to the service using the HealthCheckServiceConfigurator
bean, similar to the documentation service.
@Bean
public HealthCheckServiceConfigurator healthCheckServiceConfigurator() {
return healthCheckServiceBuilder -> healthCheckServiceBuilder
.updatable(true)
.startUnhealthy();
}
Collecting metrics
Armeria provides a built-in metric service using the Micrometer library. You can expose the collected metrics to various monitoring systems, such as Prometheus or Dropwizard for comprehensive monitoring and analysis.
To include the metrics service, simply add the service ID metrics
to the armeria.internal-services.include
configuration.
You do not need to specify armeria.enable-metrics
or armeria.metrics-path
as the default path for the metrics service is /internal/metrics
.
armeria:
internal-services:
include: metrics
metrics-path: /internal/metrics
enable-metrics: true # default is true
We will illustrate the bean configuration using an example with the Prometheus monitoring system.
First, create a PrometheusMeterRegistry
bean.
If you are using a different monitoring system, create a bean of MeterRegistry
type.
@Bean
public PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
To add a prefix to the ID of collected metrics, you can use the MeterIdPrefixFunction
bean.
Additionally, you can customize the configuration of the service by utilizing the MetricCollectingServiceConfigurator
bean.
@Bean
public MeterIdPrefixFunction meterIdPrefixFunction() {
return MeterIdPrefixFunction.ofDefault("my.armeria.service");
}
@Bean
public MetricCollectingServiceConfigurator metricCollectingServiceConfigurator() {
return metricCollectingServiceBuilder -> metricCollectingServiceBuilder
.successFunction((context, log) -> {
final int statusCode = log.responseHeaders().status().code();
// Treat a 404 response as a success
return statusCode >= 200 && statusCode < 400 || statusCode == 404;
});
}
Actuator support
If you are using Spring Boot Actuator,
you can serve it at the armeria.internal-services.port
.
The internal services may also be served at the management.server.port
if specified.
In order to use the actuator service, you need to first add the following dependencies.
dependencies {
...
implementation 'com.linecorp.armeria:armeria-spring-boot3-actuator-starter'
}
To include the actuator service, add the service ID actuator
to the armeria.internal-services.include
configuration.
armeria:
internal-services:
include: actuator
Other bean configurations
We will provide an explanation of some additional bean configurations that can be helpful to know.
ArmeriaServerConfigurator
vs Consumer<ServerBuilder>
You can use both ArmeriaServerConfigurator
and Consumer<ServerBuilder>
to configure the server using ServerBuilder
.
In fact, both are essentially same functions of ServerBuilder -> void
. The most significant difference lies in the order in which they are applied to the server.
Armeria first configures all ArmeriaServerConfigurator
beans and subsequently applies all Consumer<ServerBuilder>
beans.
If you have multiple ArmeriaServerConfigurator
or Consumer<ServerBuilder>
beans, you can set the order
using the @Order annotation.
It is important to note that the default order of ArmeriaServerConfigurator
is zero,
and not Ordered.LOWEST_PRECEDENCE
.
DependencyInjector
Armeria provides the ability to manually inject dependencies using DependencyInjector
.
You can refer to the example in the 1.17.0 release notes for more details.
Create the DependencyInjector
bean, which will replace the default dependency injector.
It's important to note that in this case dependencies that were automatically injected before may not be injected anymore.
@Bean
public DependencyInjector dependencyInjector() {
return DependencyInjector.ofSingletons(
new BadRequestExceptionHandler(),
new AuthDecorator((ctx, req) ->
CompletableFuture.supplyAsync(() -> req.headers().get(AUTHORIZATION).equals("auth-token"))
)
);
}
You can also utilize a dependency injector that leverages the BeanFactory
of Spring. After configuring the properties, you can effortlessly create beans to be injected, similar to how you would do it in a Spring-based setup.
armeria:
enable-auto-injection: true