天天看點

深入Spring Boot:實作對Fat Jar jsp的支援

spring boot 對于jsp支援的限制

對于jsp的支援,Spring Boot官方隻支援了war的打包方式,不支援fat jar。參考官方文檔:

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html#boot-features-jsp-limitations

這裡spring boot官方說是tomcat的問題,實際上是spring boot自己改變了打包格式引起的。參考之前的文章:

http://hengyunabc.github.io/spring-boot-classloader/#spring-boot-1-3-%E5%92%8C-1-4-%E7%89%88%E6%9C%AC%E7%9A%84%E5%8C%BA%E5%88%AB

原來的結構之下,tomcat是可以掃描到fat jar裡的

META-INF/resources

目錄下面的資源的。在增加了

BOOT-INF/classes

之後,則tomcat掃描不到了。

那麼怎麼解決這個問題呢?下面給出一種方案,來實作對spring boot fat jar/exploded directory的jsp的支援。

個性化配置tomcat,把BOOT-INF/classes 加入tomcat的ResourceSet

在tomcat裡,所有掃描到的資源都會放到所謂的

ResourceSet

裡。比如servlet 3規範裡的應用jar包的

META-INF/resources

就是一個

ResourceSet

現在需要想辦法把spring boot打出來的fat jar的

BOOT-INF/classes

目錄加到

ResourceSet

裡。

下面通過實作tomcat的

LifecycleListener

接口,在

Lifecycle.CONFIGURE_START_EVENT

事件裡,擷取到

BOOT-INF/classes

的URL,再把這個URL加入到

WebResourceSet

/**
 * Add main class fat jar/exploded directory into tomcat ResourceSet.
 *
 * @author hengyunabc 2017-07-29
 *
 */
public class StaticResourceConfigurer implements LifecycleListener {

    private final Context context;

    StaticResourceConfigurer(Context context) {
        this.context = context;
    }

    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            URL location = this.getClass().getProtectionDomain().getCodeSource().getLocation();

            if (ResourceUtils.isFileURL(location)) {
                // when run as exploded directory
                String rootFile = location.getFile();
                if (rootFile.endsWith("/BOOT-INF/classes/")) {
                    rootFile = rootFile.substring(0, rootFile.length() - "/BOOT-INF/classes/".length() + 1);
                }
                if (!new File(rootFile, "META-INF" + File.separator + "resources").isDirectory()) {
                    return;
                }

                try {
                    location = new File(rootFile).toURI().toURL();
                } catch (MalformedURLException e) {
                    throw new IllegalStateException("Can not add tomcat resources", e);
                }
            }

            String locationStr = location.toString();
            if (locationStr.endsWith("/BOOT-INF/classes!/")) {
                // when run as fat jar
                locationStr = locationStr.substring(0, locationStr.length() - "/BOOT-INF/classes!/".length() + 1);
                try {
                    location = new URL(locationStr);
                } catch (MalformedURLException e) {
                    throw new IllegalStateException("Can not add tomcat resources", e);
                }
            }
            this.context.getResources().createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/", location,
                    "/META-INF/resources");

        }
    }
}           

為了讓spring boot embedded tomcat加載這個

StaticResourceConfigurer

,還需要一個

EmbeddedServletContainerCustomizer

的配置:

@Configuration
@ConditionalOnProperty(name = "tomcat.staticResourceCustomizer.enabled", matchIfMissing = true)
public class TomcatConfiguration {
    @Bean
    public EmbeddedServletContainerCustomizer staticResourceCustomizer() {
        return new EmbeddedServletContainerCustomizer() {
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                if (container instanceof TomcatEmbeddedServletContainerFactory) {
                    ((TomcatEmbeddedServletContainerFactory) container)
                            .addContextCustomizers(new TomcatContextCustomizer() {
                                @Override
                                public void customize(Context context) {
                                    context.addLifecycleListener(new StaticResourceConfigurer(context));
                                }
                            });
                }
            }

        };
    }
}           

這樣子的話,spring boot就可以支援fat jar裡的jsp資源了。

demo位址:

https://github.com/hengyunabc/spring-boot-fat-jar-jsp-sample

總結

  • spring boot改變了打包結構,導緻tomcat沒有辦法掃描到fat jar裡的

    /BOOT-INF/classes

  • 通過一個

    StaticResourceConfigurer

    把fat jar裡的

    /BOOT-INF/classes

    加到tomcat的

    ResourceSet

    來解決問題