How to process custom annotation in Spring Boot with Kotlin? - spring

CONTEXT:
I would like to create a custom annotation in Spring Boot and add extra logic for processing. I give an example with rather simplistic annotation but I want to have several of such annotations with more fine-grained control.
There are several approaches to this issue:
Create Filter
Create Interceptor
Create annotation with custom processing
I have to move with the latest one as two above don't work with my use-case.
ISSUE:
I have a custom annotation in Kotlin and I want it to be registered and be checked in the runtime.
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class OfflineProcessing(val allow: Bool)
REST controller is below:
#RestController
class ApiController {
#GetMapping("/api/version")
#OfflineProcessing(true)
fun apiVersion(): String {
return "0.1.0"
}
}
The idea is to have annotation per method and make conditional logic based on OflineProcessing allowed or not.
I have tried creating elementary PostBeanProcessor:
#Component
class OfflineProcessingAnnotationProcessor #Autowired constructor(
val configurableBeanFactory: ConfigurableListableBeanFactory
) : BeanPostProcessor {
#Throws(BeansException::class)
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
println("Before processor. Bean name: $beanName, Bean: $bean. Bean factory: $configurableBeanFactory.")
return super.postProcessBeforeInitialization(bean, beanName)
}
#Throws(BeansException::class)
override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
println("After processor. Bean name: $beanName, Bean: $bean. Bean factory: $configurableBeanFactory.")
return super.postProcessAfterInitialization(bean, beanName)
}
}
Apparently, annotation doesn't get logged among other annotations in BeanPostProcessor and I confused how to access it, so far I didn't find any other good examples without BeanPostProcessor.
Dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
Is there anything I do wrong? Or do I trying to use wrong method for the task?

This is a general question and not specific to Kotlin.
I think the misconception in your attempt to solve this is the fact that you are relaying on BeanPostProcessors. The bean is created in an early stage and it’s probably a singleton so it will not be called when you execute a rest request. This means that you will need to check for a bean that has your annotation and then somehow create a proxy bean over them with your logic embedded in that proxy.
This is very similar to what AOP does and then #eol’s approach is match easter.
I would like to suggest using an interceptor but not a bean creation interceptor.
My answer was inspired by Spring Boot Adding Http Request Interceptors
Define the annotation
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class OfflineProcessing(val allow: Boolean)
Define the interceptor
#Component
class CustomRestAnnotationInterceptor:HandlerInterceptor {
private val logger: Logger = LoggerFactory.getLogger(this.javaClass)
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
if (handler is HandlerMethod) {
var isOffline: OfflineProcessing? = handler.getMethodAnnotation(OfflineProcessing::class.java)
if (null == isOffline) {
isOffline = handler.method.declaringClass
.getAnnotation(OfflineProcessing::class.java)
}
if (null != isOffline) {
logger.info("method called has OfflineProcessing annotation with allow=${isOffline.allow}" )
}
}
return true
}
}
Add the interceptor to the path
#Configuration
class WebConfig : WebMvcConfigurer {
#Autowired
lateinit var customRestAnnotationInterceptor: CustomRestAnnotationInterceptor
override fun addInterceptors(registry: InterceptorRegistry) {
// Custom interceptor, add intercept path and exclude intercept path
registry.addInterceptor(customRestAnnotationInterceptor).addPathPatterns("/**")
}
}
Use the annotation on your controller
the log will display
2022-04-14 08:48:58.785 INFO 32595 --- [nio-8080-exec-1] .h.s.k.q.CustomRestAnnotationInterceptor : method called has OfflineProcessing annotation with allow=true

Not a direct answer to your question, but I'd simply use Spring AOP in that case instead of implementing BeanPostProcessor. To do so, you can define the following annotation and and a corresponding aspect, e.g.:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
annotation class OfflineProcessing(val allowed: Boolean = true)
#Aspect
#Component
class OfflineProcessingAspect {
#Pointcut("#annotation(<path.to.annotation.package>.OfflineProcessing)")
fun offlineProcessingPointcut(offlineProcessing: OfflineProcessing?) {
}
#Around("offlineProcessingPointcut(offlineProcessing)")
#Throws(Throwable::class)
fun around(pjp: ProceedingJoinPoint, offlineProcessing: OfflineProcessing): Object {
if (offlineProcessing.allowed()) {
// your pre-processing logic here
val result = pjp.proceed()
// post-processing logic here
return result
}
// do the same for non-allowed offline-processing ...
}
}
Lastly, add the following dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Related

Is there a way to overide automatically injected beans in Spring boot when writing tests?

I have a class annotated with a spring bean #Repository("clientDatasource") called ClientServiceDatasource which implements an interface called Datasource. I also have a mock implementation of this interface also annotated with a spring bean #Repository("mockDatasource") called MockClientServiceDatasource. I also have a class annotated with the spring bean #Service called ClientService and in in its constructor, I pass in a datasource. I do it like so:
#Service
class ClientService (#Qualifier("clientDatasource") private val dataSource: Datasource){}
As you can see that the service will default to the clientDatasource, because of the #Qualifier when the application is running.
However when I run my tests I annotate my test class with #SpringTest . In my understanding this means that it boots up the entire application as if it were normal. So I want to somehow overide that #Qualifier bean thats being used in the client service in my test so that the Client Service would then use the mockedDatasource class.
I'm fairly new to kotlin and spring. So I looked around and found ways to write a testConfig class to configure beans like so :
#TestConfiguration
class TestConfig {
#Bean
#Qualifier("clientDatasource")
fun mockDatasource(): Datasource {
return MockClientServiceDatasource()
}
}
and then using it in the test like so:
#SpringTest
#Import(TestConfig::class)
class ClientServiceTest {
...
}
I also asked chatGPT and it gave me this:
#SpringBootTest
class ClientServiceTest {
#Autowired
lateinit var context: ApplicationContext
#Test
fun testWithMockedDatasource() {
// Override the clientDatasource bean definition with the mockDatasource bean
val mockDatasource = context.getBean("mockDatasource", Datasource::class.java)
val mockClientDatasourceDefinition = BeanDefinitionBuilder.genericBeanDefinition(MockClientServiceDatasource::class.java)
.addConstructorArgValue(mockDatasource)
.beanDefinition
context.registerBeanDefinition("clientDatasource", mockClientDatasourceDefinition)
// Now the ClientService should use the mockDatasource when it's constructed
val clientService = context.getBean(ClientService::class.java)
// ... do assertions and other test logic here ...
}
}
But some of the methods don't work, I guess chatGPT knowledge is outdated.
I also looked through spring docs, but couldn't find anything useful.
Okay, So I took a look at the code previously with the TestConfig class. And I realised by adding the:
#Primary
annotation to the method inside my TestConfig class, it basically forces that to be the primary repository bean. Like so:
#TestConfiguration
class TestConfiguration {
#Bean
#Primary
#Qualifier("clientDatasource")
fun mockDatasource(): Datasource {
return MockClientDataSource()
}
}
and in the test I only imported the test and it just worked. I didn't have to autowire anything
This is my test class:
#SpringBootTest
#AutoConfigureMockMvc
#Import(TestConfiguration::class)
internal class ServiceControllerTest{
#Suppress("SpringJavaInjectionPointsAutowiringInspection")
#Autowired
lateinit var mockMvc: MockMvc
#Test
fun `should return all clients` () {
// when/then
mockMvc.get("/clients")
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON) }
jsonPath("$[0].first_name") {value("John")}
}
}
}

Spring custom annotation for bean injection

I am looking to inject a bean by means of a custom annotation
#Service
class Foo(#MyBean val bar: Bar) { fun someMethod() { bar.invoke() } }
with
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
annotation class MyBean
Currently we have a configuration class that defines multiple bean methods
#Configuration
class config {
#Bean
fun bar(): Bar = { getBaz() }
}
I have seen implementations of BeanPostProcessor but that seems to add behaviour to already existing beans. My question is, is there a way to initialise and assign beans to a field by means of a custom annotation.
Since you're injecting the field through a constructor, you don't need to annotate the field itself, but rather the constructor.

Kotlin with Spring DI: lateinit property has not been initialized

I don't get Spring-based setter dependency injection in Kotlin to work as it always terminates with the error message "lateinit property api has not been initialized". I could reduce the problem to the following scenario: There is an interface
interface IApi {
fun retrieveContent(): String
}
which is implemented by
class Api : IApi {
override fun retrieveContent() = "Some Content"
}
I want to use the implementation in another class where the dependency injection is supposed to take place:
#Component
class SomeController {
#Autowired lateinit var api: IApi
fun printReceivedContent() {
print(api.retrieveContent())
}
}
However, the application terminates with the above-mentioned error message. My Spring config looks as follows:
#Configuration
open class DIConfig {
#Bean
open fun getApiInstance(): IApi = Api()
}
In the main function I load the application context and call the method:
fun main(args: Array<String>) {
val context = AnnotationConfigApplicationContext()
context.register(DIConfig::class.java)
context.refresh()
val controller = SomeController()
controller.printReceivedContent()
}
What is the problem here?
Spring isn't involved if you just call the constructor yourself like that. Same as in Java,
val controller = context.getBean(SomeController::class.java)
Spring Framework 5.0 adds Kotlin extensions, so you could also write either one of
val controller = context.getBean<SomeController>()
val controller: SomeController = context.getBean()
your api is currently no a bean managed by spring, try annotating it with #Service or #Component
The #Autowired is usually added to the setter of a property. So instead of using it for the property, you should explicitly annotate the setter:
#set:Autowired lateinit var api: IApi

Spring can not register spring hateoas resource assembler

I am using spring hateoas in spring and got the problem is spring could not instance hateoas resource assembler , here is my snippet code:
UserHateoasResourceAssembler.java:
#Service
public class UserHateoasResourceAssembler extends ResourceAssemblerSupport<UserDTO, UserHateoasResource> {
public UserHateoasResourceAssembler() {
super(UserController.class, UserHateoasResource.class);
}
#Override
public UserHateoasResource toResource(UserDTO entity) {
UserHateoasResource resource = createResourceWithId(entity.getId(), entity);
return resource;
}
#Override
protected UserHateoasResource instantiateResource(UserDTO entity) {
return new UserHateoasResource(entity);
}
}
UserController.java:
#RestController
#RequestMapping("/api/")
public class UserController {
#Inject
private UserHateoasResourceAssembler userAssembler ;
....
}
The exception was thrown is "No qualifying bean of type [UserHateoasResourceAssembler] found for dependency. I know this root cause is can not create instance of assembler.
I tried to use #Service or #Component but both does not work. I also tried to use #Autowire instead, but did not work too. I have to fix that by adding #Scope( proxyMode = ScopedProxyMode.TARGET_CLASS). But I wonder if there is any another solution to resolve it instead of using #Scope ?
Thanks.
I found the elegant solution. Due to my application using generated code and it used #EnableAspectJAutoProxy, this annotation default set auto-proxy = false and using JDK proxy, so almost the instance of class that implementing an interface was not allowed. We have to #inject the interface instead. So to inject the implementation class, have 2 options here:
Set #EnableAspectJAutoProxy(proxyTargetClass = true )
Remove this annotation if we does not really need that.

Vaadin-spring autowired

I have a vaadin project and I need to use the #Autowired Spring annotation. So I use the vaadin-spring add-on
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring</artifactId>
<version>1.0.0</version>
</dependency>
Now, if I define autowired class in UI class, it works correctly. But if I define autowired class in another class, this class is always null. For example in this case "serv" is always null.
#SuppressWarnings("serial")
public class ProfileWindow extends Window {
#Autowired
private Servizio serv;
...
...
private Component buildFooter() {
HorizontalLayout footer = new HorizontalLayout();
footer.addStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR);
footer.setWidth(100.0f, Unit.PERCENTAGE);
Button ok = new Button("OK");
ok.addStyleName(ValoTheme.BUTTON_PRIMARY);
ok.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Dati dd = new Dati(Double.parseDouble(nomeField.getValue()), Double.parseDouble(cognomeField.getValue()));
String out = serv.converti(dd);
Notification success = new Notification(out);
success.setDelayMsec(2000);
success.setStyleName("bar success small");
success.setPosition(Position.BOTTOM_CENTER);
success.show(Page.getCurrent());
BsciEventBus.post(new ProfileUpdatedEvent());
close();
}
});
ok.focus();
footer.addComponent(annulla);
footer.setComponentAlignment(annulla, Alignment.TOP_LEFT);
footer.addComponent(ok);
footer.setComponentAlignment(ok, Alignment.TOP_RIGHT);
return footer;
}
}
ServizioImpl.java
#SpringComponent
public class ServizioImpl implements Servizio {
#Override
public String converti(Dati dati) {
//implementation...
return "out";
}
}
Can you help me to use the autowired annotataion?
Do you have some example code to suggest? I don't understand how solve this problem.
If I use #SpringComponent on my ProfileWindow, the autowired annotation doesn't work yet.
Thanks
If your class has an injected member, make sure the class itself is instantiated from the ApplicationContext. You can for example create the instance with context.getBean(ProfileWindow.class), or the class instance itself is an annotated member of a managed class. If you instantiate your ProfileWindow class with new then the injection mechanism doesn't work.
Thanks all, but I solved the problem using vaadin spring integration add-on
<dependency>
<groupId>ru.xpoft.vaadin</groupId>
<artifactId>spring-vaadin-integration</artifactId>
<version>3.2</version>
</dependency>

Resources