How are the beans retrieved in Spring-MVC? - java

In a simple spring application you register your bean in the spring IoC container by using #Component annotation and then to retrieve the bean you first read the spring configuration file and then retrieve the bean from container using:
ClassPathXMLApplicationContext context = new ClassPathXMLApplicationContext("spring config file")
Coach theCoach=context.getBean("beanId","classname")
Now, you can call the methods on theCoach.
How are we retrieving the bean from the container as we are not using
context.getBean();
Is the DispatcherServlet handling this?
After editing-
/*********************Spring Application*******************************/
applicationContext.xml
<beans _______>
<context:component-scan base-package="packageName"/>
</beans>
Coach.java
public interface Coach{
public String getDailyWorkOut();
}
TennisCoach.java
#Component
public class TennisCoach implements Coach{
public String getDailyWorkOut(){
return "practise back hand volley";
}
ApplicationDemo.java
public class ApplicationDemo{
public static void main(String[] args){
ClassPathXMLApplicationContext context = new ClassPathXMLApplicationContext("applicationContext.xml");
Coach theCoach=context.getBean("tennisCoach",Coach.class)
theCoach.getDailyWorkOut();
}
}
/*********************Spring Application*******************************/
Now for Spring MVC-
/*****************Spring-MVC Application**************************/
web.xml
<web-app>
<servlet>
<servlet-name>HelloWeb</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigurationLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>HelloWeb</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
applicationContext.xml
<beans _______>
<context:component-scan base-package="packageName"/>
</beans>
Coach.java
public interface Coach{
public String getDailyWorkOut();
}
TennisCoach.java
#Component
public class TennisCoach implements Coach{
#RequestMapping("/")
public String getDailyWorkOut(){
return "practise back hand volley";
}
/*********************Spring-MVC Application*********************/
What I want to know is -
That in above given spring application I am retrieving bean from the container using context.getBean(), how is the Coach bean being retrieved in Spring-MVC application?

Yes, you can just create field with annotation #Autowired and spring inject it for you. Make sure your class where you are going to use this bean is also spring bean.

In you example you are retrieving bean via bean lookup from ApplicationContext.
Coach theCoach=context.getBean("tennisCoach",Coach.class)
In this case you know exact class name you need(such you are an author of your bean), and you simply get it from the context.
For DispatcherServlet it is not so easy, because it knows nothing about beans you've added to the context.
The only option it has is a full scanning of all defined in the context beans and detecting anything it can recognise (Controller, RestController, RequestMapping). Example of such detector is AbstractDetectingUrlHandlerMapping with it's implementations. SpringMvc has various implementations of such detectors, you can implement your own if you need.

Related

How to create a REST endpoint in Spring Boot Java without #SpringBootApplication, using only XML file?

I'm trying to implement a REST endpoint between two non-web applications with all configuration in XML files.
I've created a simple controller with a method that only returns "OK", so I can run some tests using Postman.
Unfortunately, the endpoint is not being created.
I did some research and I found out that I need to add the "context" tag with the component-scan pointing to the controller package for it to work.
But my current implementation is not enough for it to work:
<context:component-scan base-package="com.app.REST"/>
My controller class is:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class TestController {
#RequestMapping("/test")
#ResponseBody
public String test(){
return "OK";
}
}
My question is: is there any way to create a REST endpoint without annotating the main class with #SpringBootApplication? If yes, what am I missing? Is it something in my XML file or somewhere else?
For enabling the MVC functionality you need to instruct spring to scan for your controllers
this is done via the <mvc:annotation-driven /> tag.
Also as the DispatcherServlet is taking care of your requests you need to add the correct configuration for it in your web.xml
....
<servlet>
<servlet-name>my-dispatcher-servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:web-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher-servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
LE:
<mvc:annotation-driven> is similar to <context:component-scan> they register all beans annotated with #Controller, #Component, #Service, #Repository and #Bean...
The main difference with mvc:annotation-driven is that it also creates some extra beans which take care of the endpoint registration in the dispatcherServlet (the HandlerMapping, HandlerAdapters and some default conversionServices needed to represent your data received in your controllers for example)

Spring MVC (4.3.3) - RestController Get request parameter using direct field access

Say I have a RestController:
#RestController
#RequestMapping("path")
public class MyRestController {
#GetMapping("path")
public void myMethod(final MyObject object) throws Exception {
...
}
}
By default Spring uses getters and setters to set fields values for the object variable.
How can I specify to use direct field access?
I've tried with a custom Configuration class, but it doesn't work.
#Configuration
public class CustomWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
#Override
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
final ConfigurableWebBindingInitializer initializer = super.getConfigurableWebBindingInitializer();
initializer.setDirectFieldAccess(true);
return initializer;
}
}
When working on projects you don't know very well keep an eye on every XML file. When working with a mixture of XML and Java configurations something may not work as expected (especially if you haven't read the Spring documentation carefully).
Basically if you've defined an XML configuration like:
<?xml version="1.0" encoding="UTF-8"?>
<beans ... [skipped]>
<description>Spring XML configuration</description>
<mvc:annotation-driven />
<context:component-scan base-package="com.my.package" />
</beans>
And you try customizing the web configuration extending WebMvcConfigurationSupport:
#Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
#Override
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
final ConfigurableWebBindingInitializer initializer = super.getConfigurableWebBindingInitializer();
initializer.setDirectFieldAccess(true);
return initializer;
}
}
You're basically dealing with two different instances, one created by Spring using the XML description, and one created by WebMvcConfiguration.
I solved using only the Java configuration.
So, by coding your web.xml file this way, you can delete entirely the XML configuration. You can see I specified I want an Annotation configuration for the contextClass parameter, and my configuration class for the contextConfigLocation parameter
<servlet>
<servlet-name>SpringDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.my.package.WebMvcConfiguration</param-value>
</init-param>
</servlet>
Remember to add the #ComponentScan annotation to the Java class:
#ComponentScan("com.my.package")

#Value not working inside a class which extends other

Is it possible to use #Value inside a class that extends another class?
Below are the relevant code snippets. In the Lo_Controller class it works perfectly, but in the Lo_DisplayHandler always returns null. The only reason I can think of is because it depends on another class, which is not annotated with #Component. If that is the cause, what is the recommended option to read a value from properties file similar to #Value?
Just to test it out, I changed from #Component to #Controller in Lo_DisplayHandler to see, if they are somehow related to each other, however it returns null as well.
This works:
package com.ma.common.controller;
imports ...
#Controller
#RequestMapping("/log")
public class Lo_Controller {
#Value("${log.display.lastpage}")
private String lastPageUrl;
...
This always returns null:
package com.ma.log.handler;
imports ...
#Component
public class Lo_DisplayHandler extends Lo_Handler {
public Lo_DisplayHandler() {
super();
}
#Value("${log.display.lastpage}")
private String lastPageUrl;
...
mvc-dispatcher-servlet.xml
<context:component-scan base-package="com.ma.common.controller, com.ma.log.handler" />
<context:property-placeholder location="classpath:restServices.properties"/>
<mvc:annotation-driven />
<mvc:resources mapping="/**" location="/" />
#Component
public class Lo_DisplayHandler extends Lo_Handler {
#Value("${log.display.lastpage}")
private String lastPageUrl;
public void anyOtherMethod(){
String _a = lastPageUrl; //FAIL - always null
}
#PostConstruct
public void initIt() throws Exception {
String _a = lastPageUrl; //OK - when the application is deployed and started for the first time
}
#PreDestroy
public void cleanUp() throws Exception {
String _a = lastPageUrl; //OK - when the application is stopped
}
web.xml
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Three possible problems here:
First, I disencourage to extends classes as Spring works better autowiring dependencies. But it should work
Second, you have to focus on the lifecycle of a bean. The property will be set after the instanciation. #PostConstruct to validate the content.
Third, The visibility in hierarchical context of the property holder is not straight forward. So if you define #value in the root applicationContext, it will not set by your dispatcherServlet context. To test it, please inject the dependency of the bean defined at root level, you will see that your #Value will be take in account.
The lastPageUrl property will be accessible from a bean within the same context (by push creation or pull creation).
In case of push creation, if another bean autowire the Lo_DisplayHandler bean and call your method anyOtherMethod(), it will get the value.
#Component
public class ScannableIntoYourContext{
#Autowired
private Lo_DisplayHandler myHandler;
}
Other way is to pull the bean from ObjectFactory.
#Autowired
private ObjectFactory<Lo_DisplayHandler> bean;
Lo_DisplayHandler instanceFromContext = bean.getObject();

AnnotationConfigApplicationContext.getBean returns a different bean, Spring

I have a problem that I have a ClassA needs RoomService to be injected, and it works fine that I find in ClassA, the roomService's id is the same.
While for some reason, I need roomservice to create room instance based on some input param for me, so I use below config to achieve this:
#Configuration
#EnableAspectJAutoProxy
public class Application {
private static ApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);
public static ApplicationContext getApplicationContext(){
return ctx;
}
#Bean
public RoomService roomService(){
return new RoomService();//Singleton
}
#Bean
#Scope("prototype")
public AbstractRoom room(AbstractRoom.Mode roomMode){
RoomService roomService = (RoomService) ctx.getBean(RoomService.class);
LogUtil.debug("--------from application:" +roomService.id1);//here, I find the id is different every time
return roomService.newRoom(roomMode);
}
}
The problem is that I need RoomService to be singleton, but I find that in the Application.java , the ctx.getBean(roomService) always returns a different bean which has different id. Isn't Spring should reuse the same bean? Why is that?
Here is how I create a room in RoomService.java
public AbstractRoom createRoom(String userID,int playerCount,Mode roomMode){
ApplicationContext ctx =Application.getApplicationContext();
AbstractRoom room = (AbstractRoom)ctx.getBean(AbstractRoom.class,roomMode);
}
Update:
I tried reusing the same ctx and it does not work. One hint is that I find my constructor of RoomService() is called several times(I put a break point in it.) when I run tomcat to start it
Here is my web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>wodinow</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Please help!
Each time you retrieve your RoomService you are creating a new ApplicationContext instance. But singleton beans are only guaranteed to be singleton within a single instance of ApplicationContext.
So if you want the bean to be singleton you must use the same ApplicationContext instance each time you retrieve it.
From Spring documentation:
singleton: (Default) Scopes a single bean definition to a single
object instance per Spring IoC container.
Update 1
You can just call roomService() in your room() method to get the room service without creating application context and Spring will ensure that they are the same instance since it is marked as #Bean which has implicit singleton scope.
Update 2
Based on the updated question here are couple of issues with your code in general:
1. Do not create ApplicationContext in your configuration class. When you start your Spring application in Tomcat, application context is created for you by Spring. You just need to tell Spring which configuration classes it should register.
2. Remove the room() bean definition from your configuration class. Method for creating a room should be in RoomService. So for example if you needed to create a new room in Spring MVC controller you would inject the RoomService and call createRoom method on it. The injected service would be singleton. Example:
#Controller
#RequestMapping("/rooms")
public class RoomController {
#Autowired
private RoomService roomService;
#RequestMapping(value="/create", method=POST)
public String createRoom() {
roomService.createRoom(/* Parameters for room creation */);
return "redirect:/somelocation";
}
}
Try to rework your code based on these suggestions and it should work.

How to Add a Controller in Spring MVC 3?

I have created a new Spring MVC 3 project using NetBean. But there is no option of adding a new controller in the IDE.
Well adding a Controller is as simple as adding a class annotated with
#Controller
And specifying the package to be scanned from applicationContext.xml which in turn is specified in the web.xml. Something like this:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/appServlet/applicationContext.xml
</param-value>
</context-param>
in web.xml
Then in /WEB-INF/spring/appServlet/applicationContext.xml :
<context:component-scan base-package="your.package" />
Of course you need the actual schema in your applicationContext.xml
xmlns:context="http://www.springframework.org/schema/context"
And under schema location:
http://www.springframework.org/schema/context/spring-context-3.0.xsd
And then a class :
package your.package
.....
#Controller
MyController{
.....
If you are using an annotation driven implementation of Spring you don't need to do anything special. Create a standard Java class inside the package that Spring is configured to scan. Then annotate the class with #Controller then create your method(s) and mappings using #RequestMapping.
In its simplest form a controller would be something like:
#Controller
public class MyClass {
#RequestMapping("/myUrlMapping.do")
public ModelAndView myMethod() {
return new ModelAndView("myView");
}
}
This assumes you already have Spring configured correctly.

Categories