In my Header HTML I display a UL/LI Menu where the visiblity of one of the LI items depends on a Service method call.
I tried this:
HomeController
#Controller
public class HomeController {
private static final Logger log = LogManager.getLogger(HomeController.class);
#Autowired
private EtdService etdService;
#GetMapping("/home")
public String home(Model model) throws EtdException {
model.addAttribute("tierTemplate", etdService.getTierTemplate());
// Also tried this explicitly
model.addAttribute("etdService", etdService);
return "home";
}
}
Service Interface (EtdService)
public interface EtdService {
boolean isChangeUserAllowed();
}
Service Implementation (EtdServiceImpl)
#Component
public class EtdServiceImpl implements EtdService {
#Override
public boolean isChangeUserAllowed() {
System.out.println("Got here");
return false;
}
}
HTML:
<li th:if="${#etdService.isChangeUserAllowed()}" class="nav-item dropdown" id="changeUserPanel" role="presentation">
<!-- ... Definition of this LI -- note can't put a new DIV in a UL list ... -->
</li>
Error:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'etdService' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:772) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1221) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
In addition to the answer by bphilipnyc (set the direct value into the model),
model.addAttribute("isChangeUserAllowed", etdService.isChangeUserAllowed());
If you need to globalize common Model Attributes without re-adding every time, a solution is a #ControllerAdvice class with a #ModelAttribute, e.g.
/**
* This class is used to globalize common Model Attributes without re-adding every time
* The approach is to mark it as #ControllerAdvice to make it apply to every Controller method,
* and implement a #ModelAttribute Model-Adder to append to the model on every Controller method.
*/
// Makes the methods of this class apply to all Controller Request methods
#ControllerAdvice
public class GlobalController {
#Autowired
MyService myService;
#ModelAttribute // A Model Attribute Adder method
public void setGlobalModelAttributes(HttpServletRequest request, Model model) {
model.addAttribute("isChangeUserAllowed", myService.isChangeUserAllowed());
model.addAttribute("currentUserFullName", myService.getCurrentUserFullName());
}
}
Some more examples
https://stackoverflow.com/a/33879102/1005607
https://www.baeldung.com/spring-mvc-and-the-modelattribute-annotation
You are referencing an instance method in Thymeleaf. Here are two options:
1) Reference it by adding the value of the boolean to the model:
#GetMapping("/home")
public String home(Model model) throws EtdException {
//...
model.addAttribute("isChangeUserAllowed", etdService.isChangeUserAllowed());
return "home";
}
And in your HTML: th:if="${isChangeUserAllowed}"
To avoid NPEs, you can alternatively use #bools.isTrue(isChangeUserAllowed) or the appropriate method in the bools utility.
This is the preferred way and the path that the Thymeleaf documentation takes. A clear benefit is that the front-end is now not tied to the service.
2) Reference it statically instead (not recommended):
Error trying call method from view Thymeleaf Spring
Aside: the recommended way is to use constructor injection instead of autowiring.
Related
I am working on a Spring MVC application and I have the following problem.
I have this RegistrazioneInfo class that contains some information inserted into a form by the user:
public class RegistrazioneInfo {
#NotNull
#Size(min=16, max=16)
private String codiceFiscale;
String gRecaptchaResponse;
public String getCodiceFiscale() {
return codiceFiscale;
}
public void setCodiceFiscale(String codiceFiscale) {
this.codiceFiscale = codiceFiscale;
}
public String getgRecaptchaResponse() {
return gRecaptchaResponse;
}
public void setgRecaptchaResponse(String gRecaptchaResponse) {
this.gRecaptchaResponse = gRecaptchaResponse;
}
}
Then I have this controller class:
#Controller
public class RegistrazioneController extends BaseController {
private RegistrazioneInfo registrazioneInfo;
...............................................
...............................................
...............................................
}
that contains some methods handling request towards some resources.
Ok, my problem is that I want to use an instance of the previous RegistrazioneInfo class as session attribute by the use of the #SessionAttributes Spring annotation as shown here: http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-ann-sessionattrib
My problem is, in the previous example do something like this:
#SessionAttributes("pet")
public class EditPetForm {
// ...
}
So what exactly is pet? I think that it is something like an id that identify the object that have to be used as a session attribute or something like this. How can I say to put an instance of my RegistrazioneInfo as session attribute?
#SessionAttributes is declared in a Controller Class (#Controller), so on the class level.
Pet is an Bean Object that persist in HttpSession
From the documentation:
This will typically list the names of model attributes which should be transparently stored in the session or some conversational storage, serving as form-backing beans. Declared at the type level, applying to the model attributes that the annotated handler class operates on.
(emphasis is mine)
Also note that, as indicated in the documentation, you should not use that for "non temporary" elements.
I am pretty new in Spring MVC and I have the following problem.
Into an home() controller method defined into a class named **HomeController I retrieve an object using a service. This object have to be put as Session Attribute so it can be used from other methods in other controller classes.
So I have done in this way:
#Controller
#SessionAttributes({"progettoSelezionato"})
#PropertySource("classpath:messages.properties")
public class HomeController {
private static final Logger logger = Logger.getLogger(LoginController.class);
private #Autowired HomeService homeService;
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(Model model) {
List<Tid023Intervento> listaInterventi = homeService.getListaInterventiRUP(18);
model.addAttribute("progettoSelezionato", listaInterventi.get(0));
model.addAttribute("listaProgettiRUP", listaInterventi);//TODO
return "home";
}
}
As you can see I have used this annotation on the class level:
#SessionAttributes({"progettoSelezionato"})
and then I put this object by:
model.addAttribute("progettoSelezionato", listaInterventi.get(0));
I am not sure that this is correct because, from what I know, this put the retrieved listaInterventi.get(0) object into the model with the voice progettoSelezionato. So I am absolutly not sure that it is putted as SessionAttributes.
Then, after that this object is putted as SessionAttriibute I have to retrieve and use if from a gestioneDatiContabiliEnte() method defined into another controller class, so I am doing in this way:
#Controller
#SessionAttributes({"progettoSelezionato"})
#PropertySource("classpath:messages.properties")
public class GestioneDatiContabiliEnteController {
private static final Logger logger = Logger.getLogger(LoginController.class);
#RequestMapping(value = "/gestioneDatiContabiliEnte", method = RequestMethod.GET)
public String gestioneDatiContabiliEnte(Model model) {
System.out.println("INTO gestioneDatiContabiliEnte()");
System.out.println("PROGETTO SELEZIONATO: " + progettoSelezionato);
return "gestioneDatiContabiliEnte/gestioneDatiContabiliEnte";
}
}
But it seems can't work because Eclipse sign me error on this line:
System.out.println("PROGETTO SELEZIONATO: " + progettoSelezionato);
The error is: progettoSelezionato cannot be resolved to a variable.
How can I correctly put the listaInterventi.get(0) as SessionAttribute into my HomeController? And how can I retrieve and use it into the gestioneDatiContabiliEnte() method defined into my GestioneDatiContabiliEnteController class?
Soled by myself, I can access to this object by:
model.asMap().get("progettoSelezionato");
I have many controllers in my Spring MVC web application and there is a param mandatoryParam let's say which has to be present in all the requests to the web application.
Now I want to make that param-value available to all the methods in my web-layer and service-layer. How can I handle this scenario effectively?
Currently I am handling it in this way:
... controllerMethod(#RequestParam String mandatoryParam, ...)
and, then passing this param to service layer by calling it's method
#ControllerAdvice("net.myproject.mypackage")
public class MyControllerAdvice {
#ModelAttribute
public void myMethod(#RequestParam String mandatoryParam) {
// Use your mandatoryParam
}
}
myMethod() will be called for every request to any controller in the net.myproject.mypackage package. (Before Spring 4.0, you could not define a package. #ControllerAdvice applied to all controllers).
See the Spring Reference for more details on #ModelAttribute methods.
Thanks Alexey for leading the way.
His solution is:
Add a #ControllerAdvice triggering for all controllers, or selected ones
This #ControllerAdvice has a #PathVariable (for a "/path/{variable}" URL) or a #RequestParam (for a "?variable=..." in URL) to get the ID from the request (worth mentioning both annotations to avoid blind-"copy/past bug", true story ;-) )
This #ControllerAdvice then populates a model attribute with the data fetched from database (for instance)
The controllers with uses #ModelAttribute as method parameters to retrieve the data from the current request's model
I'd like to add a warning and a more complete example:
Warning: see JavaDoc for ModelAttribute.name() if no name is provided to the #ModelAttribute annotation (better to not clutter the code):
The default model attribute name is inferred from the declared
attribute type (i.e. the method parameter type or method return type),
based on the non-qualified class name:
e.g. "orderAddress" for class "mypackage.OrderAddress",
or "orderAddressList" for "List<mypackage.OrderAddress>".
The complete example:
#ControllerAdvice
public class ParentInjector {
#ModelAttribute
public void injectParent(#PathVariable long parentId, Model model) {
model.addAttribute("parentDTO", new ParentDTO(parentId, "A faked parent"));
}
}
#RestController
#RequestMapping("/api/parents/{parentId:[0-9]+}/childs")
public class ChildResource {
#GetMapping("/{childId:[0-9]+}")
public ChildDTO getOne(#ModelAttribute ParentDTO parent, long childId) {
return new ChildDTO(parent, childId, "A faked child");
}
}
To continue about the warning, requests are declaring the parameter "#ModelAttribute ParentDTO parent": the name of the model attribute is not the variable name ("parent"), nor the original "parentId", but the classname with first letter lowerified: "parentDTO", so we have to be careful to use model.addAttribute("parentDTO"...)
Edit: a simpler, less-error-prone, and more complete example:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
public #interface ProjectDependantRestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
*
* #return the suggested component name, if any
*/
String value() default "";
}
#ControllerAdvice(annotations = ParentDependantRestController.class)
public class ParentInjector {
#ModelAttribute
public ParentDTO injectParent(#PathVariable long parentId) {
return new ParentDTO(parentId, "A faked parent");
}
}
#ParentDependantRestController
#RequestMapping("/api/parents/{parentId:[0-9]+}/childs")
public class ChildResource {
#GetMapping("/{childId:[0-9]+}")
public ChildDTO getOne(#ModelAttribute ParentDTO parent, long childId) {
return new ChildDTO(parent, childId, "A faked child");
}
}
I have UsersController with method:
#RequestMapping(value={"/new"}, method=RequestMethod.GET)
public String showCreationForm(#ModelAttribute User user){
return "user_registration_form";
}
which displays registration form. I want to keep modularity (would be nice to use this controller in some other project) in my project so User is an interface and there is its implementation - UserImpl. The problem is that Spring cannot instatiate User interface. Is there a way to configure spring to use some default implementation of User?
You can provide an object to be populated with request data using #ModelAttribute-annotated method:
#ModelAttribute
public User createUser() {
return new UserImpl();
}
Create a simple class that implements the interface minimally. It is the same idea as an interface, but it is a class. It does not contain any of your logic or validation or anything else. It is just the simplest implementation of the interface, call it UserSimple, and it implements your interface. It is called a Data Transfer Object.
public class UserSimple implements User {
String name;
String address;
//getters and setters only
}
Add a converter that copies the real properties of the UserImpl into the UserSimple.
#Component
public class ImplToSimpleConverter
implements Converter<UserImpl, UserSimple> {
#Override
public UserSimple convert(UserImpl source) {
UserSimple target = new UserSimple();
BeanUtils.copyProperties(source, target);
return target;
}
}
Use UserSimple in the handler.
#RequestMapping(value={"/new"}, method=RequestMethod.GET)
public String showCreationForm(#ModelAttribute UserSimple user){
return "user_registration_form";
}
This allows you to keep the code generic. Adding a different converter is all you would have to do to use the same class in a different application.
I am trying to understand architecture of Spring MVC. However, I am completely confused by behavior of #SessionAttributes.
Please look at SampleController below , it is handling post method by SuperForm class. In fact, just field of SuperForm class is only binding as I expected.
However, After I put #SessionAttributes in Controller, handling method is binding as SubAForm. Can anybody explain me what happened in this binding.
-------------------------------------------------------
#Controller
#SessionAttributes("form")
#RequestMapping(value = "/sample")
public class SampleController {
#RequestMapping(method = RequestMethod.GET)
public String getCreateForm(Model model) {
model.addAttribute("form", new SubAForm());
return "sample/input";
}
#RequestMapping(method = RequestMethod.POST)
public String register(#ModelAttribute("form") SuperForm form, Model model) {
return "sample/input";
}
}
-------------------------------------------------------
public class SuperForm {
private Long superId;
public Long getSuperId() {
return superId;
}
public void setSuperId(Long superId) {
this.superId = superId;
}
}
-------------------------------------------------------
public class SubAForm extends SuperForm {
private Long subAId;
public Long getSubAId() {
return subAId;
}
public void setSubAId(Long subAId) {
this.subAId = subAId;
}
}
-------------------------------------------------------
<form:form modelAttribute="form" method="post">
<fieldset>
<legend>SUPER FIELD</legend>
<p>
SUPER ID:<form:input path="superId" />
</p>
</fieldset>
<fieldset>
<legend>SUB A FIELD</legend>
<p>
SUB A ID:<form:input path="subAId" />
</p>
</fieldset>
<p>
<input type="submit" value="register" />
</p>
</form:form>
When processing POST request, Spring does the following:
Without #SessionAttributes: Spring instantiates a new instance of SuperForm (type is inferred from the signature of register()), populates its properties by values from the form fields and passes it to the register() method.
With #SessionAttributes: Spring obtains an instance of model attribute from the session (where it was placed when processing GET due to presence of #SessionAttributes), updates its properties by values from the from fields and passes it to the register() method.
That is, with #SessionAttributes , register() gets the same instance of the model attribute object that was placed into the Model by getCreateForm().
Adding on to what #axtavt said: Suppose, in getCreateForm you are putting some values for a drop-down (list or map), or you are putting some values in form that you want in register method but you don't want them to show in form (not even in hidden fields). Now suppose that an error occurred in register method and you need to show the form again. To populate drop down values and other values that you would need in next post, you would have to repopulate them in form. The #SessionAttribute helps here as #axtavt very well described above.
#Controller
#SessionAttributes("test")
public class Controller{
Customer customer;
public Controller() {
super();
customer = new Customer();
}
#ModelAttribute("test")
public Customer getCustomer() {
customer.setName("Savac");
return customer;
}
#RequestMapping({"/index"})
public ModelAndView showMainPage (#ModelAttribute("test") Customer customer, ModelMap model, method = RequestMethod.GET) {
//in the view you set the name
return new ModelAndView("index");
}
#RequestMapping(value = "customer/{customerID}", method = RequestMethod.GET)
public ModelAndView viewAdvice(#PathVariable("customerID") int customerID, #ModelAttribute("test") Customer customer, ModelMap model) {
customer.setName("AnotherName");
model.addAttribute("test", customer);
return new ModelAndView("customer");
}
}
According to Spring reference documentation #ModelAttribute annotated method argument is resolved as follows:
Retrieve from model object if it is present (normally added via #ModelAttribute annotated methods)
Retrieve from HTTP session by using #SessionAttributes.
Create using URI path variable that matches the #ModelAttribute name through a converter
Create using default constructor and add it to Model.
A handler class can be annotated with #SessionAttributes with a list of names as its arguments. This is to instruct Spring to persist (in session) those data items present in the model data which match the names specified in #SessionAttributes annotation.
Thus in the SampleController, the post method's #ModelAttribute argument is resolved with #SessionAttributes field due to the resolution method mentioned above.