Spring Boot Request Scoped Beans Autowired not Populated - java

I'm new to Spring Boot, I only like to use its dependency injection, not all its opinionated frameworks but some. I know there are other alternatives for this functionality, but I would like to learn more Spring.
I am trying to have a request scoped bean and populate it with regular dependency injection. My plan is to have some User object that I can populate hold some custom business details that is easy to access that makes code clean.
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
class MyBean {
#Autowired
lateinit var req: HttpServletRequest
#Autowired
lateinit var env: Environment
#PostConstruct
fun pc() {
println("I am constructed $this, $req, $env")
}
var a = 3
}
#RestController
class MyController {
#GetMapping("/api/xyz")
fun login(m: MyBean): Int {
println("new bean m")
return m.a
}
}
Every time I hit that endpoint, I see new objects are instantiated. However the inner dependencies are not autowired, they are always null. What am I doing wrong? Do I need to write a filter? But how would I know how to find all the endpoints that needs that bean to be initialized? If, I remove the request scope the variables are initialized.

RestControllers are beans like Components or Services. So in all of these you should deal with injected dependencies the same way.
Coming to the example you were adding, beans should not be parameters of actual endpoint mappings. Asking for request specific information like headers, path variables or the payload body are candidates for endpoint mapping method arguments.
For a bean instance of your class MyBean you should rather use a member variable injected directly or via the constructor of your MyController (where constructor based injection is the recommended approach).
Don't be confused when injecting your MyBean which is RequestScoped in the constructor of MyController: this is totally intended and works. Spring actually injects a proxy instance that will resolve to the request specific instance once your request is reaching the controller invocation.

Related

Builder class using Spring services

I would like to create a builder class (i.e. a class implementing the builder design pattern) in a Spring project.
The problem is that I need the builder to use some Spring services.
I obviously don't want the user to explicitly provide the services in the constructor, but when I tried to #Autowire the services, I got Autowired members must be defined in valid Spring bean. I could Annotate my builder with #Component, but that would make it a singleton which will not be desirable for a builder.
How can I inject services to my builder class without making it a singleton?
To use the example from this article, lets say I have the following builder:
BankAccount account = new BankAccount.Builder(1234L)
.withOwner(25324)
.atBranch("Springfield")
.openingBalance(100)
.atRate(2.5)
.build();
I want withOwner to use my UserService to get the actual user from the database given the id number received as a parameter. How would I go about injecting UserService to builder?
There are 2 ways to do that:
1) Put service into withOwner() method
new BankAccount.Builder(1234L)
.withOwner(25324, userService)
2) Add UserService to the Builder and create a builder factory:
#Component
class BuilderFactory {
#Autowire
private UserService user service
BankAccount.Builder newBuilder(Long id) {
return BankAccount.Builder(service, id);
}
}
Usage:
builderFactory.newBuilder(1234L)
.withOwner(25324)
How would I go about injecting UserService to builder?
In your spring bean definitions, you cannot and have not to mix objects managed by Spring and these created by yourself and which Spring is not aware.
While you may make it working but it should be used only in very specific rare cases and generally for legacy/third party dependencies reasons, not in a code where you can change that.
Definitively, you want to inject beans dependencies in beans.
This runtime error message means that you don't respect this rule :
but when I tried to #Autowire the services, I got Autowired members
must be defined in valid Spring bean
About :
I could Annotate my builder with #Component, but that would make it a
singleton which will not be desirable for a builder.
singleton is the default scope but Spring allows you to specify other scopes for a component. Just define it as a prototype bean and it will create a new instance of it at each call.
public class BankAccount{
// ...
#Component
#Scope(value="prototype")
public static class Builder{
//...
}
}

How to reinit bean object in previously autowired objects?

I need to reinit singleton bean instance for all previously autowired fields.
I tried to remove the bean from registy in app context, and re-register new object.
ConfigurableApplicationContext configContext = (ConfigurableApplicationContext)appContext;
SingletonBeanRegistry beanRegistry = configContext.getBeanFactory();
((DefaultListableBeanFactory) beanRegistry).destroySingleton("fbdProg");
((DefaultListableBeanFactory) beanRegistry).registerSingleton("fbdProg", program);
It works only once, the first time it is called.
The issue is how to reinit the bean to make new instance available in other classes, where it is autowired
If you need a new instance of this bean everytime it is injected, you can use #Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) on your bean. E.g:
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyComponent {
// methods, fields, etc.
}
Spring then creates a new instance of MyComponent everytime it is injected into another component. So Bean A will have a different instance of MyComponent than Bean B, evaluating beanA.myComponent != beanB.myComponent to true
Every class with a #Bean annotation is a singleton by default in spring boot, but you can provide a #Scope annotation if you want that the lifehook of a variable to be related to
a session
an http request
the lifehook of a serveletContext
anyway you have to keep in mind that #Bean should be singleton, for example in the persistence pattern you have an entity and a repository. The respository should be a singletone because it essentialy gives only methods to store and retrieve entities, while an entity should not be a singletone because you need an entity for every object you have to store in a database. The same for services. You may also want to taake a look at this article: spring boot scopes

How do HttpSession and HttpServletRequest inject into Spring by #Autowired? [duplicate]

As I know per default are controllers in Spring MVC singletons. HttpServletRequest passed offen to the controller handler method. And its ok, while HttpServletRequest is request-scoped, but I see often HttpServletRequest gets #Autowired into the controller field, like this:
#Controller("CMSProductComponentController")
#RequestMapping(CMSProductComponentController.CONTROLLER_PATH)
public class CMSProductComponentController {
#Autowired
private HttpServletRequest request;
}
Could be this a problem? And more general question: What happens if inject a reqeust-scoped component into a singleton?
No, for HttpServletRequest it will not be a problem and it shouldn't for other request scoped beans. Basically, Spring will generate a proxy HttpServletRequest that wraps some kind of ObjectFactory (RequestObjectFactory for HttpServletRequest) (YMMV) that knows how to retrieve the actual instance. When you use any of the methods of this proxy, they will delegate to that instance.
What's more, this is done lazily, so it won't fail at initialization. It will however fail if you try to use the bean when there is no request available (or if you haven't registered the RequestScope).
The following is in response to the comments and to clarify in general.
Regarding the proxy-mode attribute of #Scope or the XML equivalent, the default is ScopedProxyMode.NO. However, as the javadoc states
This proxy-mode is not typically useful when used with a non-singleton
scoped instance, which should favor the use of the INTERFACES or
TARGET_CLASS proxy-modes instead if it is to be used as a dependency.
With request scoped beans, this proxy-mode value will not work. You'll need to use INTERFACES OR TARGET_CLASS depending on the configuration you want.
With scope set to request (use the constant WebApplicationContext.SCOPE_REQUEST), Spring will use RequestScope which
Relies on a thread-bound RequestAttributes instance, which can be
exported through RequestContextListener, RequestContextFilter or
DispatcherServlet.
Let's take this simple example
#Component
#Scope(proxyMode = ScopedProxyMode.INTERFACES, value = WebApplicationContext.SCOPE_REQUEST)
public class RequestScopedBean {
public void method() {}
}
...
#Autowired
private RequestScopedBean bean;
Spring will generate two bean definitions: one for your injected bean, a singleton, and one for the request scoped bean to be generated on each request.
From those bean definitions, Spring will initialize the singleton as a proxy with the types of your target class. In this example, that is RequestScopedBean. The proxy will contain the state it needs to produce or return the actual bean when it is needed, ie. when a method is called on the proxy. For example, when
bean.method();
is called.
This state is basically a reference to the underlying BeanFactory and the name of the request-scoped bean definition. It will use these two to generate a new bean and then call method() on that instance.
The documentation states
The Spring IoC container manages not only the instantiation of your
objects (beans), but also the wiring up of collaborators (or
dependencies). If you want to inject (for example) an HTTP request
scoped bean into another bean, you must inject an AOP proxy in place
of the scoped bean. That is, you need to inject a proxy object that
exposes the same public interface as the scoped object but that can
also retrieve the real, target object from the relevant scope (for
example, an HTTP request) and delegate method calls onto the real
object.
All eagerly loaded request scoped beans, if implemented correctly, will be proxies. Similarly, request scoped beans that aren't eagerly loaded will either be proxies themselves or be loaded through a proxy. This will fail if there is no HttpSerlvetRequest bound to the current thread. Basically, a proxy is necessary somewhere in the bean dependency chain for request scoped beans.
What happens if inject a reqeust-scoped component into a singleton?
Try it and you'll get a BeanCreationException¹ during application context initialization. The error message clearly explains why this doesn't happen with HttpServletRequest:
Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton;
So obviously HttpServletRequest is a scoped proxy. If you want to use beans of smaller scopes in singletons they have to be proxies. The documentation elaborates about smaller scoped dependencies in Scoped beans as dependencies.
[1]: unless you didn't change the default behaviour for proxyMode, which is NO or try to inject it with #Lazy. The latter might result into a valid application context but might lead to request scoped beans acting like singletons (e.g. if a request scoped bean is injected into a singleton).

If one bean is managed by Spring does this mean all created beans also have to be managed

If I have a Spring bean that contains another non Spring bean like so :
#Component
class SpringBean {
private MyBean mb = new MyBean();
}
I receive error : unable to wire bean MyBean
But if I declare the Bean within a method :
#Component
class SpringBean {
private void myMethod(){
MyBean mb = new MyBean();
}
}
Then the bean is initialized correctly when the bean is called.
So does Spring not allow non Spring beans to be created by Spring beans ? What is the reasoning behind this?
Only beans created by the Spring container will be managed by Spring and provide dependency injection and all the benefits (and problems) that Spring gives you.
So does Spring not allow non Spring beans to be created by Spring beans ?
Spring beans are POJOs after all, so they can be manually created like any class with a public constructor. This means, you can do:
public void foo() {
SpringBean springBeanNotManagedBySpring = new SpringBean();
//use springBeanNotManagedBySpring...
}
But, as stated at the beginning of my answer, springBeanNotManagedBySpring is not managed by Spring, so it won't have the benefits of a Spring managed bean.
Note that you're doing two different things in your example:
In the first code, you have a field in the class of type MyBean and Spring tries to inject it but looks like doesn't find a Spring bean to inject. You should post the configuration of your bean in order for us to do a better analysis on this.
In the second code, you're creating a local variable (completely different from a field in class) of type MyBean, where the instantiation is up to the programmer.

SpringMVC #Scope session not creating threads

Say I have the following class...
#Controller
public class WebController {
#Autowired PersonService personService;
#RequestMapping(value = "/get", method = RequestMethod.GET)
#ResponseBody
#Scope("session")
public List<Player> getPerson(String personName) {
return playerService.getByName(personName);
}
}
Now this invokes the following service...
#Service("playerService")
public class PlayerServiceImpl implements PlayerService {
private List<Player> players;
#Override
#Transactional
public List<Player> getByName(final String name) {
if (players == null) {
players = getAll();
}
return getValidPlayers(name);
}
If I initially start my application, players is null, correctly, then when in the same session, I invoke this method again with a new value, players is no longer null, as you would expect. However, no new thread appears to be being created, if I open a new browser window (therefore creating a new session) and invoke this method, it still has the values from the previous session.
Why is #Scope("session") not creating a new thread in the thread pool?
I've specified <context:component-scan base-package="com." /> in my servlet-context as expected, everything works fine apart from the service methods are all acting as singletons rather than creating a new thread per session like say a Java EE container.
If players was marked as static I'd understand.
I've also tried marking my controller as #Scope("session") (as shown below) but this appears to have no impact either. What's the best way to make my Spring app create a new thread for a new session?
#Controller
#Scope("session")
public class PlayerController {
You are using #Scope annotation the wrong way.
Quoting the docs:
When used as a type-level annotation in conjunction with the Component annotation, indicates the name of a scope to use for instances of the annotated type.
When used as a method-level annotation in conjunction with the Bean annotation, indicates the name of a scope to use for the instance returned from the method.
So you can annotate either a spring component bean or a method that creates a bean if you're using java config. Java config is the only reason it even compiles (it wouldn't in pre 3.0 spring)
In your case that annotation is on a normal bean method where it doesn't mean anything.
Solving the right problem
It looks like you're trying to implement db cache by storing query results in a List<Player> players.
Don't do that. Use one of the prebuilt cache abstractions (spring has a very nice one) instead.
So where should #Scope go?
Annotating #Controller with #Scope("session") won't help as it will create session scoped controllers but the service they have injected is still a singleton.
Annotating only Service bean won't work either, cause #Controller is a singleton and it's dependencies are autowired on application startup.
Annotating both #Service and #Controller might work, but seems a bit heavy handed.
It's better to avoid state at all.
New threads are created for each request.
Your service has an instance variable (players) which is not threadsafe - it is shared by all threads. Any spring bean - including controllers and services are by default a singleton, you need to specify on the service annotation its scope.
#Service("playerService")
#Scope("session")
public class PlayerServiceImpl
But its best(simpler, easier to scale) to keep beans singletons and not rely on instance variables (unless they are also managed by spring/threadsafe/singletons).

Categories