javaspringspring-mvcclasspathwebjars

Webjars-locator doesn't work with XML based Spring MVC 4.2.x configuration?


I have a Spring MVC 4.2.x project. I am configuring it via XML based configuration file:

<servlet>
    <servlet-name>foo</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

In the context.xml file I have configured resources and annotation-driven mode:

<mvc:resources mapping="/webjars/**" location="/webjars/"/>
<mvc:annotation-driven/>

Everything works fine except the one thing: webjars-locator - Webjars locator doesn't work at all.


I've started look into the Spring MVC sources to understand what's going wrong, and found, that webjars-locator works through WebJarsResourceResolver class, which's object is added in the ResourceChainRegistration.getResourceResolvers() class method.

The full sequence looks like:

WebMvcConfigurationSupport.resourceHandlerMapping() >> ResourceHandlerRegistration.getHandlerMapping() >> ResourceHandlerRegistration.getRequestHandler() >> ResourceChainRegistration.getResourceResolvers(), where it is added as:

if (isWebJarsAssetLocatorPresent) {
    result.add(new WebJarsResourceResolver());
}

The problem, is that in case of XML based configuration described above, this sequence is not invoked, neither WebMvcConfigurationSupport class is used.

Furthermore, if I add

@EnableWebMvc
@Configuration
public class WebConfig {
}

into project, WebMvcConfigurationSupport obviously works, but in ResourceHandlerRegistration.getHandlerMapping():

protected AbstractHandlerMapping getHandlerMapping() {
    if (registrations.isEmpty()) {
        return null;
    }
...
}

registrations is empty!


After all, the only one way how to forcibly make Spring to add WebJarsResourceResolver into the resolver chain, is:

1) remove <mvc:resources mapping="/webjars/**" location="/webjars/"/> from the context.xml and

2) add addResourceHandlers into WebConfig:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry
            .addResourceHandler("/webjars/**")
            .addResourceLocations("/webjars/")
            .setCachePeriod(3600)
            .resourceChain(true)  // !!! very important
    ;
}

My question is: what am I doing wrong? Why XML bases configuration doesn't cause WebJarsResourceResolver to be registered?


Solution

  • Let's say that the context root path is /ctx. With your configuration, a resource path as /webjars/RESOURCE is actually mapped to /ctx/webjars/RESOURCE; in contrary to common expectation that it should be mapped to /webjars/RESOURCE.

    Based on Spring 4.2.x documentation and an example similar issue, you need to map /webjars/** to the default dispatcher servlet as:

    This allows for mapping the DispatcherServlet to "/" (thus overriding the mapping of the container’s default Servlet), while still allowing static resource requests to be handled by the container’s default Servlet. It configures a DefaultServletHttpRequestHandler with a URL mapping of "/**" and the lowest priority relative to other URL mappings.

    which means adding the following should fix the issue:

    <mvc:resources mapping="/webjars/**" location="/webjars/">
        <mvc:resource-chain>
            <mvc:resource-cache />
        </mvc:resource-chain>
    </mvc:resources>
    <mvc:annotation-driven />
    <mvc:default-servlet-handler />
    

    Another note that is the example petclinic uses location="classpath:/META-INF/resources/webjars/" for webjars which I am not sure is relevant here.

    Hope this helps.


    Added by @Andremoniy:

    for Spring 4 it has slightly different markup:

    <mvc:resources mapping="/webjars/**" location="/webjars/">
        <mvc:resource-chain resource-cache="true"/>
    </mvc:resources>