Spring - Get request header without using #RequestHeader annotation - spring

My code is currently implemented like so:
fun test(#RequestHeader key: String) { ... }
But I wan't to remove the parameter, so it's something like this:
fun test() { request.headers("key") ... }
Is this possible with Spring?

Autowire HttpServletRequest in your class and use it within the functions defined in the class for example:
class MyClass {
#Autowired
lateinit var request: HttpServletRequest
fun test() { request.getHeader("key") ... }
}

Related

kotlin.UninitializedPropertyAccessException: lateinit property has not been initialized

This is my main function
object Service {
fun getConfigMappings(client: RedissonClient, request: GetRequest): IndataType {
****
return obj
}
}
I calling it in my main class, and everything works good, I can get the response.
#Autowired
lateinit var client: RedissonClient
val indataObj = Service.getConfigMappings(client, request)
When I want to write a test for it, I got error "kotlin.UninitializedPropertyAccessException: lateinit property client has not been initialized", can anyone help me with that?
"
class ServiceTest {
#Autowired
lateinit var client: RedissonClient
#Test
fun `test1`() {
val request = GetRequest {
***
}
val indataObj = Service.getConfigMappings(client, request)
}
}
kotlin.UninitializedPropertyAccessException: lateinit property has not been initialized is occured because there is no configuration for AutoWired.
If you want to use AutoWired in the unit test, it needs annotation for configuration.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations="classpath:config/springbeans.xml")
public class BeanSpringTest {
if you want to use #Autowired, there must have a #Bean somewhere

It is possible to inject map with enums keys?

From this question, it is possible to inject map with enums?
For example, i have enum:
class enum SomeEnum (val value) {
ONE("one"),
TWO("two"),
THREE("three")
}
And i have some interface with implementations:
interface SomeInterface {
}
#Component
#Qualifier("one")
class OneClass: SomeInterface {
...
}
#Component
#Qualifier("two")
class TwoClass: SomeInterface {
...
}
#Component
#Qualifier("three")
class ThreeClass: SomeInterface {
...
}
But such injection not works:
#Component
#ConfigurationProperties
class SomeProperties {
#Autowired
lateinit var someMap: Map<SomeEnum, SomeInterface>
}
I want to autoinject someMap. How can i fix it, to make such code on spring framework side?
var someMap: Map<SomeEnum, SomeInterface> = Map.of(ONE, oneClass,
TWO, twoClass,
THREE, threeClass)
// Where oneClass, twoClass, threeClass - beans instances
First of all you misuse #Qualifier annotation. It's intended not to name the bean but to help Spring to choose single autowire candidate among multiple. #Qualifier is only meaningful at injection point - either in pair with #Autowired for class properties
#Autowired
#Qualifier("one")
lateinit var someImpl: SomeInterface
or for injection into method/constructor arguments
#Bean
fun createSomeService(#Qualifier("two") someImpl: SomeInterface): SomeService {
return SomeService(someImpl)
}
//or
class SomeService(#Qualifier("three") private val someImpl: SomeInterface) {
...
}
Correct way to name a bean is simply #Component("name") (or #Service("name"))
As far as I know, you can only autowire Maps with String keys, where key is the name of the bean. However you can easily convert this map into enum-key map like this:
interface SomeInterface
#Component("one")
class OneClass: SomeInterface
#Component("two")
class TwoClass: SomeInterface
#Component("three")
class ThreeClass: SomeInterface
#Component
#ConfigurationProperties
class SomeProperties(implMap: Map<String, SomeInterface>) {
val someMap: Map<SomeEnum, SomeInterface> = implMap.mapKeys {
SomeEnum.values().firstOrNull { e -> e.value == it.key }!!
}
}
UPDATED:
or using field injection (not recommended)
#Component
#ConfigurationProperties
class SomeProperties {
private lateinit var someMap: Map<SomeEnum, SomeInterface>
#Autowired
private lateinit var implMap: Map<String, SomeInterface>
#PostConstruct
fun initMap() {
someMap = implMap.mapKeys {
SomeEnum.values().firstOrNull { e -> e.value == it.key }!!
}
}
}
Not quite sure what you want to do, but from my point of view you dont need this mapping. I assume you want to know which implementation to use for certain case. So just autowire a list or set of your interface and iterate trough it to find the right implementation. (I show the stuff in Java)
#Autowired
List<SomeInterface> someInterfaces;
In this list you will have all the injected implementations of this interface. If you still need an Enum to see which implementation to use, just add this as an attribute to every of your implementation class. So you can get the Enum value by its implementation.
EDIT:
Create a config class and autowire the list of implementations. In this config class you create a Bean of your map.
#Configuration
public class MyMapConfig {
#Autowired
List<SomeInterface> someInterfaces;
#Bean
public Map<SomeEnum, SomeInterface> myMap() {
Map<SomeEnum, SomeInterface> map = new HashMap<>();
someInterfaces.forEach(s -> {
// logic here to add correct Enum to its implementation.
map.put(SomeEnum.A, s);
});
return map;
}
public enum SomeEnum {
A, B, C
}
}
Then you can autowire your map anywhere you want:
#Autowired
Map<SomeEnum, SomeInterface> myMap;

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 Configuration - Inject Mock Beans

I am using Spring, Junit and Mockito. I need to override beans defined in the main spring configuration using another mockito test configuration (injecting mock beans only as needed). Nested beans have been #Autowired in the application.
Update:
Based on alfcope's answer below, it is important to add the name attribute so that spring can allow the primary bean (mock) to override the original one. Otherwise you get this exception:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
The info message in the spring log shows:
Skipping bean definition for [BeanMethod:name=bar,declaringClass=test.package.MockitoTestConfiguration]: a definition for bean 'bar' already exists. This top-level bean definition is considered as an override.
Example:
I have a simplified example below that works. Here, Bar is the nested inside Foo, and I need to mock Bar for testing:
#Component
public class Foo
{
#Autowired
private Bar bar;
public String getResponseFromBar(String request)
{
String response = bar.someMethod(String request);
//do something else with this reponse
return response;
}
}
#Component
public class Bar {
public String someMethod(String request) {
String response = null;
//do something
return response;
}
}
Now for testing, let's say I want to inject a mockbar instead of the real bar. How can I achieve this in my test class below?
#Profile("test")
#Configuration
public class MockitoTestConfiguration {
//adding the name attribute is important.
#Bean(name="mockBar")
#Primary
public Bar bar() {
logger.debug("injecting mock bar");
return Mockito.mock(Bar.class);
}
}
Actual test case:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath*:test-context.xml")
public class FooTest {
#Autowired
Foo foo;
#Autowired
Bar mockBar; //need this to set up the mock response in the test case.
#Test
public void testAMethodInFoo_WithBarInjectedByMockito() {
//set up the mockBar response
Mockito.when(mockBar.someMethod("1")).thenReturn("1-response");
String response = foo.getResponseFromBar();
assertEquals("1-response", response);
}
}
Based on the ConfigurationClassBeanDefinitionReader code I guess you are using xml configuration to define your main bean. If so, just add a name when creating your mockito bean.
#Bean(name="mockbar")
#Primary
public Bar bar() {
logger.debug("injecting mock bar");
return Mockito.mock(Bar.class);
}
This way Spring will allow you to have both beans, and as you are using #Primary it will be the one used by your tests.
Spring overriding primary bean with non-primary bean
ConfigurationClassBeanDefinitionReader Code
Alternatively, if you use Mockito, you can do this and completely do away with the extra MockitoTestConfiguration class and named primary mock beans in the test profile. Just simply do this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath*:test-context.xml")
public class FooTest {
#Autowired
#InjectMocks
Foo foo;
#Mock
Bar mockBar; //need this to set up the mock response in the test case.
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void testAMethodInFoo_WithBarInjectedByMockito() {
//set up the mockBar response
Mockito.when(mockBar.someMethod("1")).thenReturn("1-response");
String response = foo.getResponseFromBar();
assertEquals("1-response", response);
}
}

Spring autowire HttpServletRequest in integration tests

We have singleton controllers like
#Controller
class C {
#Autowire MyObject obj;
public void doGet() {
// do something with obj
}
}
MyObject is created in a filter/interceptor and is put into HttpServletRequest attributes. Then it's obtained in #Configuration:
#Configuration
class Config {
#Autowire
#Bean #Scope("request")
MyObject provideMyObject(HttpServletRequest req) {
return req.getAttribute("myObj");
}
}
Everything works well in main code, but not in testing: when I run it from an integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/web-application-config_test.xml")
class MyTest {
#Autowired
C controller;
#Test
void test() {
// Here I can easily create "new MockHttpServletRequest()"
// and set MyObject to it, but how to make Spring know about it?
c.doGet();
}
}
it complains that NoSuchBeanDefinitionException: No matching bean of type [javax.servlet.http.HttpServletRequest]. (At first, it complained about request scope is not active, but I resolved it using CustomScopeConfigurer with SimpleThreadScope as suggested here).
How to let Spring injection know about my MockHttpServletRequest? Or directly MyObject?
Workarounded temporarily, but it looks like the right approach: in Config, instead of req.getAttribute("myObj"), write
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
return (MyObject) requestAttributes.getAttribute("myObj", RequestAttributes.SCOPE_REQUEST);
so it does not need a HttpServletRequest instance anymore. And fill it in test:
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute("myObj", /* set up MyObject instance */)
RequestContextHolder.setRequestAttributes(new ServletWebRequest(request));

Resources