#ModelAttribute("classname") dynamic paramater binding - spring-mvc

spring mvc
#ModelAttribute("classname"),
How to make the argument "classname" a dynamic one ?
Whatever comes from view can get appended there.

Instantiation of the command object is the only place where Spring needs to know a command class. However, you can override it with #ModelAttribute annotated method:
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request,
#ModelAttribute("objectToShow") Object objectToShow)
{
...
}
#ModelAttribute("objectToShow")
public Object createCommandObject() {
return getCommandClass().newInstance();
}
By the way, Spring also works fine with the real generics:
public abstract class GenericController<T> {
#RequestMapping("/edit")
public ModelAndView edit(#ModelAttribute("t") T t) { ... }
}
#Controller #RequestMapping("/foo")
public class FooController extends GenericController<Foo> { ... }

Related

Custom Spring annotation for request parameters - custom resolver never invoked

I've implemented custom annotation in my project, but implemented method argument resolver was never invoked.
Can someone help me with this issue?
My implementation:
Annotation
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface MyAnnotation {
String value();
}
Resolver
public class MyAnnotationResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
System.out.println("supportsParameter invoked!");
return parameter.getParameterAnnotation(MyAnnotation.class) != null;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
...
return "someString";
}
}
Configuaration
#Configuration
#EnableWebMvc
public class Config extends WebMvcConfigurerAdapter {
...
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new MyAnnotationResolver());
System.out.println("Resolver added!");
}
...
}
Controller
#Controller
#RequestMapping("/project")
public class ProjectController{
#RequestMapping(method = RequestMethod.GET, value= "/{hashCode}")
#ResponseBody
public String index(#MyAnnotation("hashCode") String hashCode, Model model) {
...
System.out.println(hashCode == null ? "HashCode=null" : "HashCode=" + hashCode);
}
As output I get:
Resolver added!
HashCode=null
Why supportsParameter(...) was never invoked?
It is not reproducible. supportsParameter gets called with the given code.
Since 3 years have passed after you asked, I checked the code that was used back then when you posted this question. However, the relevant code didn't change that much.
One possible explanation is that there was an argument resolver that supported the parameter, since it doesn't check another when it finds one.

Why do we need RedirectAttributes.addAttribute()?

Spring provides the following option to pass attributes(params) to the redirect page by using RedirectAttributes.
#Controller
#RequestMapping("/")
public class RedirectController {
#GetMapping("/redirectWithRedirectView")
public ModelAndView redirectWithUsingRedirectView(RedirectAttributes attributes) {
attributes.addAttribute("attribute", "redirectWithRedirectView");
return new ModelAndView("redirect:redirectedUrl");
}
}
The same can be achieved even if we simply append attribute to redirect URL, like the following
#Controller
#RequestMapping("/")
public class RedirectController {
#GetMapping("/redirectWithRedirectView")
public ModelAndView redirectWithUsingRedirectView() {
return new ModelAndView("redirect:redirectedUrl?attribute=redirectWithRedirectView");
}
}
Both do the same job. Do we get any benefits in terms of memory if we use RedirectAttributes? I am guessing, in the second case, we would be constructing separate ModelAndView object if the attribute value changes for each request.

Spring MVC - PropertyEditor not called during ModelAttribute type conversion

Using Spring 3.2.3, I'm trying to implement a simple CRUD controller that handles REST-ful URLs. It relies on a PropertyEditor to convert a path variable to a BusinessService entity by loading it from an application service. Code is as follows:
#Controller
public class BusinessServiceController {
#Autowired
private BusinessServiceService businessSvcService;
public BusinessServiceController() {
}
#InitBinder
public void initBinder(final WebDataBinder binder) {
binder.registerCustomEditor(BusinessService.class, new BusinessServicePropertyEditor(businessSvcService));
}
#RequestMapping(value = "/ui/account/business-services/{businessSvc}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView update(#ModelAttribute("businessSvc") #Valid final BusinessService businessSvc, final BindingResult result,
final RedirectAttributes redirectAttribs) throws UnknownBusinessServiceException {
ModelAndView mav;
if (result.hasErrors()) {
mav = new ModelAndView("/business-service/edit");
}
else {
businessSvcService.updateBusinessService(XSecurity.principal().getId(), businessSvc);
mav = new ModelAndView("redirect:/ui/account/business-services");
redirectAttribs.addFlashAttribute("message", Message.info("businessService.updated", businessSvc.getTitle()));
}
return mav;
}
}
public class BusinessServicePropertyEditor extends PropertyEditorSupport {
private final BusinessServiceService businessSvcService;
public BusinessServicePropertyEditor(final BusinessServiceService businessSvcService) {
this.businessSvcService = businessSvcService;
}
#Override
public String getAsText() {
final BusinessService svc = (BusinessService) getValue();
return Long.toString(svc.getId());
}
#Override
public void setAsText(final String text) {
final BusinessService svc = businessSvcService.getBusinessService(Long.parseLong(text));
setValue(svc);
}
}
According to SPR-7608, starting from Spring 3.2, #ModelAttribute method argument resolution checks if a path variable by the same name exists (it does here), in which case it tries to convert that path variable's value to the target parameter type through registered Converters and PropertyEditors. This is not what I'm experiencing. When I inspect what ServletModelAttributeMethodProcessor does, it clearly uses the request DataBinder's ConversionService to perform type conversion, which does not consider registered PropertyEditors, and hence BusinessServicePropertyEditor#setAsText is never called.
Is this a configuration problem or an actual bug?
Thanks for your help!
Spring's ConversionService and Converters are replacement for standard Java Beans PropertyEditors.
You need to implement Converter instead of PropertyEditor if this feature is based purely on conversion service.
To register your custom converters in WebDataBinder you might use ConfigurableWebBindingInitializer or #InitBinder method.

Spring MVC jackson auto serialize?

I would like to serialize an object with jackson in spring MVC.
I have a controller which returns an ObjectTest1 which has a property ObjectTest2.
public class ObjectTest1{
private ObjectTest2;
// setters getters...
}
public class ObjectTest2{
private String value;
// setters getters...
}
public #ResponseBody ObjectTest1 test() throws IOException ...
I have a mapper and I have a serializer for ObjectTest2 and I've annotated the ObjectTest1.getObjectTest2 method with #JsonSerialize(using = ObjectTest2.class).
It works correctly!
But I want to use this serializer in a lot of Object, not just in ObjectTest1.
What should I do to avoid put annotation every getter method? Can use spring this serializer automatically for all properites which is ObjectTest2?
UPDATED:
I've already use this in my code:
<mvc:annotation-driven>
In ajax response Objects generated correctly as json.
Maybe I should try to explain another way.
So.
I have these objects:
public class DTO{
private InnerThing innerThing;
#JsonSerialize(using=ThingSerializer.class)
public InnerThing getThing(){...}
}
public class InnerThing{
private String value;
}
Generated json looks like:
{"innerThing":{"value":"something"}}
Afther when I've written a serializer, json is:
{"innerThing":"something"}
It is OK, but to get the second version of json I must annotate the getInnerThing method in DTO class with #JsonSerialize...
I don't want to annotate all methods where I use InnerThing as a property.
So my question is, can spring auto serialize every property which type is InnerThing?
By default, Spring will handle serialization and de-serialization of JSON automatically if you add Jackson to the classpath and you use either <mvc:annotation-driven> or #EnableWebMvc.
Links to the Spring Reference Docs:
Spring 3.0: <mvc:annotation-driven>
Spring 3.1: <mvc:annotation-driven> and #EnableWebMvc
You want Jackson to always use your custom JsonSerializer or JsonDeserializer to serialize/deserialize a specific type?
I ended up writing a custom Jackson module to let Jackson find serializers and deserializers that are Spring beans.
I am using Spring 3.1.2 and Jackson 2.0.6
Simplified version:
public class MyObjectMapper extends ObjectMapper {
#Autowired
public MyObjectMapper(ApplicationContext applicationContext) {
SpringComponentModule sm = new SpringComponentModule(applicationContext);
registerModule(sm);
}
}
Module:
public class SpringComponentModule extends Module {
private ApplicationContext applicationContext;
public SpringComponentModule(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override public String getModuleName() {
return "jackson-spring-component";
}
#Override public Version version() {
return SpringComponentModuleVersion.instance.version();
}
#Override
public void setupModule(SetupContext context) {
context.addSerializers(new SpringComponentSerializers(this.applicationContext));
context.addDeserializers(new SpringComponentDeserializers(this.applicationContext));
}
}
ComponentSerializer class:
public class SpringComponentSerializers extends Serializers.Base {
private ApplicationContext applicationContext;
public SpringComponentSerializers(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public JsonSerializer<?> findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) {
Class<?> raw = type.getRawClass();
Map<String,JsonSerializer> beanSet = applicationContext.getBeansOfType(JsonSerializer.class);
for(String beanName : beanSet.keySet()) {
JsonSerializer<?> serializer = beanSet.get(beanName);
if(serializer.handledType().isAssignableFrom(raw)) {
return serializer;
}
}
return null;
}
}

Abstract Factory Method pattern not working with Spring MVC

I have problem with Abstract Factory pattern implementation on Spring. I am using Spring 3 MVC and Hibernate 3, which works fine if I don't use Abstract Factory Method pattern.
I am not sure what I have to add to the Controller to have access the Factory Class (CategoryFactory).
Is there anything missing in the Controller or bean initiating ?
class SectionsController extends MultiActionController {
/* Do I have to initiate the CategoryFactory here? */
public ModelAndView secList() throws Exception {
CategoryFactory.CategoryType type = CategoryFactory.CategoryType.valueOf("view");
modelMap.addAttribute("sectionList", CategoryFactory.findCategory(type).list(id));
return new ModelAndView("Form", modelMap);
}
}
Abstract Factory
public abstract class Category {
public abstract List list(int departId);
}
public class CategoryFactory {
public enum CategoryType { firstclass, secondClass, ... }
public static Category findCategory(CategoryType categoryType) {
// Create type specific Category implementation
}
}
It shoud be:
class SectionsController extends MultiActionController {
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
public ModelAndView secList() throws Exception {
CategoryFactory.CategoryType type=CategoryFactory.CategoryType.valueOf("view");
modelMap.addAttribute("sectionList",CategoryFactory.findCategory(type).list(hibernateTemplate,id);
return new ModelAndView("Form", modelMap);
}
}

Resources