讓Tomcat7識別War包的Class-Path(MANIFEST.MF)屬性動態加載類路徑

1、背景

幾個Web項目須要打包到一個發佈包中,問題是War包都各自包含了本身的WEB-INF/lib,其中很多依賴Jar有重複。因此但願把War包的全部的lib都放到外部的一個公共目錄減少總工程大小。但放在一個目錄,以目錄方式加載到全局Classpath就會產生類庫衝突問題,如:web1使用spring2,web2使用spring3。 java

2、分析

通常Jar包中都包含一個META-INF/MANIFEST.MF文件。在這個配置文件中能夠指定一個Class-Path屬性,再執行java -jar xxx.jar時,Java會以這個Class-Path屬性中定義爲準。 web

War包中也有MANIFEST.MF文件,固然也能夠添加Class-Path屬性(Maven)。但問題是這個屬性從Java規範上講並無約定,因此應用服務器通常都不識別。 spring

咱們須要解決的是類路徑加載的問題。還好Tomcat上下文配置文件的Context標籤中有Loader標籤,容許咱們重寫WebappLoader。 shell

3、開發

一、Tomcat應用上下文XML配置樣例

在Tomcat應用上下文配置文件中,定義Web工程的Loader指定到自定義WebappLoader。 apache

<?xml version="1.0" encoding="UTF-8"?>
<Context docBase="${xxx.home}/apps/xxx">
    <Loader className="org.noahx.tomcat.ManifestClasspathWebappLoader" />
</Context>

二、ManifestClasspathWebappLoader類源碼

繼承WebappLoader,override startInternal方法。 tomcat

package org.noahx.tomcat;

import org.apache.catalina.Container;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappLoader;

import java.io.*;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

/**
 * 從War中的MANIFEST.MF得到類路徑並進行額外加載
 * User: noah
 * Date: 10/29/13
 * Time: 1:20 AM
 * To change this template use File | Settings | File Templates.
 */
public class ManifestClasspathWebappLoader extends WebappLoader {

    public ManifestClasspathWebappLoader() {
        super();
    }


    public ManifestClasspathWebappLoader(ClassLoader parent) {
        super(parent);
    }

    @Override
    protected void startInternal() throws LifecycleException {
        final Container container = getContainer();
        if (container instanceof StandardContext) {
            final String docBase = ((StandardContext) container).getDocBase();
            File baseFile = new File(docBase);

            if (baseFile.exists() && baseFile.canRead()) {  //是否可讀

                if (baseFile.isDirectory()) {     //目錄

                    final File manifestFile = new File(baseFile, "META-INF/MANIFEST.MF");

                    if (manifestFile.exists() && manifestFile.canRead() && manifestFile.isFile()) {     //MANIFEST.MF文件可讀

                        System.out.println("found MANIFEST.MF" + manifestFile);

                        try {
                            FileInputStream fileInputStream = new FileInputStream(manifestFile);
                            setClasspaths(baseFile, fileInputStream);

                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }

                    }

                } else if (baseFile.isFile()) { //文件(war)
                    //目前沒有實現war方式的解析
                }

            }
        }

        super.startInternal();
    }

    /**
     * 設置MANIFEST.MF流中的類路徑
     *
     * @param baseFile
     * @param inputStream
     */
    private void setClasspaths(File baseFile, InputStream inputStream) {
        String classpaths[] = null;
        try {
            final Manifest manifest = new Manifest(inputStream);
            final Attributes attributes = manifest.getMainAttributes();
            String classpathValue = attributes.getValue("Class-Path");
            if (classpathValue != null) {          //不爲null說明發現Class-Path
                classpathValue = classpathValue.replaceAll("[\r\n]+$", ""); //移除換行
                classpaths = classpathValue.split("\\s+");     //拆分類路徑字符串
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (classpaths != null) {   //若是發現類路徑則設置類路徑
            for (String classpath : classpaths) {
                addRepository(new File(baseFile.getParent(), classpath).toURI().toString());    //轉換相對路徑爲實際路徑並轉換爲URI
            }
            System.out.println(baseFile.getName() + " append " + classpaths.length + " classpaths.");

        }

    }

}

只實現了目錄方式的Web部署,你們能夠按須要擴展War文件方式的。
addRepository方法就是在爲Tomcat啓動該Web工程提供新擴展的類路徑。
*該類須要以Jar包方式放在Tomcat的lib目錄(該lib中包含tomcat的Jar,如:catalina.jar等)中。 服務器

三、Web工程添加Class-Path屬性的方法(Maven)

<build>
        <plugins>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>../lib/</classpathPrefix>
                            <useUniqueVersions>false</useUniqueVersions>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
War包中MANIFEST.MF文件,實際效果文件以下:

四、Tomcat後臺控制檯輸出

found MANIFEST.MF/nautilus/xxxxxxx/apps/xxx/META-INF/MANIFEST.MF
xxx append 90 classpaths.

4、總結

全部Web工程的Jar包都放在外部的公共目錄,減少了空間佔用。雖然在同一個目錄但相互之間並不會產生任何影響,Tomcat會按War包的Class-Path屬性一個一個文件進行精確加載類路徑(非目錄)。 app

相關文章
相關標籤/搜索