让Tomcat7识别War包的Class-Path(MANIFEST.MF)属性动态加载类路径

原文:https://my.oschina.net/noahxiao/blog/173499

目录

 

一、背景

二、分析

三、开发

四、总结


一、背景

几个Web项目需要打包到一个发布包中,问题是War包都各自包含了自己的WEB-INF/lib,其中不少依赖Jar有重复。所以希望把War包的所有的lib都放到外部的一个公共目录减小总工程大小。但放在一个目录,以目录方式加载到全局Classpath就会产生类库冲突问题,如:web1使用spring2,web2使用spring3。

二、分析

一般Jar包中都包含一个META-INF/MANIFEST.MF文件。在这个配置文件中可以指定一个Class-Path属性,再执行java -jar xxx.jar时,Java会以这个Class-Path属性中定义为准。

War包中也有MANIFEST.MF文件,当然也可以添加Class-Path属性(Maven)。但问题是这个属性从Java规范上讲并没有约定,所以应用服务器一般都不识别。

我们需要解决的是类路径加载的问题。还好Tomcat上下文配置文件的Context标签中有Loader标签,允许我们重写WebappLoader。

三、开发

1、Tomcat应用上下文XML配置样例

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

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

2、ManifestClasspathWebappLoader类源码

继承WebappLoader,override startInternal方法。

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等)中。

3、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文件,实际效果文件如下:

4、Tomcat后台控制台输出

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

四、总结

所有Web工程的Jar包都放在外部的公共目录,减小了空间占用。虽然在同一个目录但相互之间并不会产生任何影响,Tomcat会按War包的Class-Path属性一个一个文件进行精确加载类路径(非目录)。

发布了200 篇原创文章 · 获赞 43 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/corleone_4ever/article/details/105240151