Spring 3 MVC: #ModelAttribute method + #Autowired not working - java

I am new to Spring and trying to learn #Autowired magic in SPring MVC. I was trying out a demo application using the #ModelAttribute method and #Autowired. Every time I am getting null which means #Autowired is not happening properly. Below is what I tried:
Controller
#Controller
public class ModelAttributeAutoWiredController {
#Autowired
private Employee empl;
public void setEmpl(Employee empl) {
this.empl = empl;
}
#RequestMapping(value="/home")
public ModelAndView returnhome(){
ModelAndView modelView = new ModelAndView("home");
System.out.println("Employee First Name: " + empl.getFirstName()); // NULL
return modelView;
}
#RequestMapping(value="/index")
public ModelAndView returnindex(){
ModelAndView modelView = new ModelAndView("index");
System.out.println("Employee Last Name: " + empl.getLastName()); // NULL
return modelView;
}
#ModelAttribute("empl")
public Employee populateEmployee(){
Employee empl = new Employee();
empl.setFirstName("XXX");
empl.setLastName("YYY");
return empl;
}
}
Employee
#Component
public class Employee {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
context xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config />
<context:component-scan
base-package="com.pack" />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/pages/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
Can someone please help me out in the above code and make me understand why #Autowired is not working?

You're confusing several Spring concepts here.
First, #Component is used for program components, generally objects that provide some service that other pieces of the program need. It is not intended for data objects such as your Employee class, and data objects that are runtime data (and not configuration objects) shouldn't be autowired, they should be passed in to the specific method calls that operate on them.
#ModelAttribute tells Spring that it should add whatever is being annotated to the MVC Model object so that it's available to the controller and view. This has nothing to do at all with #Autowired.
Here's what's happening in your code:
Your Employee class is annotated #Component, so Spring creates a singleton bean and registers it in the context. This bean never has its fields set, so they're null, but the bean itself exists, so it's wired into your controller's empl field. This is why you don't get a NullPointerException, which you would if the autowiring really weren't working.
Your #ModelAttribute is evaluated by Spring and added to the Model for each request. However, you never pass this model to any of your controllers, so they never see it.
Your controller methods create new, empty ModelAndView objects with nothing in them.
They then read the completely different, empty Employee object that was injected into empl and print out the null value on the fields (but don't throw NullPointerExceptions because the autowiring succeeded).

Please check this: http://www.mkyong.com/spring-mvc/spring-3-mvc-and-xml-example/
You'll need the <mvc:annotation-driven/> and also the <context:component-scan base-package="com.your.package" /> in your context.xml file.
Looks like this isn't possible and if you think about it, it doesn't make much sense to have a #ModelAttribute as an instance variable. Please read here to get a better understanding.

#Autowired is not intended for the domain objects like Employee.
In your case Spring creates an Employee object using the default constructor, so its lastname is null. Spring creates the Employee object because of #Component annotation on the Employee class, it has nothing to do with the #ModelAttribute.
The method annotated with #ModelAttributes instructs Spring to create another Employee object and inject it to the Model object that you don't have to create by yourself.
Just return "index" String from your "returnhome" method.
For debugging, you may add #ModelAttribute Employee e as an argument to your "returnhome" method.

Related

How to define a bean in ApplicationContext.xml that has no constructor?

I have a class
public class DataStore {
public String name;
public String username;
public String password;
public String token;
public String connectionString;
public String type;
public String scheme;
public boolean usesBasicAuth;
public boolean usesBearerAuth;
}
I need to create an bean for it in another project. But i need to fill the fields somehow. The problem is I can not use <constructor-arg ... /> because there is no constructor.
The code below results in BeanCreationException: "Could not resolve matching constructor"
<bean id="dataStore"
class="com.fressnapf.sdk.dataaccess.services.DataStore">
<constructor-arg index="0" value="${spring.datastore.name}"/>
...
</bean>
Assuming, You have public (getters and setters for your) properties, and only the "default (no args) constructor", then You can change your configuration to:
<bean id="dataStore" class="com.fressnapf.sdk.dataaccess.services.DataStore">
<property name="connectionString" value="..."/>
<!-- ... -->
</bean>
Using property instead of constructor-arg.
Docs (Spring 4.2): https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/xsd-configuration.html
Yes, there is a constructor.
If you don't explicitly put one, Java will automatically add a default (non argument) constructor.
Use that one.
It will set all the instance variables to the default value of their type.
In your case: null for every String variable, false for every boolean.

Spring bean constructor-arg property resolution internally

I am using these classes:
Coach.java
public interface Coach {
void getDailyWorkout();
String getName();
String getExperience();
}
TrackCoach.java
public class TrackCoach implements Coach {
private String name;
private String experience;
public TrackCoach(String name, int num, String experience) {
this.name = name;
this.experience = experience;
}
public void getDailyWorkout() {
System.out.println("Run 5k on track");
}
public String getName() {
return name;
}
public String getExperience() {
return experience;
}
#Override
public String toString() {
return "TrackCoach{" +
"name='" + name + '\'' +
", experience='" + experience + '\'' +
'}';
}
}
appContext.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="coach4" class="com.prakhar.TrackCoach">
<constructor-arg value="7 years"/>
<constructor-arg name="name" value="Mr. Track #4"/>
<constructor-arg name="num" value="10"/>
</bean>
</beans>
When I am trying to get the coach4 bean, I am getting an error of UnsatisfiedDependencyException type. I am not able to understand how Spring resolves constructor arguments, the actual steps / flow. Its like a magic to me, sometimes it works, sometimes it doesn't. For example, if we remove second constructor-arg's name property, it will be working fine.
Could anyone please tell me how Spring resolves constructor arguments internally? I am using Spring 5.2.3.
In your case order matters:
<bean id="coach4" class="com.prakhar.TrackCoach">
<constructor-arg value="7 years"/>
<constructor-arg name="name" value="Mr. Track #4"/>
<constructor-arg name="num" value="10"/>
</bean>
public TrackCoach(String name, int num, String experience) {
So, the first constructor-arg is interpreted as name, because it's the first argument of constructor in TrackerCoach class.
Then you have second constructor-arg, which have name attribute, which has the value of name, because of this conflict happens. You have 2 args which match to name constructor arg and 0 which match to experience.
When you remove name attribute in XML, 7 years goas as name Mr. Track #4 goas as experience and 10 goas as num.
Hope this helps.
The constructor-arg element within the bean element is used to set the property value thru constructor injection.
In your case, your Object (TrackCoach) only has two properties and three constructor arguments. The reason it works when you remove the second name property is because the defaults become two which match the object properties and it then gets matched the two propeties of the TrackCoach object. There is no property called num, therefore the spring container will blow up with an Exception.
In a nutshell, your object properties have to match your constructor arguments.

How to bind subclass object in spring form submit as modelAttribute

I Have
Class Shape {
//Implementation
}
Class Round extends Shape{
//Implementation
}
Controller
I Have
#Requestmapping(value="/view/form")
public ModelAndView getForm(){
ModelAndView mav=new ModelAndView();
mav.addObject("shape",new Round());
}
#RequestMapping(value="/submit", method = RequestMethod.POST)
public ModelAndView submitForm(#ModelAttribute("shape") Shape shape){
if(shape instanceof Round){ //**Not giving positive result**
}
}
in Jsp
<form:form action="/submit" commandName="shape" method="post">
//form tags
</form:form>
when I submit the form with Round object. At controller side ModelAttribute is not giving instance of Round . its giving instance of shape only. How to do this
this will never work
<form:form action="/submit" commandName="shape" method="post">
you are submitting a shape from the form and expecting a shape
in the controller method
public ModelAndView submitForm(#ModelAttribute("shape") Shape shape)
which will never give you an Round object.
simply submit a Round object from the form and use that.
<form:form action="/submit" commandName="round" method="post">
public ModelAndView submitForm(#ModelAttribute("round") Round round)
edited :-
have a hiddenInput type in the form which will tell controller the type of Shape it is passing, you can change the value of hidden tag dynamically
upon user request.
<input type="hidden" name="type" value="round">
get the value of the type inside contoller and use it to cast the Shape object
public ModelAndView submitForm(
#ModelAttribute("shape") Shape shape,
#RequestParam("type") String type)
{
if(type.equals("round")){
//if type is a round you can cast the shape to a round
Round round = (Round)shape;
}
}
You cannot do this. Because those are two different request lifecycles.
#Requestmapping(value="/view/form")
public ModelAndView getForm(){
ModelAndView mav=new ModelAndView();
mav.addObject("shape",new Round());
}
When above request executes, even if you added Round object in mav, it has been converted into html response and sent back to client.
So when below request comes next when you submit the form, it's a totally separate request and spring has no way to identify which object type was added for previous request.
#RequestMapping(value="/submit", method = RequestMethod.POST)
public ModelAndView submitForm(#ModelAttribute("shape") Shape shape){
if(shape instanceof Round){ //**Not giving positive result**
}
}
But you can try exploring #SessionAttributes, using this you might be able to maintain the same object across different requests
Its possible to specify the subclass that you need to bind to. You have to add an additional parameter (hidden input) in your form which specify the type that needs to be bound to. This field must have the same name as the model attribute in this case shape. You then need to implement a converter that converts this string parameter value to the actual instance that you need to bind to
The following are the changes that you need to implement
1)In your jsp add the hidden input inside your
<form:form action="/submit" commandName="shape" method="post">
<input type="hidden" name="shape" value="round"/>
//other form tags
</form:form>
2)Implement a Converter to convert from String to a Shape
public class StringToShapeConverter implements Converter<String,Shape>{
public Shape convert(String source){
if("round".equalsIgnoreCase(source)){
return new Round();
}
//other shapes
}
}
3) Then register your converter so that Spring MVC knows about it. If you are using Java config you need to extends WebMvcConfigurerAdapter and override the addFormatters method
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter{
#Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new StringToShapeConverter());
}
}
If you are using xml configuration you can use the mvc:annotation-driven element to specify the conversion-service to use. Then register your converter using the FormattingConversionSErviceFactoryBean
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="some.package.StringToShapeConverter"/>
</set>
</property>
</bean>
</beans>

Use the session object UserBean across dao methods

I get an Object say UserBean something like this from the session
public class UserBean {
private String username;
private String userid;
private String userType;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
}
Now i want to use this bean in many of my dao methods. The problem i face is i have to modify my dao parameters to include this bean info which i think will not be a good design.
How can i include UserBean Details without modifying my DAO methods ?
I am using spring MVC 3
I think it is not a good idea to access web context classes in your DAO, this will introduce a tight coupling among the layers and changing something in the presentation layer might effect your DAO layer objects. You can pass this information to your DAO classes as a method parameter instead.
But if you really want to get access to session attributes in your DAO, you can use RequestContextHolder for this:
ServletRequestAttributes request = (ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes();
HttpSession session = request.getRequest().getSession();
UserBean UserBean = (UserBean)session.getAttribute("userBean");
As you are referring to request attributes outside of an actual web request, you need to add RequestContextListener to your web.xml to expose the current request:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
DAO layer should not be aware of the web session.
Implement session related logic in 'upper' layer, in manager or service.
In your 'refactoring' case it would have an added benefit of acting as an adapter between Spring MVC layer and DAO, beside being good design.
You can create an instance variable in your DAO class and one setter and getter for that.
But before calling any DAO method you have to set userBean in that DAO which is again not good practice but may be one of the solution.
You could define UserBean as a Spring bean and use Spring's session scope. E.g.:
<bean id="user" class="com.foo.UserBean" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userDao" class="com.foo.UserDao">
<property name="user" ref="user"/>
</bean>
For documentation, see 3.5.4.5 Scoped beans as dependencies in the Spring documentation.

Is it possible to have destination view name configurable in spring mvc 3?

Code snippet is like this:
#Controller
#RequestMapping(value="/test")
public class TestController {
........
#RequestMapping(method=RequestMethod.GET)
public String getCreateForm(Model model) {
model.addAttribute(new AccountBean());
return "newtest";
}
.........
"newtest" is the hard-coded view name. Is it possible to have it configured in an XML-style Spring config file? Thank you!
I guess the real question is how to configure properties of autodiscovered bean via XML.
You can do it by defining a <bean> with the same name as the autodiscovered one have (when the name of autodiscovered bean is not specified, it's assumed to be a classname with the first letter decapitalized):
#Controller
#RequestMapping(value="/test")
public class TestController {
private String viewName = "newtest";
public void setViewName(String viewName) {
this.viewName = viewName;
}
#RequestMapping(method=RequestMethod.GET)
public String getCreateForm(Model model) {
model.addAttribute(new AccountBean());
return viewName;
}
}
.
<bean id = "testController" class = "TestController">
<property name = "viewName" value = "oldtest" />
</bean>
Another option is to use #Value with SpEL expressions
#Value("#{testViewName}") private String viewName;
.
<bean id = "testViewName" class = "java.lang.String">
<constructor-arg value = "oldtest" />
</bean>
or property placeholders
#Value("${testViewName}") private String viewName;
.
<context:property-placeholder location = "viewnames" />
viewnames.properties:
testViewName=oldtest
Well, it is possible to return any string there. So yes - it can be configured.
Update: there are many ways to configure it, one of which (and my preference) being a combination of PropertyPlaceholderConfigurer and the #Value annotation, but that was already covered by axtavt.

Categories

Resources