springboot 打包与启动03. "java -jar"启动分析

206 阅读3分钟

1. maven打包后的文件

进入springboot-jar/target目录,使用tree命令,目录结构如下:

 $ tree
.
├── classes
│   └── com
│       └── gitee
│           └── funcy
│               └── maven
│                   └── jar
│                       ├── Main.class
│                       └── controller
│                           └── IndexController.class
├── generated-sources
│   └── annotations
├── maven-archiver
│   └── pom.properties
├── maven-status
│   └── maven-compiler-plugin
│       └── compile
│           └── default-compile
│               ├── createdFiles.lst
│               └── inputFiles.lst
├── springboot-jar-1.0.0.jar
└── springboot-jar-1.0.0.jar.original

14 directories, 7 files

注意springboot-jar-1.0.0.jarspringboot-jar-1.0.0.jar.original的区别:springboot-jar-1.0.0.jar.original属于原始Maven打包jar文件,该文件仅包含应用本地资源,如编译后的classes目录下的资源文件等,未引入第三方依赖资源;而springboot-jar-1.0.0.jar 引入了第三方依赖资源(主要为jar包)。

使用 unzip springboot-jar-1.0.0.jar -d tmp解压jar包,内容如下:

 $ tree tmp/
tmp/
├── BOOT-INF
│   ├── classes
│   │   └── com
│   │       └── gitee
│   │           └── funcy
│   │               └── maven
│   │                   └── jar
│   │                       ├── Main.class
│   │                       └── controller
│   │                           └── IndexController.class
│   └── lib
│       ├── classmate-1.4.0.jar
│       ├── hibernate-validator-6.0.13.Final.jar
│       ├── jackson-annotations-2.9.0.jar
│       ├── jackson-core-2.9.7.jar
│       ├── jackson-databind-2.9.7.jar
│       ├── jackson-datatype-jdk8-2.9.7.jar
│       ├── jackson-datatype-jsr310-2.9.7.jar
│       ├── jackson-module-parameter-names-2.9.7.jar
│       ├── javax.annotation-api-1.3.2.jar
│       ├── jboss-logging-3.3.2.Final.jar
│       ├── jul-to-slf4j-1.7.25.jar
│       ├── log4j-api-2.11.1.jar
│       ├── log4j-to-slf4j-2.11.1.jar
│       ├── logback-classic-1.2.3.jar
│       ├── logback-core-1.2.3.jar
│       ├── slf4j-api-1.7.25.jar
│       ├── snakeyaml-1.23.jar
│       ├── spring-aop-5.1.3.RELEASE.jar
│       ├── spring-beans-5.1.3.RELEASE.jar
│       ├── spring-boot-2.1.1.RELEASE.jar
│       ├── spring-boot-autoconfigure-2.1.1.RELEASE.jar
│       ├── spring-boot-starter-2.1.1.RELEASE.jar
│       ├── spring-boot-starter-json-2.1.1.RELEASE.jar
│       ├── spring-boot-starter-logging-2.1.1.RELEASE.jar
│       ├── spring-boot-starter-tomcat-2.1.1.RELEASE.jar
│       ├── spring-boot-starter-web-2.1.1.RELEASE.jar
│       ├── spring-context-5.1.3.RELEASE.jar
│       ├── spring-core-5.1.3.RELEASE.jar
│       ├── spring-expression-5.1.3.RELEASE.jar
│       ├── spring-jcl-5.1.3.RELEASE.jar
│       ├── spring-web-5.1.3.RELEASE.jar
│       ├── spring-webmvc-5.1.3.RELEASE.jar
│       ├── tomcat-embed-core-9.0.13.jar
│       ├── tomcat-embed-el-9.0.13.jar
│       ├── tomcat-embed-websocket-9.0.13.jar
│       └── validation-api-2.0.1.Final.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── com.gitee.funcy
│           └── springboot-jar
│               ├── pom.properties
│               └── pom.xml
└── org
    └── springframework
        └── boot
            └── loader
                ├── ExecutableArchiveLauncher.class
                ├── JarLauncher.class
                ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                ├── LaunchedURLClassLoader.class
                ├── Launcher.class
                ├── MainMethodRunner.class
                ├── PropertiesLauncher$1.class
                ├── PropertiesLauncher$ArchiveEntryFilter.class
                ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
                ├── PropertiesLauncher.class
                ├── WarLauncher.class
                ├── archive
                │   ├── Archive$Entry.class
                │   ├── Archive$EntryFilter.class
                │   ├── Archive.class
                │   ├── ExplodedArchive$1.class
                │   ├── ExplodedArchive$FileEntry.class
                │   ├── ExplodedArchive$FileEntryIterator$EntryComparator.class
                │   ├── ExplodedArchive$FileEntryIterator.class
                │   ├── ExplodedArchive.class
                │   ├── JarFileArchive$EntryIterator.class
                │   ├── JarFileArchive$JarFileEntry.class
                │   └── JarFileArchive.class
                ├── data
                │   ├── RandomAccessData.class
                │   ├── RandomAccessDataFile$1.class
                │   ├── RandomAccessDataFile$DataInputStream.class
                │   ├── RandomAccessDataFile$FileAccess.class
                │   └── RandomAccessDataFile.class
                ├── jar
                │   ├── AsciiBytes.class
                │   ├── Bytes.class
                │   ├── CentralDirectoryEndRecord.class
                │   ├── CentralDirectoryFileHeader.class
                │   ├── CentralDirectoryParser.class
                │   ├── CentralDirectoryVisitor.class
                │   ├── FileHeader.class
                │   ├── Handler.class
                │   ├── JarEntry.class
                │   ├── JarEntryFilter.class
                │   ├── JarFile$1.class
                │   ├── JarFile$2.class
                │   ├── JarFile$JarFileType.class
                │   ├── JarFile.class
                │   ├── JarFileEntries$1.class
                │   ├── JarFileEntries$EntryIterator.class
                │   ├── JarFileEntries.class
                │   ├── JarURLConnection$1.class
                │   ├── JarURLConnection$JarEntryName.class
                │   ├── JarURLConnection.class
                │   ├── StringSequence.class
                │   └── ZipInflaterInputStream.class
                └── util
                    └── SystemPropertyUtils.class

21 directories, 91 files

可以看到,文件中主要分为如下几个目录:

  • BOOT-INF/classes 目录存放应用编译后的class文件;
  • BOOT-INF/lib 目录存放应用依赖的jar包;
  • META-INF/ 目录存放应用依赖的jar包;
  • org/ 目录存放spring boot相关的class文件。

2. java -jar 启动 springboot jar包

java官方规定,java -jar 命令引导的具体启动类必须配置在MANIFEST.MF文件中,而根据jar文件规范MANIFEST.MF文件必须存放在/META-INF/目录下。因此,启动类配置在jar包的/META-INF/MANIFEST.MF文件中,查看该文件,内容如下:

$ cat MANIFEST.MF 
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: chengyan
Start-Class: com.gitee.funcy.maven.jar.Main
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.1.1.RELEASE
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_222
Main-Class: org.springframework.boot.loader.JarLauncher

发现Main-Class属性指向的Classorg.springframework.boot.loader.JarLauncher,而该类存放在jar包的org/springframework/boot/loader/目录下,并且项目的引导类定义在Start-Class属性性中,该属性并非java平台标准META-INF/MANIFEST.MF属性。

注:

  1. org.springframework.boot.loader.JarLauncher是可执行jar的启动器,org.springframework.boot.loader.WarLauncher 是可执行war的启动器。

  2. org.springframework.boot.loader.JarLauncher所在的jar文件的Maven GAV信息为org.springframework.boot:spring-boot-loader:${springboot-version},通常情况下,这个依赖没有必要引入springboot项目的pom.xml文件。

查看 JarLauncher 源码,如下:

public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

}

可以发现,BOOT-INF/classes/BOOT-INF/lib/ 分别使用常量 BOOT_INF_CLASSESBOOT_INF_LIB 表示,并且用于isNestedArchive(Archive.Entry) 方法判断,从该方法的实现分析,方法参数Archive.Entry看似为jar文件中的资源,比如application.properties

Archive.Entry 有两种实现,其中一种为org.springframework.boot.loader.archive.JarFileArchive.JarFileEntry,基于java.util.jar.JarEntry,表示 FAT JAR 嵌入资源,另一种为org.springframework.boot.loader.archive.ExplodedArchive.FileEntry,基于文件系统实现。这也说明了JarLauncher支持JAR文件系统两种启动方式。

文件系统启动方式如下:

  1. 解压jar包到temp目录:unzip springboot-jar-1.0.0.jar -d tmp
  2. 进入temp目录,运行命令: java org.springframework.boot.loader.JarLauncher 可以看到,项目同样能正常启动。

JarLauncher 作为引导类时,当执行java -jar 命令时,/META-INF 资源的Main-Class属性将调用其main(String[])方法,实际上调用的是JarLauncher#launch(args) 方法,而该方法继承于基类org.springframework.boot.loader.Launcher,它们之间的继承关系如下:

  • org.springframework.boot.loader.Launcher
    • org.springframework.boot.loader.ExecutableArchiveLauncher
      • org.springframework.boot.loader.JarLauncher
      • org.springframework.boot.loader.WarLauncher

简单来说,springboot jar启动过程如下:

  1. java -jar xxx.jar 运行的是 JarLauncher
  2. JarLauncher#main(String[])方法会调用Launcher#launch(String[])方法,创建ClassLoader()及调用项目的main方法
    • 项目主类的获取实现位于ExecutableArchiveLauncher#getMainClass(),主要是从/META-INF/MANIFEST.MF获取Start-Class属性
    • 项目主类的main()方法调用位于MainMethodRunner#run(),使用反射方式进行调用

3. java -jar 启动 springboot war包

从上面的分析,我们得到了启动jar包的org.springframework.boot.loader.JarLauncher以及启动war包的org.springframework.boot.loader.WarLauncher,这里我们来分析下WarLauncher上如何工作的。

WarLauncher 代码如下:

public class WarLauncher extends ExecutableArchiveLauncher {

	private static final String WEB_INF = "WEB-INF/";

	private static final String WEB_INF_CLASSES = WEB_INF + "classes/";

	private static final String WEB_INF_LIB = WEB_INF + "lib/";

	private static final String WEB_INF_LIB_PROVIDED = WEB_INF + "lib-provided/";

	public WarLauncher() {
	}

	protected WarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	public boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(WEB_INF_CLASSES);
		}
		else {
			return entry.getName().startsWith(WEB_INF_LIB)
					|| entry.getName().startsWith(WEB_INF_LIB_PROVIDED);
		}
	}

	public static void main(String[] args) throws Exception {
		new WarLauncher().launch(args);
	}

}

可以看到,WEB-INF/classes/WEB-INF/lib/WEB-INF/lib-provided/均为WarLauncherClass Path,其中WEB-INF/classes/WEB-INF/lib/是传统的Servlet应用的ClassPath路径,而 WEB-INF/lib-provided/属性springboot WarLauncher 定制实现。那么WEB-INF/lib-provided/ 究竟是干嘛的呢?看到provided,我们可以大胆猜想WEB-INF/lib-provided/存放的是pom.xml文件中,scopeprovided的jar。

为了验证以上猜想,修改的pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gitee.funcy</groupId>
    <artifactId>springboot-war</artifactId>
    <version>1.0.0</version>
    <!-- 指定打包方式为war包 -->
    <packaging>war</packaging>
    <name>springboot非parent war打包方式</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-boot.version>2.1.1.RELEASE</spring-boot.version>
        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <!-- Import dependency management from Spring Boot -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 测试war包中 WEB-INF/lib-provided/ 目录内容-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <!-- 打成war包时,需要添加该插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <!-- 保持与 spring-boot-dependencies 版本一致 -->
                <version>3.2.2</version>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

这里我们添加了springboot的测试jar org.springframework.boot:spring-boot-test,并将其scope设置为provided. 运行maven打包命令mvn clean install -Dmaven.test.skip=true ,可以看到项目能正常打包。

打包完成后,进入target目录,运行java -jar springboot-war-1.0.0.war,项目能正常启动。

接下来,我们来看看springboot-war-1.0.0.war有些啥。首先使用unzip springboot-war-1.0.0.war -d tmp 命令解压,再使用tree -h 命令查看文件结构,结果如下

 $ tree -h
.
├── [ 128]  META-INF
│   ├── [ 311]  MANIFEST.MF
│   └── [  96]  maven
│       └── [  96]  com.gitee.funcy
│           └── [ 128]  springboot-war
│               ├── [  95]  pom.properties
│               └── [3.3K]  pom.xml
├── [ 160]  WEB-INF
│   ├── [  96]  classes
│   │   └── [  96]  com
│   │       └── [  96]  gitee
│   │           └── [  96]  funcy
│   │               └── [  96]  maven
│   │                   └── [ 160]  war
│   │                       ├── [ 688]  Main.class
│   │                       ├── [ 891]  StartApplication.class
│   │                       └── [  96]  controller
│   │                           └── [ 646]  IndexController.class
│   ├── [1.2K]  lib
│   │   ├── [ 65K]  classmate-1.4.0.jar
│   │   ├── [1.1M]  hibernate-validator-6.0.13.Final.jar
│   │   ├── [ 65K]  jackson-annotations-2.9.0.jar
│   │   ├── [316K]  jackson-core-2.9.7.jar
│   │   ├── [1.3M]  jackson-databind-2.9.7.jar
│   │   ├── [ 33K]  jackson-datatype-jdk8-2.9.7.jar
│   │   ├── [ 98K]  jackson-datatype-jsr310-2.9.7.jar
│   │   ├── [8.4K]  jackson-module-parameter-names-2.9.7.jar
│   │   ├── [ 26K]  javax.annotation-api-1.3.2.jar
│   │   ├── [ 65K]  jboss-logging-3.3.2.Final.jar
│   │   ├── [4.5K]  jul-to-slf4j-1.7.25.jar
│   │   ├── [258K]  log4j-api-2.11.1.jar
│   │   ├── [ 17K]  log4j-to-slf4j-2.11.1.jar
│   │   ├── [284K]  logback-classic-1.2.3.jar
│   │   ├── [461K]  logback-core-1.2.3.jar
│   │   ├── [ 40K]  slf4j-api-1.7.25.jar
│   │   ├── [294K]  snakeyaml-1.23.jar
│   │   ├── [360K]  spring-aop-5.1.3.RELEASE.jar
│   │   ├── [656K]  spring-beans-5.1.3.RELEASE.jar
│   │   ├── [935K]  spring-boot-2.1.1.RELEASE.jar
│   │   ├── [1.2M]  spring-boot-autoconfigure-2.1.1.RELEASE.jar
│   │   ├── [ 413]  spring-boot-starter-2.1.1.RELEASE.jar
│   │   ├── [ 421]  spring-boot-starter-json-2.1.1.RELEASE.jar
│   │   ├── [ 423]  spring-boot-starter-logging-2.1.1.RELEASE.jar
│   │   ├── [ 422]  spring-boot-starter-tomcat-2.1.1.RELEASE.jar
│   │   ├── [ 421]  spring-boot-starter-web-2.1.1.RELEASE.jar
│   │   ├── [1.0M]  spring-context-5.1.3.RELEASE.jar
│   │   ├── [1.2M]  spring-core-5.1.3.RELEASE.jar
│   │   ├── [274K]  spring-expression-5.1.3.RELEASE.jar
│   │   ├── [ 23K]  spring-jcl-5.1.3.RELEASE.jar
│   │   ├── [1.3M]  spring-web-5.1.3.RELEASE.jar
│   │   ├── [782K]  spring-webmvc-5.1.3.RELEASE.jar
│   │   ├── [3.1M]  tomcat-embed-core-9.0.13.jar
│   │   ├── [244K]  tomcat-embed-el-9.0.13.jar
│   │   ├── [257K]  tomcat-embed-websocket-9.0.13.jar
│   │   └── [ 91K]  validation-api-2.0.1.Final.jar
│   └── [  96]  lib-provided
│       └── [194K]  spring-boot-test-2.1.1.RELEASE.jar
└── [  96]  org
    └── [  96]  springframework
        └── [  96]  boot
            └── [ 544]  loader
                ├── [3.5K]  ExecutableArchiveLauncher.class
                ├── [1.5K]  JarLauncher.class
                ├── [1.5K]  LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                ├── [5.6K]  LaunchedURLClassLoader.class
                ├── [4.6K]  Launcher.class
                ├── [1.5K]  MainMethodRunner.class
                ├── [ 266]  PropertiesLauncher$1.class
                ├── [1.4K]  PropertiesLauncher$ArchiveEntryFilter.class
                ├── [1.9K]  PropertiesLauncher$PrefixMatchingArchiveFilter.class
                ├── [ 19K]  PropertiesLauncher.class
                ├── [1.7K]  WarLauncher.class
                ├── [ 416]  archive
                │   ├── [ 302]  Archive$Entry.class
                │   ├── [ 437]  Archive$EntryFilter.class
                │   ├── [ 945]  Archive.class
                │   ├── [ 273]  ExplodedArchive$1.class
                │   ├── [1.1K]  ExplodedArchive$FileEntry.class
                │   ├── [1.5K]  ExplodedArchive$FileEntryIterator$EntryComparator.class
                │   ├── [3.7K]  ExplodedArchive$FileEntryIterator.class
                │   ├── [5.1K]  ExplodedArchive.class
                │   ├── [1.7K]  JarFileArchive$EntryIterator.class
                │   ├── [1.1K]  JarFileArchive$JarFileEntry.class
                │   └── [7.2K]  JarFileArchive.class
                ├── [ 224]  data
                │   ├── [ 485]  RandomAccessData.class
                │   ├── [ 282]  RandomAccessDataFile$1.class
                │   ├── [2.6K]  RandomAccessDataFile$DataInputStream.class
                │   ├── [3.2K]  RandomAccessDataFile$FileAccess.class
                │   └── [3.9K]  RandomAccessDataFile.class
                ├── [ 768]  jar
                │   ├── [4.9K]  AsciiBytes.class
                │   ├── [ 616]  Bytes.class
                │   ├── [3.0K]  CentralDirectoryEndRecord.class
                │   ├── [5.1K]  CentralDirectoryFileHeader.class
                │   ├── [4.5K]  CentralDirectoryParser.class
                │   ├── [ 540]  CentralDirectoryVisitor.class
                │   ├── [ 345]  FileHeader.class
                │   ├── [ 12K]  Handler.class
                │   ├── [3.5K]  JarEntry.class
                │   ├── [ 299]  JarEntryFilter.class
                │   ├── [2.0K]  JarFile$1.class
                │   ├── [1.2K]  JarFile$2.class
                │   ├── [1.3K]  JarFile$JarFileType.class
                │   ├── [ 15K]  JarFile.class
                │   ├── [1.6K]  JarFileEntries$1.class
                │   ├── [2.0K]  JarFileEntries$EntryIterator.class
                │   ├── [ 14K]  JarFileEntries.class
                │   ├── [ 702]  JarURLConnection$1.class
                │   ├── [4.2K]  JarURLConnection$JarEntryName.class
                │   ├── [9.6K]  JarURLConnection.class
                │   ├── [3.5K]  StringSequence.class
                │   └── [1.8K]  ZipInflaterInputStream.class
                └── [  96]  util
                    └── [5.1K]  SystemPropertyUtils.class

22 directories, 93 files

相比于FAT JAR的解压目录,War 增加了 WEB-INF/lib-provided,并且该目录仅有一个jar文件,即spring-boot-test-2.1.1.RELEASE.jar,这正是我们在pom.xml文件中设置的scopeprovided的jar包。

由此可以得出结论:WEB-INF/lib-provided存放的是scopeprovided的jar包

我们现来看下META-INF/MANIFEST.MF的内容:

$ cat META-INF/MANIFEST.MF 
Manifest-Version: 1.0
Built-By: chengyan
Start-Class: com.gitee.funcy.maven.war.Main
Spring-Boot-Classes: WEB-INF/classes/
Spring-Boot-Lib: WEB-INF/lib/
Spring-Boot-Version: 2.1.1.RELEASE
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_222
Main-Class: org.springframework.boot.loader.WarLauncher

可以看到,该文件与jar包中的META-INF/MANIFEST.MF很相似,在文件中同样定义了Main-ClassStart-Class,这也说明了该war可以使用java -jar xxx.jarjava org.springframework.boot.loader.WarLauncher 启动,这也与我们的验证结果一致。

4. tomcat等外部容器启动war包

在springboo刚开始推广的时候,我们还是习惯于将项目打成war包,然后部署到tomcat等web容器中运行。那springboot的war包是如何做到既能用java命令启动,又能放在tomcat容器中启动呢?这就是之前提到的WEB-INF/lib-provided目录的功能了。

传统的servlet应用的class path路径仅关注WEB-INF/classes/WEB-INF/lib/WEB-INF/lib-provided/目录下的jar包将被servlet容器忽略,如servlet api,该api由servlet容器提供。我们在打包时,可以把servlet相关jar包的scope设置成provided,这样就完美实现了servlet容器启动与java命令启动的兼容:

  • 当部署到servlet容器中时,WEB-INF/lib-provided/目录下的jar包就被容器忽略了(由于servlet容器本身就提供了servlet的相关jar包,如果不忽略,就会出现jar包重复引入问题);
  • 当使用java命令执行时,此时无servlet容器提供servlet的相关jar包,而WarLauncher在运行过程中会加载WEB-INF/lib-provided/目录下的jar包。