14

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

    2 Answers 2

    11
    @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.

    1
    • Is possibile to use this method to set content-type, encoding headers and so on of the controller?
      – Tobia
      Commented Dec 18, 2015 at 14:39
    5

    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");
        }
    
    }
    
    1

    Not the answer you're looking for? Browse other questions tagged or ask your own question.