How to create a request scoped bean at runtime with spring - java

I have a spring application and want to create a bean at runtime per request to inject it into another class, just like #Producer for CDI.
My bean is just a simple POJO:
public class UserDetails {
private String name;
// getter / setter ...
public UserDetails(String name) {
this.name = name;
}
}
My producer class looks like this:
#Configuration
public class UserFactory {
#Bean
#Scope("request")
public UserDetails createUserDetails() {
// this method should be called on every request
String name = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal(); // get some user details, just an example (I am aware of Principal)
// construct a complex user details object here
return new UserDetails(name)
}
}
And this is the class where the UserDetails instance should be injected:
#RestController
#RequestMapping(value = "/api/something")
public class MyResource {
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<String> getSomething(UserDetails userDetails) {
// the userdetails should be injected here per request, some annotations missing?
// do something
}
}
The problem is that Spring complains at runtime about no default constructor (of course).
Failed to instantiate [UserDetails]: No default constructor found
But this is intended and I want to call my own factory to let it handle the Instantiation.
How can I achieve this? Why is UserFactory never called?

Basically you aren't using your scoped proxy. You cannot inject a scoped proxy into a method, you have to inject it into your controller.
public List<String> getSomething(UserDetails userDetails) { ... }
This will lead to spring trying to create a new instance of UserDetails through reflection, it will not inject your scoped bean. Hence it complains about the fact you need a default no-args constructor.
Instead what you should do is wire the dependency into your controller instead of the controller method.
#RestController
#RequestMapping(value = "/api/something")
public class MyResource {
#Autowired
private UserDetails userDetails;
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<String> getSomething() {
// the userdetails should be injected here per request, some annotations missing?
// do something
}
}
The idea is that the UserDetails is a scoped proxy and when used will either use the already present object or create a new one based on the #Bean method.
Additonally, the #Scope annotation in the UserFactory has to be modified as follows in order to work:
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

You're trying to inject a request scoped bean on a singleton bean.
You need to use a proxy for the UserDetails
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)

You need a no arguments constructor in class UserDetails.
public class UserDetails {
private String name;
// getter / setter ...
public UserDetails() {
}
public UserDetails(String name) {
this.name = name;
}
}

Related

Mocking an OpenFeign client for Unit Testing in a spring library and NOT for a spring boot application

I've implemented a feign client that calls a get API based on this official repository. I have a rule class UserValidationRule that needs to call that get API call getUser() and validate some stuff. That works as expected but when I get to testing that rule class, mocking the feign client is not successful and it continues to call the actual API. I've simplified the situation so please ignore the simplicity lol. This is a follow up question I have after i found this stackoverflow question
The API returns this model:
#Data
public class userModel {
private long id;
private String name;
private int age;
}
The interface with the rest client method:
public interface UserServiceClient {
#RequestLine("GET /users/{id}")
UserModel getUser(#Param("id") int id);
}
And in the rule class, i build the feign client and call the API:
#RequiredArgsConstructor
#Component
public class UserValidationRule {
private static final String API_PATH = "http://localhost:8080";
private UserServiceClient userServiceClient;
public void validate(String userId, ...) {
// some validations
validateUser(userId);
}
private void validateUser(String userId) {
userServiceClient = getServiceClient();
UserModel userModel = userServiceClient.gerUser(userId);
// validate the user logic
}
}
private UserServiceClient getServiceClient() {
return Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(UserServiceClient.class, API_PATH);
}
}
And here comes the test class:
public class UserValidationRuleTest {
private UserServiceClient userServiceClient = mock(UserServiceClient.class);
private UserValidationRule validationRule = new UserValidationRule();
private UserModel userModel;
#Before
public void init() {
userModel = generateUserModel();
}
#Test
public void validateWhenAgeIsNotBlank() {
doReturn(userModel).when(userServiceClient).getUser(any());
validationRule.validate("123", ...);
// some logic ...
assertEquals(.....);
verify(userServiceClient).getUser(any());
}
private UserModel generateUserModel() {
UserModel userModel = new UserModel();
userModel.setName("Cody");
userModel.setAge("22");
return accountModel;
}
}
As I debug validateWhenAgeIsNotBlank(), i see that the userModel is not the one that's generated in the test class and the values are all null. If I pass in an actual userId, i get an actual UserModel that I have in my db.
I think the problem is that UserServiceClient is not being mocked. The verify is failing as it says the getUser() is not invoked. It might be something to do with how the feign client is declared in the UserValidationRule with the feign.builder()...
Please correct me if I'm wrong and tell me what I'm missing or any suggestions on how to mock it correctly.
You are not using the spring managed UserServiceClient bean. Every time you call UserValidationRule.validate it calls validateUser which in turn calls the getServiceClient method. This getServiceClient creates a new instance of UserServiceClient for each invocation. This means when testing the mocked UserServiceClient is not in use at all.
I would restructure the code as below;
First either declare UserServiceClient as final with #RequiredArgsConstructor or replace #RequiredArgsConstructor with #AllArgsConstructor. The purpose of this change is to allow an instance of UserServiceClient be injected rather than creating internally in the service method.
#Component
#RequiredArgsConstructor
public class UserValidationRule {
private final UserServiceClient userServiceClient;
.... // service methods
}
Then have a separate Configuration class that builds the feign client as spring bean;
#Bean
private UserServiceClient userServiceClient() {
return Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(UserServiceClient.class, API_PATH);
}
At runtime this bean will now be injected into the UserValidationRule.
As for the unit test changes you are creating the mock correctly but aren't setting/injecting that mock anywhere. You either need to use #Mock and #InjectMocks annotations or manually create instance of UserValidationRule in your #Before method.
Here is how #Mock and #InjectMocks use should look like;
#RunWith(MockitoJUnitRunner.class)
public class UserValidationRuleTest {
#Mock private UserServiceClient userServiceClient;
#InjectMocks private UserValidationRule validationRule;
... rest of the code
or continue using mock(...) method and manually create UserValidationRule.
public class UserValidationRuleTest {
private UserServiceClient userServiceClient = mock(UserServiceClient.class);
private UserValidationRule validationRule;
private UserModel userModel;
#Before
public void init() {
validationRule = new UserValidationRule(userServiceClient);
userModel = generateUserModel();
}
... rest of the code
This will now ensure you are using single instance of spring managed feign client bean at runtime and mocked instance for the testing.

Not able to access Spring boot application.properties

I am trying to access application.properties values in spring boot application's service class. But every time value is null, so it throws NullPointException. I can get the right value for port in controller class(if i add #Autowired in controller) but not in service class. What changes are required to make this properties available through out the application?
Controller looks like:
#RestController
public class MyController {
MyService ss = new MyService();
#RequestMapping(value = "/myapp/abcd", method = RequestMethod.POST, consumes = {"application/json"})
public ResponseMessage sendPostMethod(#RequestBody String request) {
log.debug(" POST Request received successfully; And request body is:"+ request);
ResponseMessage response = ss.processRequest(request);
return response;
}
}
And Service class is:
#Service
public class MyService {
private ApplicationProperties p;
#Autowired
public setProps(ApplicationProperties config) {
this.p = config;
}
public ResponseMessage processRequest(String request) {
System.out.println("Property value is:"+ p.getPort());
}
}
ApplicationProperties.java looks like:
#Component
#Getter
#Setter
#ConfigurationProperties
public class ApplicationProperties {
String port;
}
Finally, application.properties file has:
port=1234
I have even tried passing ApplicationProperties instance from controller to service's default constructor, but it did not work. When I debug, value persist while application startup, but when I make a rest web service POST call, it is null.
In your controller MyController, you have to inject the service, replacing:
MyService ss = new MyService();
by:
#Autowired
MyService ss;
Also, instead of your ApplicationProperties class, you can use #Value annotation, from Spring, to load properties from application.properties file. Take a look at this code:
import org.springframework.beans.factory.annotation.Value;
// ...
#Service
public class MyService {
#Value("${port}")
private String port;
// ...
}
I had the same issue but for other reasons.
For me the solution was to add spring. before each parameter in application.properties.
So e.g. "spring.flyway.user=root".

REQUEST_SCOPE bean with TARGET_CLASS proxy mode sees only null parameters when injected in other components, like #Service's or #Repository's

I have a java bean defined as REQUEST_SCOPE and with TARGET_CLASS proxy mode. Something like this:
#Configuration
public class TestObjectConfiguration {
#Bean("test-object")
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserInfo testObject() {
TestObject object = new TestObject();
object.setParameter("value");
return object;
}
}
The value is set on each request, taking some values from request headers, and that is the reason why I need test-object bean to be request scoped. For simplicity, in this example I just assign a "value" String.
Then I want to retrieve it from an #Service or #Repository (aka Singleton beans). I do it like this:
#Service
public class MyServiceImpl implements MyService {
#Autowired
#Qualifier("test-object")
private TestObject object;
#Override
public void myMethod () {
logger.info(object.getParameter());
}
}
(Assume a logger is defined in the service)
The value of object.parameter is null. Inspecting the bean, it is instantiated and I see CGLIB parameters within it, but any elements inside are null.
The class is simple, just private Strings and a List<String>, with the corresponding setters and getters. Something similar to this:
public class TestObject {
private String parameter;
public String getParameter() {
return parameter;
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
}
I have checked several documentation sources, and they state that this combination should work. In practice, I see the bean instantiated at random times during the request (classes with #Controller, that call singleton #Service, that call DB #Repository), but always with all parameters null.
What I need is to store somehow information gathered from the headers, and make it accessible to #Service and #Repository classes during the request.
Any ideas on what am I missing? Does the proxy mode require some configuration I am forgetting or not finding in documentation?

Why are #Bean Generics creation methods on a superclass called later than ones on the child-class in Spring Boot?

I have a spring boot base abstract config class that creates a bean. If I then inherit from it, the bean will be created later than my controller (which needs to auto-wire it and thus fails). Note: it does get created, just after the controller. So it can't be auto-wired but has to be found via appContext.getBean( BeanType.class )
If I instead override the bean method in the child class, then it's created before the controller and it can be auto-wired.
How can i fix this and make the super-class bean definition load at the same time as the child class?
#SpringBootApplication
public class ChildConfig extends ParentConfig<PCTestBean>
{
public ChildConfig()
{
super();
}
#Override
public PCTestBean getT()
{
return new PCTestBean();
}
}
public abstract class ParentConfig<T>
{
public ParentConfig() {}
#Bean
public T createTestBean()
{
return getT();
}
public abstract T getT();
}
public class PCTestBean
{
}
#RestController
#RequestMapping( "/client" )
public class MyController
{
#Autowired
private PCTestBean pcTestBean;
#RequestMapping( "/get" )
#ResponseBody
public String getClient(HttpServletRequest request) throws Exception
{
return pcTestBean.toString();
}
}
#RunWith( SpringJUnit4ClassRunner.class )
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
#ContextConfiguration(
classes = {
ChildConfig.class
}
)
public class TestConfigs
{
#LocalServerPort
private String port;
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Before
public void setUp() throws Exception
{
mockMvc = MockMvcBuilders
.webAppContextSetup( context )
.build();
}
#Test
public void testValidCall() throws Exception
{
MvcResult result = mockMvc.perform(
MockMvcRequestBuilders.get( new URI( "http://localhost:" + port + "/client/get" ) )
)
.andExpect( MockMvcResultMatchers.status().isOk() ).andReturn();
System.out.println( result.getResponse().getContentAsString() );
}
}
When Spring scans your configuration class, ChildConfig, it discovers this inherited method
#Bean
public T createTestBean() {
return getT();
}
and registers a bean definition for it. That bean definition contains metadata about the type of the bean. That type is inferred from the return type of the method. In this case, it's resolved to Object because the type variable T has no bounds in its declaration and because Spring doesn't try to resolve it based on the type argument provided in ChildConfig's extends ParentConfig<PCTestBean> clause.
When Spring then tries to process the
#Autowired
private PCTestBean pcTestBean;
injection target, it looks for a PCTestBean bean, which it doesn't think it has, because the metadata is lacking. IF the bean hasn't been initialized through some other forced order, then Spring has no other information to go on and thinks the bean doesn't exist.
When you change your code to
instead override the bean method in the child class
the return type of the method is PCTestBean which Spring can then match to the #Autowired injection requirement, find (and initialize) the bean, and inject it.
By the time you use ApplicationContext#getBean(Class), the PCTestBean has been initialized. Spring can therefore rely on the actual type of the instance. It'll more or less loop through all beans and check whether beanClass.isInstance(eachBean), returning the one that matches (or failing if more than one does).
Pankaj, in their answer, suggests using #DependsOn (it was wrong when they suggested it, before you edited your question). That can help establish the order I mentioned earlier.
I don't how extensive your configuration class is that you think you need generics to abstract some behavior away, but I would suggest just dropping the generic behavior and be explicit in each class.
Try DependsOn annotation, it guarantees that the child bean should be created after the parent bean
#Configuration
public class ChildConfig extends ParentConfig
{
public ChildConfig()
{
super();
}
#DependsOn("parentConfig")
#Override
public TestBean createTestBean()
{
return super.createTestBean();
}*/
}
public abstract class ParentConfig
{
public ParentConfig() {}
#Bean (name ="parentConfig")
public TestBean createTestBean()
{
return new TestBean();
}
}

Session scoped bean behaves as Request scope bean

I have Session scope bean which behaves as request scope bean.
I'm using it in Singleton bean, and maybe that's why with second request is has null values?
Code which I used to setup the bean:
In singleton:
#Autowired
private MyBean myBean;
MyBean class:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanImpl implements MyBean, Serializable {
Configuration:
#Configuration
#ComponentScan(scopedProxy = ScopedProxyMode.INTERFACES, value = { "my.package.with.bean" })
public class ComponentsConfig {
}
MyBean is simple pojo. with getters and setters.
With first request I set values on that bean, with second request in the same class (singleton) I want to read those values, but all values are null. There is no way that something has overrdiden those values.
EDIT
How I make requests - It's just simple browser request, and code which read/writes to my session bean lays in filters.
This is singleton:
#Service
public class SingletonBean {
#Autowired
private MyBean myBean;
// here I save the data to session scoped bean
private void saveUser(LdapContext ctx, String principal) throws NamingException {
Attributes attributes = getAttributes();
myBean.setId("id");
myBean.setName("name");
myBean.setEmail("email");
myBean.setTelNum("123");
myBean.setGroups(Lists.newArrayList("one", "two"));
myBean.setAddress("address");
}
// here I try to read data from session scoped bean:
#Override
protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals,
LdapContextFactory ldapContextFactory) throws NamingException {
// here userInfo will be null
List<String> userInfo = myBean.getGroups();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (String role : userInfo ) {
authorizationInfo.addRole(role);
}
return authorizationInfo;
}
}
When user logs in, and he is authenticated, I save his details in session bean. When he tries to open any page method queryForAuthorizationInfo is executed (after chain of filters) and values are null in that object.

Categories