Home > Back-end >  AspectJ is not working on beans without interface and defined in the configuration class
AspectJ is not working on beans without interface and defined in the configuration class

Time:01-29

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:

  1. The target bean is not implementing any interface.
  2. 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.

  • Related