幾個Web項目須要打包到一個發佈包中,問題是War包都各自包含了本身的WEB-INF/lib,其中很多依賴Jar有重複。因此但願把War包的全部的lib都放到外部的一個公共目錄減少總工程大小。但放在一個目錄,以目錄方式加載到全局Classpath就會產生類庫衝突問題,如:web1使用spring2,web2使用spring3。 java
通常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
在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>
繼承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等)中。 服務器
<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文件,實際效果文件以下:
found MANIFEST.MF/nautilus/xxxxxxx/apps/xxx/META-INF/MANIFEST.MF xxx append 90 classpaths.
全部Web工程的Jar包都放在外部的公共目錄,減少了空間佔用。雖然在同一個目錄但相互之間並不會產生任何影響,Tomcat會按War包的Class-Path屬性一個一個文件進行精確加載類路徑(非目錄)。 app