Spring Custom Annotation
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:
- The
Main-Class
,YourApp
triggers@EnablePerformanceLogging
loading. - Load the configuration annotated with
@Import
- 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: