Skip to content

Add abstract WebApplicationInitializers #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

poutsma
Copy link
Contributor

@poutsma poutsma commented Apr 3, 2012

These two commits adds three abstract WebApplicationInitializers, to be used
for a typical setup of a Spring Web application.

  • AbstractContextLoaderInitializer provides an abstract base class for
    setting up the ContextLoaderListener.
  • AbstractDispatcherServletInitializer provides an abstract base class
    for setting up one or more DispatcherServlets, with an optional root
    context.
  • AbstractAnnotationConfigDispatcherServletInitializer provides an
    abstract base class for setting up DispatcherServlets based on
    Configuration classes.

Some points for discussion:

  • Packaging: I wasn't sure where to put these classes, and I am fully open for suggestions.
  • Naming: I am happy about AbstractContextLoaderInitializer and AbstractDispatcherServletInitializer, but less happy about AbstractAnnotationConfigDispatcherServletInitializer. Open for suggestions as well.
  • There are a couple of TODOs, where we have the opportunity to pass a ServletContext to a template method. I wasn't sure if the context was needed, so I left it out.

Also, this seems to have turned into two commits, while the goal was to have one. Blame my git ignorance.

@rstoyanchev
Copy link
Contributor

I can appreciate the goal to reduce boilerplate but the present code would require if-else conditions in every method that accepts a servlet name:

if (servletName.equals("one")) {
    // ..
}
else if (servletName.equals("two") {
    // ..
}
else {
    // shouldn't happen..
}

Seeing that potentially 4 times in one class is not appealing. Even for a single servlet if you need to make customizations, you can end up with 6-7 overridden methods in which case I would prefer to write the onStartup method as now.

Don't know what the right solution is but one option is to pass some object like a DispatcherServletRegistrar into a protected method, which subclasses can use to provide basic information for one or more servlets including servlet name, mappings, and config classes. For more advanced needs, separate protected methods could provide access to the created ConfigurableWebApplicationContext(s) and ServletRegistration.Dynamic(s):

/** The first servlet **/
protected void customizeServlet(
    ConfigurableWebApplicationContext wac, ServletRegistration.Dynamic registration) 

/** All servlets **/
protected void customizeServlets(
    Map<String, ConfigurableWebApplicationContext> applicationContexts,
    Map<String, ServletRegistration.Dynamic> registrations)

Not a perfect solution but an alternative to consider at least.

@poutsma
Copy link
Contributor Author

poutsma commented Apr 4, 2012

I think it's a bit unfair to base judgment on uncommon scenarios. I would expect the vast majority of Spring Web Apps to only have one servlet. In that case, the typical extension of AbstractDispatcherServletInitializer would not need 6-7 overridden methods, and there be no need for the if-else block. It would look something like this:

public class MyDispatcherServletInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createServletApplicationContext(String servletName) {
        StaticWebApplicationContext applicationContext = new StaticWebApplicationContext();
        applicationContext.registerSingleton("controller", MyBean.class);
        return applicationContext;
    }

    @Override
    protected String[] getServletMappings(String servletName) {
        return new String[] { "/*"};
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

}

Or if we'd use @Configuration classes, it would even be simpler:

public class MyAnnotationConfigDispatcherServletInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getServletConfigClasses(String servletName) {
        return new Class<?>[] {MyServletConfig.class};
    }

    @Override
    protected String[] getServletMappings(String servletName) {
        return new String[]{"/*"};
    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {MyRootConfig.class};
    }

}

Even with two servlets it wouldn't be so bad as suggested, by using the Java 7 switch statement:

public class MyAnnotationConfigDispatcherServletInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected String[] getServletNames() {
        return new String[]{"one", "two"};
    }

    @Override
    protected Class<?>[] getServletConfigClasses(String servletName) {
        switch (servletName) {
            case "one":
                return new Class<?>[ MyServlet1Config.class];
            case "two":
                return new Class<?>[ MyServlet2Config.class];

        }
        return null;
    }

    @Override
    protected String[] getServletMappings(String servletName) {
        switch (servletName) {
            case "one":
                return new String[] { "/one/*"};
            case "two":
                return new String[] { "/two/*"};
        }
        return null;
    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {MyRootConfig.class};
    }
}

If you do have multiple servlets, and you do have to do a lot of customization on them, then the better option is to simply extend AbstractContextLoaderInitializer or implement WebApplicationInitializer, and roll your own.

But for the simple, common case, I'd say that the code shown above is a lot easier to deal with than having a separate DispatcherServletRegistrar and a dealing with a bunch of maps.

@jhoeller and @cbeams, I'm interested to hear what you guys think.

@jhoeller
Copy link
Contributor

jhoeller commented Apr 5, 2012

Why are we even trying to support multiple servlets in AbstractDispatcherServletInitializer to begin with? Couldn't we simply keep it limited to the root context plus one DispatcherServlet scenario? After all, people can always extend AbstractContextLoaderInitializer and additionally register arbitrary servlets... I'd find that arrangement clearer.

@poutsma
Copy link
Contributor Author

poutsma commented Apr 6, 2012

I actually started out with a single servlet model, but found out it was trivial to add support for multiple servlets, by adding a servlet name parameter.

So if we'd only support one DispatcherServlet, the code wouldn't be much different than it is right now. The only difference would be the lack of said servlet name parameter in the template methods. And the the getServletNames() method would return a single string, instead of an array. As such, it wouldn't be much clearer; or less clearer, for that matter.

@cbeams
Copy link
Contributor

cbeams commented May 1, 2012

@poutsma,

Thanks for putting this together. Overall I think it's a good idea.

style

  • ASL copyright headers should be 2002-2012 for consistency. I agree this makes little sense for new sources, but this the overwhelming convention throughout the rest of the framework.
  • there is an unused JspConfigDescriptor import in DispatcherServletInitializerTests
  • to fix the two-commits problem, squash the two commits into one (e.g. using git rebase -i), then do a git push -f against your SPR-9300 branch. This pull request will automatically update, showing that there is just one commit now.
  • please add the "Issue: SPR-9300" footer to your commit comment, as per the contributor guidelines. please eliminate the trailing period from the subject line of the commit comment as well.
  • @since tags read "3.1.2", but it's not clear to me that we'll backport this yet. I would prefer not to until we've at least vetted it during the 3.2 M1 phase. After that, if there's consensus we could possibly backport in a 3.1.3, but in any case the @since tags should be updated to read "3.2".
  • javadoc @return tags etc that are long enough for a line break shouldn't be indented after the line break. I agree this reads better in the sources, but it's not the convention throughout the rest of the framework.

content

  • I'll echo @jhoeller and @rstoyanchev with regard to providing support for multiple dispatcher servlets. I think it would clarify the matter if we restrict support only to a single servlet. This is by far the majority case (I don't know if I can think of actually seeing a multiple-DS config in the wild, though I'm sure it's out there on occasion). This means that the servletName property will in most cases go unused, and we'll have to explain that in the Javadoc. It means that the getServletNames method is immediately confusing, because most folks only have a single DS. It also means that folks who do wish to override getServletNames will always have to box up the name of their servlet in a String[]{...}. I'm of the mind that most users barely understand the configuration and mechanics of the Spring DispatcherServlet, if at all. Whatever we can do to make things simple and straightforward is good in my book -- and that's why I generally like these subclasses -- but dislike the multi-servlet support. Last point: if someone really wants the multi-servlet approach, an Abstract_Multi_DispatcherServletInitializer could be created/contributed. That would prove that at least one real user wants it.
  • I think that the packaging is sensible. No problems from my side.
  • I don't think it's necessary to provide the ServletContext as a parameter to #createServletApplicationContext. At least I can't think of a use case at the moment.
  • these new classes should be documented in order to be useful. Please consider adding references to Javadoc where appropriate (like @see tags in WebApplicationInitializer, ContextLoaderListener, DispatcherServlet, etc), and also of course the reference documentation. At least please add a JIRA issue for this. It wouldn't make for a bad blog post, either when 3.2 M1 drops. With regard to Javadoc on the classes themselves, what you've already written looks good. You may have noticed that I've been taking an example-driven approach, actually putting typical usage snippets in the Javadoc (see WebApplicationInitializer itself). This would be nice for consistency and usability in your implementations as well.
  • I agree that the naming of AbstractAnnotationConfigDispatcherServletInitializer is unfortunate, but it is consistent (with the other types in the hierarchy and with the AnnotationConfig[Web]ApplicationContext hierarchy). We've gone this long without abbreviating classes, I'm not sure why we'd start now. I'm open to other opinions/naming suggestions, though -- based on the feedback I've been getting so far, people do seem interested in the code-based approach to servlet container configuration, so it's likely enough that these classes will get used. Naming shouldn't scare them off.

@cbeams
Copy link
Contributor

cbeams commented May 16, 2012

@poutsma, just a ping on this. We can still get this into 3.2 M1 if you're interested.

@poutsma
Copy link
Contributor Author

poutsma commented May 16, 2012

The earliest I can take a look at it is Monday, because of a short holiday in taking Thursday and Friday. Let me know if that's good enough.

@cbeams
Copy link
Contributor

cbeams commented May 16, 2012

Yes, that's fine. We won't release until later in the week, for sure.

@poutsma
Copy link
Contributor Author

poutsma commented May 22, 2012

I've pushed a new version, incorporating all the changes suggested. I also tried to squash the commits into one, but failed.

@cbeams
Copy link
Contributor

cbeams commented May 22, 2012

Assuming you have the three commits above sitting as the three most recent commits on your SPR-9300 branch, you should be able to do the following:

git rebase -i HEAD^^^

The editor will open and you'll see the following:

pick fd6d390 Add abstract WebApplicationInitializers
pick 7fc18a5 Fixed merge conflicts
pick 5288ac4 Polishing

change this to read:

pick fd6d390 Add abstract WebApplicationInitializers
fixup 7fc18a5 Fixed merge conflicts
fixup 5288ac4 Polishing

quit the editor, rebasing occurs, and you're left with a single commit which you can then force-push back to to your remote, updating the pull request in the process.

This commit adds three abstract WebApplicationInitializers, to be used
for a typical setup of a Spring Web application.

- AbstractContextLoaderInitializer provides an abstract base class for
   setting up the ContextLoaderListener.
- AbstractDispatcherServletInitializer provides an abstract base class
   for setting up one or more DispatcherServlets, with an optional root
   context.
- AbstractAnnotationConfigDispatcherServletInitializer provides an
   abstract base class for setting up DispatcherServlets based on
   @configuration classes.

Issue: SPR-9300
@cbeams
Copy link
Contributor

cbeams commented May 22, 2012

Thanks, Arjen; this is merged. Note that I made several changes that you can see here: https://gist.github.com/2769617. The most substantive among these is the renaming of #addContextLoaderListener to #registerContextLoaderListener and likewise #addDispatcherServlet to #registerDispatcherServlet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants