I am using the pure example code of simple REST service from the spring guide as a base:
http://spring.io/guides/gs/rest-service/
I have added single Bean configuration:
#Configuration
public class Config {
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST)
public RequestData requestHelper() {
return new RequestData();
}
}
Then my modified controller looks as follows:
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
System.out.println(applicationContext.getBean(RequestData.class));
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
and I am getting
java.lang.IllegalStateException: No Scope registered for scope 'session']
as the result of calling "/greeting"
I have read some description here:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html however I am still confused.
they write:
"The request, session, and global session scopes are only available if you use a web-aware Spring ApplicationContext implementation".
Does it mean that "AnnotationConfigApplicationContext" which I am using is not allowed in such case? Am I forced to use some xml configuration instead?
The quote
web-aware Spring ApplicationContext implementation
refers to an appropriate subclass of WebApplicationContext. You're instantiating a AnnotationConfigApplicationContext which is not a subtype of WebApplicationContext and which does not register the SESSION and REQUEST scopes.
It also makes very little sense to create a brand new ApplicationContext in your #RestController. The #RestController object is already a bean within a Spring WebApplicationContext. Just add your new request scoped #Bean to that context and autowire into your controller.
Related
I am new at spring MVC framework and i am currently working in a web application that uses a session scoped bean to control some data flow.
I can access these beans in my application context using #Autowired annotation without any problem in the controllers. The problem comes when I use a class in service layer that does not have any request mapping (#RequestMapping, #GetMapping nor #PostMapping) annotation.
When I try to access the application context directly or using #Autowired or even the #Resource annotation the bean has a null value.
I have a configuration class as follow:
#Configuration
#EnableAspectJAutoProxy
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class, basePackages = "com.quantumx.nitididea.NITIDideaweb.repository")
public class AppConfig implements WebMvcConfigurer {
#Bean (name = "lastTemplate")
#SessionScope
public LastTemplate getlastTemplate() {
return new LastTemplate();
}
//Some extra code
}
The POJO class is defined as :
public class LastTemplate {
private Integer lastId;
public LastTemplate(){
}
public Integer getLastId() {
return lastId;
}
public void setLastId(Integer lastId) {
this.lastId = lastId;
}
}
The I have a Test class that is annotated as service and does not have any request mapping annotated method:
//#Controller
#Service
public class Test {
// #Autowired
// private ApplicationContext context;
// #Autowired
#Resource(name = "lastTemplate")
public LastTemplate lastTemplate;
// #Autowired
// public void setLastTemplate(LastTemplate lastTemplate) {
// this.lastTemplate = lastTemplate;
// }
public Test() {
}
// #RequestMapping("/test")
public String testing() {
// TemplateForma last = (TemplateForma) context.getBean("lastInsertedTemplate");
// System.out.println(last);
System.out.println(lastTemplate);
// System.out.println(context.containsBean("lastTemplate"));
// System.out.println(context.getBean("lastTemplate"));
System.out.println("Testing complete");
return "Exit from testing method";
// return "/Messages/Success";
}
}
As you can see, there is a lot of commented code to show all the ways i have been trying to access my application context, using an Application context dependency, autowiring, declaring a resource and trying with a request mapping. The bean is null if no controller annotation and request mapping method is used and throws a java null pointer exception when I use the context getBean() methods.
Finally I just test my class in a controller that i have in my app:
#RequestMapping("/all")
public String showAll(Model model) {
Test test = new Test();
test.testing();
return "/Administrator/test";
}
Worth to mention that I also tried to change the scope of the bean to a Application scope and singleton, but it not worked. How can access my application context in a service class without mapping a request via controller?
Worth to mention that I also tried to change the scope of the bean to a Application scope and singleton, but it not worked
It should have worked in this case.
How can access my application context in a service class without mapping a request via controller?
Try one of these :-
#Autowired private ApplicationContext appContext;
OR
Implement ApplicationContextAware interface in the class where you want to access it.
Edit:
If you still want to access ApplicationContext from non spring managed class. Here is the link to article which shows how it can be achieved.
This page gives an example to get spring application context object with in non spring managed classes as well
What worked for me is that session scoped bean had to be removed in the application configuration declaration and moved to the POJO definition as follows:
#Component
#SessionScope
public class LastTemplate {
private Integer lastId;
public LastTemplate(){
}
public Integer getLastId() {
return lastId;
}
public void setLastId(Integer lastId) {
this.lastId = lastId;
}
}
The I just call the bean using #Autowired annotation.
I have a simple spring boot app with the following config:
#Configuration
public class MyConfig {
#Bean
#Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public HashMap<String, BidAskPrice> beanyBoi() {
System.out.println("creating a new one");
return dataFetcher().getPairPricesBidAsk();
}
}
and i have a service
#Service
public class ProfitCalculatorService {
#Autowired
private HashMap<String, BidAskPrice> prices;
public HashMap<String, BidAskPrice> getPrices() {
return prices;
}
}
and i have a controller
#RestController
public class TestController {
#Autowired
ProfitCalculatorService profitCalculatorService;
#GetMapping("/getprices")
public HashMap<String, BidAskPrice> someprices() {
return profitCalculatorService.getPrices();
}
}
}
now when i hit the /getprices endpoint, i was seeing some odd behaviour. The following message is logged twice: "creating a new one".
Your beans have a prototype scope, it means that every time that you asked for this bean a new instance will be created.
Official explanation:
The non-singleton prototype scope of bean deployment results in the
creation of a new bean instance every time a request for that specific
bean is made. That is, the bean is injected into another bean or you
request it through a getBean() method call on the container. As a
rule, you should use the prototype scope for all stateful beans and
the singleton scope for stateless beans.
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-prototype
Another thing is the proxyMode with TARGET_CLASS value. It means that the bean injected into the ProfitCalculatorService is not the BidAskPrice itself, but a proxy to the bean (created using CGLIB) and this proxy understands the scope and returns instances based on the requirements of the scope (in your case prototype).
So, my suggestion is: You don't need to create an explicitly bean of HashMap<String, BidAskPrice>. Since BidAskPrice is a bean, you can inject the HashMap<String, BidAskPrice> directly in your service, Spring will manage this list for you!
I'm writing a Spring app and learning Spring as I go. So far, whenever I find myself wanting to give something a reference to the ApplicationContext, it has meant I'm trying to do something the wrong way so I thought I'd ask before I did it.
I need to instantiate a prototype bean for each request:
#Component
#Scope("prototype")
class ComplexThing {
#Autowired SomeDependency a
#Autowired SomeOtherDependency b
public ComplexThing() { }
// ... complex behaviour ...
}
So I tried this:
#Controller
#RequestMapping ("/")
class MyController {
#GetMapping
public String index (ComplexThing complexThing, Model model) {
model.addAttribute("thing", complexThing);
return "index"
}
}
And I expected Spring to inject a new ComplexThing for the request, just like it injected a Model. But then I found the correct interpretation of that is that the caller is going to send a ComplexThing in the request.
I thought there would be a way of injecting Beans into request handlers, but I don't see one here.
So in this case am I supposed to make my Controller ApplicationContextAware and getBean?
I solved it with the ObjectProvider interface:
#Controller
#RequestMapping ("/")
class MyController {
#Autowired
ObjectProvider<ComplexThing> complexThingProvider;
#GetMapping
public String index (Model model) {
model.addAttribute("thing", complexThingProvider.getObject());
return "index"
}
}
The other benefit of ObjectProvider is that was able to pass some arguments to the constructor, which meant I could mark some fields as final.
#Controller
#RequestMapping ("/")
class MyController {
#Autowired
ObjectProvider<ComplexThing> complexThingProvider;
#GetMapping
public String index (String username, Model model) {
model.addAttribute("thing", complexThingProvider.getObject(username));
return "index"
}
}
#Component
#Scope("prototype")
class ComplexThing {
#Autowired SomeDependency a
#Autowired SomeOtherDependency b
final String username;
public ComplexThing(String username) {
this.username = username;
}
// ... complex behaviour ...
}
Your point is correct. There is no way, the prototype scoped beans (actually every other bean type too) can be directly injected into a controller request handler. We have only 4 options.
Get application Context in the caller, and pass the bean while calling the method. (But in this case, since this is a request handler, this way is not possible).
Making the controller class ApplicationContextAware, set the applicationContext object by overriding setApplicationContext() method and use it to get the bean's instance.
Create a private variable of the bean type and annotate it with #Autowired.
Create a private variable of the bean type and annotate it with #Inject (#Autowired and #Inject have the same functionality. But #Autowired is spring specific).
I want to inject request scoped bean into singleton scoped. I can do it using Provider. The problem occurs when no scope request is not active for a thread. In such case I would like to inject bean in e.g. prototype scope. Is that possible?
E.g. code:
public class Tenant {
private String name;
...
}
#Configuration
public class AppConfig {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_REQUEST)
public Tenant prototypeBean() {
return new Tenant();
}
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Tenant prototypeBean() {
return new Tenant();
}
#Bean
public MySingletonBean singletonBean() {
return new MySingletonBean();
}
public class MySingletonBean {
#Autowired
private Provider<Tenant> beanProvider;
//inject conditionally on request or prototype scope
public void showMessage() {
Tenant bean = beanProvider.get();
}
}
I want to avoid error with this message:
Error creating bean with name 'tenantDetails':
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;
nested exception is java.lang.IllegalStateException: No thread-bound
request found: Are you referring to request attributes outside of an
actual web request, or processing a request outside of the originally
receiving thread? If you are actually operating within a web request
and still receive this message, your code is probably running outside
of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
If something like this is required, then the design of an application should be reconsidered.
Is it possible? Yes.
#Component
#RequestScope
public class RequestScopeTenant implements Tenant {
private String name;
...
}
This way we have request scoped bean.
#Component
#PrototypeScope
public class DefaultTenant implements Tenant {
private String name;
...
}
here we have our default Tenant.
#Configuration
public class AppConfig {
#Primary
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Tenant prototypeBean(RequestScopeTenant requestScopeTenant, DefaultTenant defaultTenant) {
return RequestContextHolder.getRequestAttributes() != null ? requestScopeTenant : defaultTenant;
}
#Bean
public MySingletonBean singletonBean() {
return new MySingletonBean();
}
This way when no request scope is available default tenant is returned.
And again, if you would have to implement something like this - change the design or create custom scope.
I am trying to use a request scoped been inside my servlet 3.0 application.
I am not using the web.xml but an implementation of WebApplicationInitializer. The onStartup method looks like this:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext applicationContext =
new AnnotationConfigWebApplicationContext();
applicationContext.setServletContext(servletContext);
applicationContext.scan("package containing proxy scoped bean and other stuff");
applicationContext.refresh();
servletContext.addListener(new ContextLoaderListener(applicationContext));
servletContext.addListener(new RequestContextListener());
}
and the request scoped bean looks like this:
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CallerInformation {
private String clientIp;
public String getClientIp() {
return clientIp;
}
public void setClientIp(String clientIp) {
this.clientIp = clientIp;
}
}
Now the injected "CallerInformation" is not a CGLIB-proxy but behaves like prototype scoped, it is a different instance in every class and it does not hold any information through the request...
Any ideas what I am doing wrong?
EDIT:
I have tried the same scenario with servlet 2.5 and web.xml config and it worked like hell ;)
the behavior is right.
the instance only lives the one request.
see this site for further information.
try to inject the spring-component with the #Autowired annotation