I want to initialize a collection and fill it with data at the startup of my application. Then I would like to access it everywhere in my application. I want for example that my REST API can access the shared collection that is already populated with data.
I first tried to do it with an startup class annotated with #Startup and #Singleton. When I injected my userService in there I had some problems and because of the advice in this post: #Inject and #PostConstruct not working in singleton pattern I tried to do it with an #ApplicationScoped annotation:
#Named
#ApplicationScoped
public class KwetterApp {
#Inject
private UserService service;
#PostConstruct
public void init() {
try {
User harry = new User("Harry", "harry#outlook.com", "New York", "http://harry.com", "Hi, I'm Harry!", UserType.REGULAR);
User nick = new User("Nick", "nick#outlook.com", "California", "http://nick.com", "Hi, I'm Nick!", UserType.REGULAR);
User jane = new User("Jane", "jane#outlook.com", "Texas", "http://jane.com", "Hi, I'm Jane!", UserType.REGULAR);
Tweet tweet = new Tweet("eating...", harry);
Tweet tweet1 = new Tweet("swimming...", harry);
Tweet tweet2 = new Tweet("jogging...", jane);
harry.addTweet(tweet);
harry.addTweet(tweet1);
jane.addTweet(tweet2);
service.create(harry);
service.create(nick);
service.create(jane);
}
catch (Exception e){
e.printStackTrace();
}
}
public UserService getService() {
return service;
}
}
I inject this class in my rest service:
#RequestScoped
#Path("/user")
public class UserRest {
// #Inject
// private UserService userService;
#Inject
private KwetterApp kwetterApp;
// private KwetterApp kwetterApp = KwetterApp.getInstance();
private UserService userService = kwetterApp.getService();
#GET
#Produces({"application/json"})
public List<User> get() throws UserException {
return userService.getAll();
}
}
When injecting this KwetterApp it leads to the following exception:
StandardWrapperValve[rest.RestApplication]: Servlet.service() for servlet rest.RestApplication threw exception
java.lang.NullPointerException
at rest.UserRest.<init>(UserRest.java:27)
at rest.UserRest$Proxy$_$$_WeldClientProxy.<init>(Unknown Source)
I have an empty beans.xml file with bean-discovery-mode set to 'all'. The CDI framework should recognize my KwetterApp class for injection, right? Why is it null?
Thanks in advance,
Mike
Here
#Inject
private KwetterApp kwetterApp;
private UserService userService = kwetterApp.getService();
I do not think the kwetterApp field is going to be set before userService.
CDI will set that field after the object has been constructed.
An alternative, which should be used anyway, is constructor injection
#RequestScoped
#Path("/user")
public class UserRest {
private KwetterApp kwetterApp;
private UserService userService;
protected UserRest() {}
#Inject
public UserRest(final KwetterApp kwetterApp) {
this.kwetterApp = kwetterApp;
this.userService = kwetterApp.getService();
}
#GET
#Produces({"application/json"})
#Override
public List<User> get() throws UserException {
return userService.getAll();
}
}
A protected constructor is needed because #RequestScoped is a normal-scoped bean, and it must be proxiable, as described in the specification.
The only annotation that doesn't require an empty constructor is #Singleton (from javax.inject).
If you want to initialize an object by using an injected bean, then you have to use a #PostConstruct annotated method, because injected CDI beans are only available in CDI in a #PostContruct annotated method and afterwards and not during field initialization or the constructor invocation.
Therefore that the UserService is a CDI bean already, you can just inject it into your rest service bean, because it will be the same bean used within the current active scope. KwetterApp is available within the current active scope, so UserService will be as well. Only #Dependend scoped beans behave different, whereby each bean gets its own instance provided.
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'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'm looking for a way to list the beans that are injected into a particular Spring bean at runtime. For example, given these two classes:
#Controller
public class TestController {
#Autowired
private TestComponent testComponent;
private final TestService testService;
public TestController(TestService testService) {
this.testService = testService;
}
}
and
#Service
public class TestService {
}
and
#Component
public class TestComponent {
}
The list of beans for the TestController class should return:
TestService (injected via constructor)
TestComponent (injected via #Autowired annotation)
Is there an existing Spring helper/utility that can return this information for me?
You can query names of dependent beans from the ConfigurableBeanFactory for a given bean name with the method getDependenciesForBean(). So in your example the code could look like
try (ConfigurableApplicationContext app = SpringApplication.run(MySpringApplication.class)) {
ConfigurableListableBeanFactory beanFactory = app.getBeanFactory();
String[] dependencies = beanFactory.getDependenciesForBean("testController");
System.out.println(Arrays.toString(dependencies)); // [testService, testComponent]
}
The problem hereby is that you only work on names of beans. So to make the code generic for a given bean instance you would have to find out the name of the bean (which can be non-unique) and also when getting the actual injected beans for these names it can be possible that you don't get the same instances (because of #Scope(SCOPE_PROTOTYPE) on the bean definition).
As the question suggests, how do you Autowire a class with non SpringBoot managed class as constructor args.
The following is a code block illustrating this:
#Component
class Prototype
{
#Autowired
private Repository repository;
private NonSpringBootManagedBean bean;
Prototype(NonSpringBootManagedBean bean)
{
this.bean = bean;
}
}
#Component
class PrototypeClient
{
#Autowired
private ApplicationContext context;
private void createNewPrototype(NonSpringBootManagedBean bean)
{
// This throws an error saying no bean of type NonSpringBootManangedBean found
Prototype prototype = context.getBean(Prototype.class, bean);
}
}
The reason I am using ApplicationContext to obtain an instance of Prototype instead of using #Autowired is because I need a new instance of Prototype within the method createNewPrototype() every time it's invoked and not a singleton instance (Also, please advise if this way obtaining a new instance is incorrect).
Update:
As others have stated to move my creation of bean to a Java configuration class and adding method annotated by #Bean and instantiating the NonSpringBootManagedBean in the #Bean method. But I think this is not possible as this NonSpringBootManagedBean is passed by caller of PrototypeClient.createNewPrototype().
Update
I have updated my above code example with a more clarity. Please refer this now.
#Component
class Prototype
{
#Autowired
private Repository repository;
// Here Session is part of javx.websocket package and cannot be added as part of
// Java configuration class with a #Bean annotation
// In this case how can I use constructor injection?
private Session session;
Prototype(Session session)
{
this.session = session;
}
}
#Component
class PrototypeClient
{
#Autowired
private ApplicationContext context;
private void createNewPrototype(Session session)
{
Prototype prototype = context.getBean(Prototype.class, session);
}
}
#ServerEndpoint(value = "/resources")
class WebSocketController
{
private PrototypeClient client = ApplicationContext.getBean(PrototypeClient.class);
#OnMessage
void handleMessage(Session session, String message)
{
client.createNewPrototype(session);
}
}
Did you know that you can change your bean scope to be a prototype reference instead of a singleton. That way you can scope a single bean definition to any number of object instances.
https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html
private NonSpringBootManagedBean bean = new NonSpringBootManagedBean();
#Bean
public Prototype getPrototype(){
return new Prototype(bean);
}
Spring can not Autowire an Object if it is not aware of it. Some where there need to be #Component or #Bean or some other annotation like #Service etc to tell spring to manage the instance .
Also it is suggested that if you are using a private variable in Autowire it should be part of constructor(for constructor injection ) or a setter method must be provided(setter injection)
To solve your error : you can create a java config class and place it in you base pkg (same as #SpringBootApplication or add #ComponentScan("pkg in which config is present") on class with #SpringBootApplication)
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
#Configuration
public class myconfig {
#Bean
public NonSpringBootManagedBean nonSpringBootManagedBean()
{
return new NonSpringBootManagedBean();
}
}
Define a bean with scope prototype
That is each time injected as new instance.
In SpringBoot you can use the annotation #Scope("prototype") to your bean class Prototype.
#Component
#Scope("prototype")
class Prototype {
#Autowired
private Repository repository;
private NonSpringBootManagedBean bean;
Prototype() {
// you can only modify this 'NonSpringBootManagedBean' later
// because Spring calls constructor without knowing NonSpringBootManagedBean
this.bean = new NonSpringBootManagedBean();
// do something with 'repository' because its defined
}
public void setNonSpringBootManagedBean(NonSpringBootManagedBean bean) {
this.bean = bean;
}
}
Use instances of this bean
Via injection (e.g. #Autowired to constructor) you can use different instances of this prototypical bean within other beans.
#Component
class PrototypeClient {
// ApplicationContext still used?
#Autowired
private ApplicationContext context;
private Prototype prototypeInstance;
#Autowired // injects the new instance of Prototype
public PrototypeClient(Prototype p)
this.prototypeInstance = p;
// here you can change the NSBMB
modifyPrototype();
}
private void modifyPrototype(NonSpringBootManagedBean bean) {
this.prototypeInstance.setNonSpringBootManagedBean( new NonSpringBootManagedBean() );
}
}
Why is your exception thrown?
no bean of type NonSpringBootManangedBean found
Spring complains when trying to instantiate the bean of type Prototype
Prototype prototype = context.getBean(Prototype.class, bean);
because for calling its constructor it needs to pass an argument of type NonSpringBootManagedBean. Since all this bean-instantiating is done internally by Spring(Boot), you can not intercept and tell Spring: "Hey, use this bean of class NonSpringBootManagedBean" like you tried in method createNewPrototype(NonSpringBootManagedBean bean).
Why could'nt the NonSpringBootManagedBean be managed as bean by Spring(Boot)?
Autowiring in SpringBoot is a way of dependency-injection. This means a bean has been previously instantiated by SpringBoot, automatically at startup (when Spring boots). And this bean is now injected as dependency into another bean, etc. because this other bean depends on it.
If you tell us some more background, we could possibly bring light into your situation. This can be some answers to:
What is NonSpringBootManagedBean and why is it no managed bean?
What is Prototype and for which purpose does it use NonSpringBootManagedBean?
What is PrototypeClient and why does it create its own Prototype ?
I am not sure if I have understood the relationship and purpose between your objects/classes.
I want to call a common service for all the controllers to get a commmon ModelAndView object with some common objects inside it.
So I created a superclass for all the controllers- BaseController, and I am initiating the common model object inside the constructor of BaseController by calling the a method, initCommonData which uses a #Autowired bean CommonDataService, which is not present at the construction time of object and returns null, so what I should do to get #autowired dependency inside constructor.
FYI - I am using this common servie and common data to get some commod data which will be used on each jsp in header and footer of the site.
So if there is some another way of doing this without calling the common service in each controller method, in each controller please suggest.
Here is my code -
BaseController
#Controller
public class BaseController {
#Autowired
private CommonDataService commonDataService;
protected ModelAndView model;
public BaseController() {
this.initCommonData();
}
public void initCommonData(){
this.model = new ModelAndView();
this.model.addObject("headerData",commonDataService.getHeaderData());
this.model.addObject("footerData",commonDataService.getFooterData());
}
subclass controller -
#Controller
public class HomeController extends BaseController {
#Autowired
CategoryService categoryService;
#Autowired
CompanyService companyService;
#RequestMapping(value = { "", "/", "home" })
public ModelAndView homePage() {
model.setViewName("home");
.
.
.
model.addObject("name", value);
model.addObject("name2", value2);
return model;
}
CommonServiceClass -
#Service
public class CommonDataService {
#Autowired
CompanyService companyService;
#Autowired
CategoryService categoryService;
#Cacheable
public List<Category> getHeaderData(){
return categoryService.getTopCategoryList();
}
#Cacheable
public List<Company> getFooterData(){
return companyService.getTopCompanyList();
}
Please suggest if there is any other good way of doing this, getting common data from server to jsp.
Whatever #Andreas has suggested is the best solution i.e., mark your BaseController as abstract and use #Postconstruct. This makes perfect scense because BaseController itself does not own any url mappings in your case, so do not mark it as #Controller
Because of any reason, if you are looking for other options, you can consider marking your BaseController as #Component and use #Postconstruct for initCommonData so that this method will be called automatically once the BaseController bean has been loaded by the spring container:
#Component
public class BaseController {
#Autowired
private CommonDataService commonDataService;
protected ModelAndView model;
#Postconstruct
public void initCommonData(){
this.model = new ModelAndView();
this.model.addObject("headerData",commonDataService.getHeaderData());
this.model.addObject("footerData",commonDataService.getFooterData());
}
}
First, remove #Controller from your base class. You might even make the class abstract to help indicate/document that it must be subclassed.
Next, don't call initCommonData() from the constructor. Spring cannot inject field values until after the object is created, so there is no way for Spring to wire in commonDataService before constructor completes.
Instead, annotate initCommonData() with #PostConstruct:
public class BaseController {
#Autowired
private CommonDataService commonDataService;
protected ModelAndView model;
#PostConstruct
public void initCommonData(){
this.model = new ModelAndView();
this.model.addObject("headerData",commonDataService.getHeaderData());
this.model.addObject("footerData",commonDataService.getFooterData());
}