Creating Custom Spring Boot Starter To Implement Cross-Cutting Concerns

Now days Spring Boot has become de facto standard for numerous Web enterprise developments. Spring Boot helps to improve developer’s productivity by implementing lot of cross-cutting concerns as Starter Projects. We Just add these dependancy in our projects or configure few properties and Spring Boot does the magic for us by doing autoconfiguration. The starters projects automatically configure lof of stuff for us. This helps us to get started more quickly.
However, With a lof magic happening in background, it’s very Important to know How things work.

The code for this post is available for download here.

How Spring Boot’s Starter Works

On Startup, Spring Boot checks for spring.factories file. This file is located in the META-INF directory.

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ns.aop.LogMethodExecutionTimeAutoConfiguration

All the classes with @Configuration should list under EnableAutoConfiguration key in the spring.factories file.
Spring Will create Beans Based on configuration and Conditions defined in Configurations files. We will see this in detail with example.

Let’s Create Library That logs Method Execution Time

lets imagine, We want to log the method execution time for few methods in our project. We should be able to enable/disable this feature based on some property.

Creating Our Custom Spring Boot Starter Project

Create Spring Boot Project with Below dependencies.

Pom File
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

spring-boot-dependencies allows us to use any Spring dependency.
spring-boot-autoconfigure for using autoconfigure feature
spring-boot-configuration-processor to generate metadata for our configuration properties. IDEs can give us autocomplete.

Use AOP to log method execution time

Create simple annotation LogMethodExecutionTime to be used on method and aspect to log time.

Annotation & Aspect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogMethodExecutionTime {
}

@Aspect
@Slf4j
public class LogMethodExecutionTimeAspect {
@Around("@annotation(LogMethodExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
final long start = System.currentTimeMillis();
final Object proceed = joinPoint.proceed();
final long executionTime = System.currentTimeMillis() - start;
log.info(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
}

Creating Our Own Autoconfiguration

We want to control the Method Execution Time logging to be enable based on certain properties. Spring provides lot of @Conditional annotations.Based on different conditions we can control Configurations of starter projects. For this example we can use ConditionalOnProperty.
our starter will be activated only if logging.api.enabled property is present and has value true

Configuration For our Starter
1
2
3
4
5
6
7
8
@Configuration
@ConditionalOnProperty(name = "logging.api.enabled", havingValue = "true", matchIfMissing = true)
public class LogMethodExecutionTimeAutoConfiguration {
@Bean
public LogMethodExecutionTimeAspect getLogMethodExecutionTimeAspect(){
return new LogMethodExecutionTimeAspect();
}
}

spring.factories File

We need to create special file called as spring.factories. Our custom starter to be pick by Spring boot, we have to create spring.factories. This file should be placed within the src/main/resources/META-INF folder. This file has list of all classes annotated with @Configuration.

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ns.aop.LogMethodExecutionTimeAutoConfiguration

Note Naming Convention
You should make sure to provide a proper namespace for your starter. Do not start your module names with spring-boot, even if you use a different Maven groupId.
As a rule of thumb, you should name a combined module after the starter. For example, assume that you are creating a starter for “acme” and that you name the auto-configure module acme-spring-boot-autoconfigure and the starter acme-spring-boot-starter. If you only have one module that combines the two, name it acme-spring-boot-starter.

Using the custom starter

Let’s create a sample Spring Boot application client to use our custom starter.

Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<dependency>
<groupId>ns</groupId>
<artifactId>method-execution-time-logging-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

// define below property
logging.api.enabled=true
//Main Class
@SpringBootApplication
public class ClientApplication {

public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
@Bean
ApplicationRunner init(TestClass testClass) {
return (ApplicationArguments args) -> dataSetup(testClass);
}
private void dataSetup(TestClass testClass) throws InterruptedException {
testClass.run();
}
}
//Test Class
@Component
public class TestClass {
@LogMethodExecutionTime
public void run() throws InterruptedException {
Thread.sleep(3000);
}
}

The code for this post is available for download here.

Share Comments