Announcement Announcement Module
Collapse
No announcement yet.
Common Model Objects Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Common Model Objects

    Its been a long time since I have worked with Spring's MVC layer, or with IoC in general, but this seems like a common problem.

    There are some templating utilities as well as some common configuration that I always want stuffed in the ModelAndView. Is there any easy way to wire this with Spring?

    I have been attempting to just extend ModelAndView, however, there are many final methods in the Spring framework that cannot be overridden.

  • #2
    One method that we have used with Velocity is to have a 'defaultModel' to return with the ModelAndView.
    Code:
    Map model = getDefaultModel();
    ...
    public Map getDefaultModel ( ) {
      Map model = new HashMap();
      model.put( "dateTool", new DateTool() );
      model.put( "esc", new EscapeTool() );
      model.put( "number", new NumberTool() );
      return model;
    }
    You should be able to do this with other template technologies too.

    How that helps,

    Steve O

    Comment


    • #3
      Steveo,

      Thanks for the response. The same thing occured to me, but if a developer wants to consctruct a different ModelAndView and then get to the template only to realize they are missing objects?

      I am looking for something transparent to the caller.

      Comment


      • #4
        Hi Johnhup,

        If you are using the new 2.5 styled controllers then there are quite a few ways to have common reference data added to the model using the @ModelAttribute annotation. If you are using the 2.0 controllers and the ModelAndView paradigm then there are still some good options available, but they tend to be less flexible

        First off, you could create a bean which you can inject into your controllers which includes a 'getCommonModelAndView' method. This is simple and easy and flexible as all controller types can use it, 2.0 and 2.5.

        Another option is to extend a commonly used class, say SimpleFormController, and override referenceData or add a new method (like above), but this is not flexible as it will only apply to one controller type.

        Another way is to create an interceptor which implements postHandle and adds the common reference data to the ModelAndView. This is nice as it is separate from the controllers and works only after processing has been accomplished. Although, its important to note that it will not fire if an exception occurs during handling as the postHandle processing is skipped.

        If you are using the 2.5 styled controllers you can create a super class which has a @ModelAttribute method which all controllers inherit, eg.
        Code:
        @ModelAttribute
        public void commonModelData(ModelMap modelMap){
            modelMap.addAttribute("stuff", "stuff");
        }
        Be warned, if you add common model data and then redirect, the model data will be appended to the query string, I recommend using the interceptor but checking what the view is. If you use a handler exception resolver then you might have to create a hook for it to use the interceptor as well.

        I hope this helps (and makes sense)

        Josh

        Comment


        • #5
          Originally posted by joshk View Post
          Another way is to create an interceptor which implements postHandle and adds the common reference data to the ModelAndView. This is nice as it is separate from the controllers and works only after processing has been accomplished. Although, its important to note that it will not fire if an exception occurs during handling as the postHandle processing is skipped.
          Thanks Josh, that does make sense, especially using the WebRequestInterceptor.postHandle. I would like to do this route as it is transparent to the caller, however, how do I get beans that were created using Spring's container into a super class that all my controllers would extend? My main problem is, I dont want to have to wire up all of this shared functionality into every controller.

          I have been working with programatic frameworks like Pico for a while now so its hard to get my head around this again. I gotta say I really miss pico's auto-wiring constructor injection.

          Comment


          • #6
            Hi John,

            Spring has auto-wiring as well, you just have to tell the bean definition that you want to use it.

            Sorry, can I just confirm, you want to use an interceptor ? if so, why do you need to create a controller super class ?

            Josh

            Comment


            • #7
              I have read that Spring also has autowiring but sometimes there are ambiguous names due the 'targets' as the other engineer has called them which are proxy classes.

              Now I see what you mean regarding the Post Handlers. Let me see if I get you correctly. If I create a class that implements any of those handlers then Spring will call them before rendering the request?

              Comment


              • #8
                Hi John,

                I need to make an amendment to my earlier post, do not use the WebRequestInterceptor as its postHandle method does not allow you to check the view because only the ModelMap is available for use. Instead use the HandlerInterceptor interface and implement the postHandle method. Check if the view exists and if it isn't of type RedirectView and the viewname doesn't start with "redirect:", add the reference data. eg.

                (sorry if its a bit messy)

                Code:
                public void postHandle(HttpServletRequest request,
                			HttpServletResponse response, Object handler,
                			ModelAndView modelAndView) throws Exception { 
                		
                	boolean isRedirectView = modelAndView.getView() instanceof RedirectView;
                	boolean isViewObject = modelAndView.getView() == null;
                        // if the view name is null then set a default value of true
                	boolean viewNameStartsWithRedirect = (modelAndView.getViewName() == null ? true : modelAndView.getViewName().startsWith(UrlBasedViewResolver.REDIRECT_URL_PREFIX));
                		
                	if(modelAndView.hasView() && (
                			( isViewObject && !isRedirectView) || 
                			(!isViewObject && !viewNameStartsWithRedirect))){
                		modelAndView.addObject("stuff", "importantStuff");
                		modelAndView.addObject("moreStuff", "moreImportantStuff");
                	}
                }
                (Please note, I have not tried out this code)

                You will also need to register each Interceptor with each handler mapper.

                I hope this helps

                Josh

                Comment


                • #9
                  That worked like a charm. Thanks for your input Josh. Do you happen to know any documents that would explain more of the 'frameworky' type of aspects to Spring? I was thinking of writing a dynamic proxy to solve this problem but I was too afraid to go down that path!

                  Again, thanks for all your detailed help.

                  Comment


                  • #10
                    Hi John,

                    Its no problem at all, its interesting for me as well as I start digging into the code to check how it works.

                    Regarding the dynamic proxies, could you explain a bit further what you would like to achieve, anything is possible its just knowing the right path.

                    Josh

                    Comment


                    • #11
                      Well, essentially I wanted to create what spring calls interceptor which is what you just helped with. Any time a handleRequest method was called on a controller I wanted to intercept the call, add stuff the model, then invoke the method.

                      Now here's here another question for you, regarding unit testing. How do I get test my RequestHandler in a full test? When I do my normal Spring testing, the dispatcher servlet isnt loaded, hence the interceptors arent listening. I have tried loading it a bunch of ways but none seem to work. Thoughts?

                      Comment


                      • #12
                        John,

                        I'm not sure how you are setting up your tests, but I usually do something like:
                        Code:
                        public void setUp() {
                          String[] paths = 
                          { "/WEB-INF/ServletToTest-servlet.xml", };
                            ctx = new XmlWebApplicationContext();
                            ctx.setConfigLocations( paths );
                            ctx.setServletContext( new MockServletContext( "" ) );
                            ctx.refresh();
                          }
                        }
                        Then in the test, do something like:
                        Code:
                        public void testTheEvilBreakingCode () throws Exception {
                          ContToTestController cont = ( ContToTestController ) ctx.getBean( "contToTestController" );
                          ... run some tests
                        }
                        Then, the fun begins.

                        Hope that helps... if not, I would be curious to see how you are setting up your tests.

                        Steve O

                        Comment


                        • #13
                          Steve O,

                          I did try that but the interceptors are never invoked. Most likely because it is somewhere else in the stack.

                          Comment


                          • #14
                            Hi John,

                            The interceptors are used by the DispatcherServlet and thus can not be tested with how you are proposing to as the test requests are not going through a DispatcherServlet but straight to the controller.

                            I wouldn't worry about testing the interceptor working with the controller, I would test each individually for what they do.

                            Josh

                            Comment


                            • #15
                              I had a feeling you were gonna say that. I got that to work but I really wanted an integration test in its entirety. That seems odd to me, no? I have become so accustomed to programatic wiring and the ability to test processes as a whole.

                              Again, thanks for all the help.

                              Comment

                              Working...
                              X