Wednesday, September 30, 2015

Adding Spring Boot Actuator to a non Spring Boot Application

Spring Boot is excellent for setting up application quickly without re-developing any of the boiler plate (infrastructure) code.

The instructions to create Spring Boot application are provided in the spring.io link. The Spring Intializr provides a quick way to create an application. Spring Boot provides ways to integrate with the different frameworks. 

One of the key features of the Spring Boot is the spring-actuator which provides production-ready features out of the box. Spring Boot actuator exposes various endpoints which provides information about the running application. The details of the various endpoints are provided in the Spring Boot documentation

There were several questions that came to my mind which I read more about Spring Boot Actuator. 
  • What if you have a web application which uses Spring but it is not a Spring Boot application?
  • Would you have to implement all the production-ready features such as Health endpoint, Info endpoint, etc. again in your application?
  • Do you have to re-invent the wheel which the Spring team has done in the Spring Boot Actuator project?
  • Would it not be great that you could create the dependency on the Spring Boot Actuator jar and get all the functionalities from it? 

There are several blog posts which seem to suggest that it can be done. I tried following the instructions, but it seems to not work with the version of Spring Boot that I was trying to use ( version 1.2.5 ). 
So, I spend sometime debugging and looking at the Spring Boot Actuator code and figure out how I can set up Spring Boot actuator in a non-boot project. 

This is what worked for me. 

In the application's build.gradle, I added the following dependency
compile('org.springframework.boot:spring-boot-actuator:1.2.5.RELEASE'){
    exclude group: 'org.springframework.boot', module:'spring-boot-starter-logging'}

In the application's Spring Config class, I added the following things:

 import org.springframework.beans.factory.annotation.Autowired;  
 import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;  
 import org.springframework.boot.actuate.endpoint.BeansEndpoint;  
 import org.springframework.boot.actuate.endpoint.HealthEndpoint;  
 import org.springframework.boot.actuate.endpoint.InfoEndpoint;  
 import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;  
 import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;  
 import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;  
 import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;  
 import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;  
 @Configuration  
 @Import(EndpointAutoConfiguration.class)  
 public class MyAppSpringConfig {  
   @Bean  
   @Autowired  
   //Define the HandlerMapping similar to RequestHandlerMapping to expose the endpoint  
   public EndpointHandlerMapping endpointHandlerMapping(  
     Collection<? extends MvcEndpoint> endpoints  
   ){  
     return new EndpointHandlerMapping(endpoints);  
   }  
   @Bean  
   @Autowired  
   //define the HealthPoint endpoint  
   public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate){  
     return new HealthMvcEndpoint(delegate, false);  
   }  
   @Bean  
   @Autowired  
   //define the Info endpoint  
   public EndpointMvcAdapter infoMvcEndPoint(InfoEndpoint delegate){  
     return new EndpointMvcAdapter(delegate);  
   }  
   @Bean  
   @Autowired  
   //define the beans endpoint  
   public EndpointMvcAdapter beansEndPoint(BeansEndpoint delegate){  
     return new EndpointMvcAdapter(delegate);  
   }  
   @Bean  
   @Autowired  
   //define the mappings endpoint  
   public EndpointMvcAdapter requestMappingEndPoint(  
     RequestMappingEndpoint delegate  
   ){  
     return new EndpointMvcAdapter(delegate);  
   }  
 }  

Voila, you have added the spring actuator to your application.

In case, you want to get rid of additional dependency of spring-boot-autconfigure from your application, you would have to do the following to integrate it.

In the gradle, you would add: 
compile('org.springframework.boot:spring-boot-actuator:1.2.5.RELEASE'){
  exclude group: 'org.springframework.boot', module:'spring-boot-starter-logging'  exclude group: 'org.springframework.boot', module:'spring-boot-autoconfigure'}

Create a Spring Config and import it to your application's Spring Config. In the example below, I have added only health endpoint and beans endpoint for your reference
 import org.springframework.beans.factory.annotation.Autowired;  
 import org.springframework.boot.actuate.endpoint.BeansEndpoint;  
 import org.springframework.boot.actuate.endpoint.HealthEndpoint;  
 import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;  
 import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;  
 import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;  
 import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;  
 import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;  
 import org.springframework.boot.actuate.health.HealthAggregator;  
 import org.springframework.boot.actuate.health.HealthIndicator;  
 import org.springframework.boot.actuate.health.OrderedHealthAggregator;  
 import org.springframework.context.annotation.Bean;  
 import org.springframework.context.annotation.Configuration;  
 import java.util.Collection;  
 import java.util.HashMap;  
 import java.util.Map;  
 @Configuration  
 public class BootEndpointAutoConfiguration {  
 
   private HealthAggregator healthAggregator = new OrderedHealthAggregator();  
 
   private Map<String, HealthIndicator> healthIndicators = new HashMap();

   @Bean  
   @Autowired  
   //Define handler mapping  
   public EndpointHandlerMapping endpointHandlerMapping(  
     Collection<? extends MvcEndpoint> endpoints  
   ){  
     return new EndpointHandlerMapping(endpoints);  
   }  

   @Bean  
   @Autowired  
   //Define the beans for exposing the mapping  
   public EndpointMvcAdapter requestMappingEndPoint(RequestMappingEndpoint delegate){  
     return new EndpointMvcAdapter(delegate);  
   }  

   @Bean  
   @Autowired  
   //Define the bean to expose the health  
   public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate){  
     return new HealthMvcEndpoint(delegate, false);  
   }  

   @Bean  
   //Define the bean to expose an health point  
   public HealthEndpoint healthEndpoint() {  
     return new HealthEndpoint(this.healthAggregator, this.healthIndicators);  
   }  

   @Bean  
   public BeansEndpoint beansEndpoint() {  
     return new BeansEndpoint();  
   }  

   @Configuration  
   protected static class RequestMappingEndpointConfiguration {  
     protected RequestMappingEndpointConfiguration() {  
     }  
     @Bean  
     public RequestMappingEndpoint requestMappingEndpoint() {  
       RequestMappingEndpoint endpoint = new RequestMappingEndpoint();  
       return endpoint;  
     }  
   }  
 }