I created a demo application to reproduce it:
DemoService
open class DemoService {
fun test() {
println("test function is executed.")
}
}
DemoAspect
@Aspect
class DemoAspect {
@Around("execution(* com.example.demo.service.DemoService.test(..))")
fun testAspect(joinPoint: ProceedingJoinPoint) {
println("before test function.")
joinPoint.proceed()
println("after test function.")
}
}
AppConfig
@Configuration
@EnableAspectJAutoProxy
class AppConfig {
@Bean
fun demoService() = DemoService()
@Bean
fun demoAspect() = DemoAspect()
}
SpringDemoApplication
@SpringBootApplication
@Import(AppConfig::class)
class SpringDemoApplication
fun main(args: Array<String>) {
val context = runApplication<SpringDemoApplication>(*args)
val demoService = context.beanFactory.getBean(DemoService::class.java)
demoService.test()
}
Execution result:
test function is executed.
The aspect is not working which is not expected.
I tried following variations and they worked correctly:
Remove the beans in configuration services and register beans by annotations
DemoService
@Service
open class DemoService {
...
}
AppConfig
@Configuration
@EnableAspectJAutoProxy
class AppConfig {
@Bean
fun demoAspect() = DemoAspect()
}
Let DemoService
implements an interface
DemoService
interface DemoService {
fun test()
}
open class DemoServiceImpl: DemoService {
override fun test() {
println("test function is executed.")
}
}
AppConfig
@Configuration
@EnableAspectJAutoProxy
class AppConfig {
@Bean
fun demoService() = DemoServiceImpl()
@Bean
fun demoAspect() = DemoAspect()
}
I want to understand why the AspectJ is not working on this combination:
- The target bean is not implementing any interface.
- The bean is registered in Configuration class.
CodePudding user response:
Spring AOP, in contrast to native AspectJ, is based on dynamic proxies. In order to proxy a class, it must not be final
. In Kotlin terms, it must be open
. Moreover, in order to proxy a method, it must not be final
either, i.e. you also need to open
the method. See also this Baeldung tutorial.
So, please use open fun test()
, then it works as expected.
Update for follow-up question:
why it works if I use @Service even though the function is final?
Because probably in your build you use the Kotlin all-open compiler plugin with Spring support enabled. If I were you, I would not use @Bean
factory methods anyway, but simply @Component
, @Service
or similar Spring annotations directly on the implementation classes.
Of course, you could also use the generic all-open plugin in addition to the Spring version and then do this:
annotation class OpenMe()
@OpenMe
class DemoService {
fun test() {
println("test function is executed.")
}
}
Then, in Maven you would do something like:
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>all-open</plugin>
<plugin>spring</plugin>
</compilerPlugins>
<pluginOptions>
<option>all-open:annotation=org.acme.OpenMe</option>
</pluginOptions>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
Then you would not need any open
keywords on the service class or method anymore and could continue to use your @Bean
factory method. But I think this is more complicated than using Spring annotations - convention over configuration.