Search code examples
javaspringspring-cloudnetflix-zuulservlet-2.5

Spring cloud Zuul with servlet-api 2.5


  1. @EnableZuulProxy doesn't work under a servlet 2.5 container. Is there any workaround to get spring-cloud zuul work under a servet 2.5 container?

  2. Also I could not find the annotation processor of @EnableZuulProxy. Please provide the class which propesses @EnableZuulProxy so that I can better understand what this annotation really does.


Solution

  • Spring Cloud is meant to be run on servlet 3.0. That being said, it is possible to get @EnableZuulProxy running on servlet 2.5. I had to figure out a hack for this as I had to get this working in Tomcat 6.

    The main issue is due to the ZuulConfiguration.class, which has the method:

    @Bean
    @ConditionalOnMissingBean(name = "zuulServlet")    
    public ServletRegistrationBean zuulServlet() { ... }
    

    The issue here is that ServletRegistrationBean uses javax.servlet.Registration$Dynamic, which is not available until Servlet 3.0. This results in a NoClassDefFoundError.

    To work around this, use the spring-boot-legacy project to first register a DispatcherServlet. Secondly, you'll have to manually create a zuul servlet.

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.dm.gateway.microservicegateway.Application</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.boot.legacy.context.web.SpringBootContextLoaderListener</listener-class>
        </listener>
    
        <filter>
            <filter-name>ContextLifecycleFilter</filter-name>
            <filter-class>com.netflix.zuul.context.ContextLifecycleFilter</filter-class>
        </filter>
    
    
        <filter-mapping>
            <filter-name>ContextLifecycleFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <servlet>
            <servlet-name>appServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextAttribute</param-name>
                <param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet>
            <servlet-name>zuul</servlet-name>
            <servlet-class>com.netflix.zuul.http.ZuulServlet</servlet-class>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>appServlet</servlet-name>
            <url-pattern>/*</url-pattern>
        </servlet-mapping>
    
    </web-app>
    

    I found the best way to remove the auto servlet registration, is to just make a verbatim copy of ZuulConfig called ZuulOverrideConfig, and remove the zuulServlet() method. This is because the ZuulProxyConfiguration extends ZuulConfiguration, and it seemed to create the bean even when i tried to override it. I'm not 100% on the mechanics behind this, so there may be a better way.

    The second change I made in ZuulOverrideConfig was to call an extended implementation of ZuulFilterInitializer, called 'LegacyZuulFilterInitializer`. This is because for some reason, the Zuul servlet was being crated, and able to be invoked, but no filters were bootstrapped. This extension is a hacky way to get the filters to bootstrap.

    Next, I created a copy of ZuulProxyConfiguration called ZuulLegacyProxyConfiguraiton, and had it extend ZuulOverrideConfig.class.

    Finally, I annotated the Application class as follows.

    @EnableCircuitBreaker
    @EnableDiscoveryClient
    @Import(ZuulLegacyProxyConfiguration.class)
    @SpringBootApplication
    public class Application {....}
    

    After all these hacks, the implementation finally worked as expected. I wouldn't suggest using this for long, as it's pretty hacky. You won't get configuration class updates automatically when moving to new versions, and I can't guarantee that something won't break randomly!

    This is using Spring Cloud 1.1.4.RELEASE

    Gist of all the code.