You may want to modify configuration values and bean definition in some cases for your Spring application. I have also similar situation from my project in order to activate some modules commonly. How do we take refactoring for such cases? My answer was Annotation.

Example

For an instance, we assume that we want to make a log about execution time of specific methods as a function of APM(Application Performance Logging).

private val repository: ItemRepository

@LogExecutionTime
fun loadItems(criteria: ItemFetchCriteria): List<Item> {
  //...
}

We can collect the execution time of loadItems method that is annotated with @LogExecutionTime and show it to standard output via the below implementation with Spring AOP:

@Aspect
class LogExeucutionTimeApsect {
    @Around("@annotation(LogExecutionTime)")
    fun logExecutionTime(pjp: ProceedingJoinPoint): Any? {
        val signature = pjp.signature
        val typeName = signature.declaringType.simpleName
        val methodName = signature.name
        val start = System.currentTimeMillis()
        try {
            return pjp.proceed()
        } finally {
            val end = System.currentTimeMillis()
            println("%s.%s execution time : %dms".format(typeName, methodName, end - start))
        }
    }
}

Well, of course, an annotation to activate in the configuration is needed.

@Configuration
@EnableAspectJAutoProxy // AOP is enabled
class YourAppConfiguration {
    @Bean
    fun performanceLoggingAspect() = PerformanceLoggingAspect()
}

Now it works. Even though this is a simple example, it is unreasonable to copy and paste this code block to all related projects. (To introduce more complicated situation, we may use logging framework such as slf4j instead of pure println, and trigger direct API via Retrofit or Feign in order to send log rather than popular logging solutions like fluentd, logstash or filebeat. With these cases and related configuration, our code must be much longer and more complicated.)

We want to wrap all the upper configuration and classes with a single package and make referenced projects dependent to the wrapped module. This can be realized with the following simple annotation like @EnableXXX pattern.

@SpringBootApplication
@EnablePerformanceLogging
class YourApp

@Import annotation usage

By the way, @Import annotation helps us configuring several beans flexibly. It generally loads @Configuration classes, component classes, and even ImportSelector which is able to load seperate configurations in some cases.

With @Import annotation, we are able to activate YourAppConfiguration class. Let us design the process like the below:

  1. The Main-Class, YourApp triggers @EnablePerformanceLogging loading.
  2. Load the configuration annotated with @Import
  3. Enable AOP with @EnableAspectJAutoProxy in the configuration and load Aspect class as a bean.

Finally, @EnablePerformanceLogging annotation and the configuration class are implemented like the below:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Import(PerformanceLoggingConfiguration::class)
annotation class EnablePerformanceLogging

@EnableAspectJAutoProxy
class PerformanceLoggingConfiguration {
    @Bean
    fun aspect() = PerformanceLoggingAspect()
}

All remained todo is package this related classes as a common Java library.

Here is the example source code. The running output is like the following: