最近项目需要研究webapp以自带容器的方式启动,于是研究了如何使用jetty作为web容器,并嵌入到webapp里面,实现webapp自带容器启动。最终的目标是 :

命令行执行java -jar jetty-embeded-webapp.war,就能直接运行webapp。

在网上搜索了相关教程,最终整合成功,记录如下 :

【温馨提示】

这里采用maven来构建项目,方便依赖管理以及后期项目编译打包。项目最终的演示功能为 : Spring MVC + jetty 内嵌启动webapp。该demo项目的源码可以查看我的github :

https://github.com/ZzzCrazyPig/jetty-embeded-webapp-demo

下面记录实战的每一个步骤 :

1. Eclipse构建maven webapp项目

使用Eclipse构建maven webapp project

2. 加入pom依赖

2.1 加入jetty依赖

        <!-- jetty -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>${jetty.version}</version>
            <!-- <scope>provided</scope> -->
        </dependency>

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-webapp</artifactId>
            <version>${jetty.version}</version>
        </dependency>

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-jsp</artifactId>
            <version>${jetty.version}</version>
        </dependency>

2.2 加入spring依赖

        <!-- spring mvc -->

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- jackson -->

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>

3. 配置spring mvc

applicationContext.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc  http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 启动Springmvc注解驱动 -->
    <mvc:annotation-driven />

    <!-- 自动将控制器加载到bean -->
    <context:component-scan base-package="com.crazypig.demo.embededwebapp"></context:component-scan>
    <!-- 配置处理静态资源的请求 -->
    <!-- <mvc:resources mapping="assets/js/**" location="assets/js/" />
    <mvc:resources mapping="assets/css/**" location="assets/css/" /> -->

    <!-- 配置视图解析器 -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="" />
        <property name="suffix" value=".jsp" /><!--可为空,方便实现自已的依据扩展名来选择视图解释类的逻辑 -->
        <!-- <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" /> -->
    </bean>

</beans>

web.xml配置

<web-app>
  <display-name>jetty embeded webapp demo</display-name>

  <welcome-file-list>
        <welcome-file>index</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/index</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

4. 编写Spring MVC Controller

package com.crazypig.demo.embededwebapp;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class IndexController {

    @RequestMapping({"/", "/index"})
    public ModelAndView index() {

        return new ModelAndView("index");

    }

}

5. 编写启动类

package com.crazypig.demo.embededwebapp;

import java.io.File;
import java.net.URL;
import java.security.ProtectionDomain;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.webapp.WebAppContext;

/**
 * 
 * web应用启动器
 * 
 * @author CrazyPig
 * @since 2016-08-03
 *
 */
public class Launcher {

    public static final int DEFAULT_PORT = 8080;
    public static final String DEFAULT_CONTEXT_PATH = "/jetty-embeded-webapp";
    private static final String DEFAULT_APP_CONTEXT_PATH = "src/main/webapp";


    public static void main(String[] args) {

        runJettyServer(DEFAULT_PORT, DEFAULT_CONTEXT_PATH);

    }

    public static void runJettyServer(int port, String contextPath) {

        Server server = createJettyServer(port, contextPath);
        try {
            server.start();
            server.join();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                server.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    public static Server createJettyServer(int port, String contextPath) {

        Server server = new Server(port);
        server.setStopAtShutdown(true);

        ProtectionDomain protectionDomain = Launcher.class.getProtectionDomain();
        URL location = protectionDomain.getCodeSource().getLocation();
        String warFile = location.toExternalForm();

        WebAppContext context = new WebAppContext(warFile, contextPath);
        context.setServer(server);

        // 设置work dir,war包将解压到该目录,jsp编译后的文件也将放入其中。
        String currentDir = new File(location.getPath()).getParent();
        File workDir = new File(currentDir, "work");
        context.setTempDirectory(workDir);

        server.setHandler(context);
        return server;

    }

    public static Server createDevServer(int port, String contextPath) {

        Server server = new Server();
        server.setStopAtShutdown(true);

        ServerConnector connector = new ServerConnector(server);
        // 设置服务端口
        connector.setPort(port);
        connector.setReuseAddress(false);
        server.setConnectors(new Connector[] {connector});

        // 设置web资源根路径以及访问web的根路径
        WebAppContext webAppCtx = new WebAppContext(DEFAULT_APP_CONTEXT_PATH, contextPath);
        webAppCtx.setDescriptor(DEFAULT_APP_CONTEXT_PATH + "/WEB-INF/web.xml");
        webAppCtx.setResourceBase(DEFAULT_APP_CONTEXT_PATH);
        webAppCtx.setClassLoader(Thread.currentThread().getContextClassLoader());
        server.setHandler(webAppCtx);

        return server;
    }

}

6. 配置maven编译打包

pom.xml里面增加编译打包

<build>
        <finalName>jetty-embeded-webapp-demo</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <target>1.7</target>
                    <source>1.7</source>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
                <version>1.7</version>
                <executions>
                    <execution>
                        <id>main-class-placement</id>
                        <phase>prepare-package</phase>
                        <configuration>
                            <tasks>
                                <copy todir="${project.build.directory}/${project.artifactId}/">
                                    <fileset dir="${project.build.directory}/classes/">
                                        <include name="**/*/Launcher.class" />
                                    </fileset>
                                </copy>
                            </tasks>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                            <transformers>
                                <transformer
                                    implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.crazypig.demo.embededwebapp.Launcher</mainClass>
                                </transformer>
                            </transformers>
                            <artifactSet>
                                <includes>
                                    <include>org.eclipse.jetty:*</include>
                                    <include>*:javax.servlet*</include>
                                    <include>org.glassfish:javax.el*</include>
                                </includes>
                            </artifactSet>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <id>assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal><!-- 只运行一次 -->
                        </goals>
                        <configuration>
                            <descriptors> <!--描述文件路径 -->
                                <descriptor>src/main/assembly/assembly.xml</descriptor>
                            </descriptors>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

【说明】

  • 其中maven-shade-plugin插件用于将特定的第三方依赖追加到将要编译打包的jar里面(or war),使得该jar成为可运行的jar(or war)。
    <mainClass>指向main函数所在的类,在该项目中为Launcher类。<artifactSet><includes>里面指定需要整合到jar里面的第三方依赖库。

  • 另外,由于项目为web项目,打成war包的时候Launcher类所在的目录为/WEB-INF/class下,我们需要将它移动到war包根目录,因此我们使用maven-antrun-plugin插件来完成这个需求

7. 打包

使用maven-assembly-plugin插件来协助进行打包,在pom.xml文件里面加入maven-assembly-plugin的配置,如上一节所示。对应的assembly.xml文件如下所示 :

2018-04-16 : 发现之前因为assembly.xml中因为文件路径分隔符写法有问题导致在mac平台上面打包没有bin目录,这里已经将文件分隔符换成 / ,以适配不同的系统

<assembly
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>standalone</id>
    <formats>
        <format>zip</format>
    </formats>
    <dependencySets>

    </dependencySets>
    <files>
        <file>
            <source>target/${project.artifactId}.war</source>
            <outputDirectory>/</outputDirectory>
        </file>
    </files>
    <fileSets>
        <fileSet>
            <directory>src/main/resources</directory>
            <outputDirectory>/conf</outputDirectory>
            <!-- <includes> <include>some/path</include> </includes> <excludes> <exclude>some/path1</exclude> 
                </excludes> -->
        </fileSet>
        <fileSet>
            <directory>src/main/assembly/bin</directory>
            <outputDirectory>/bin</outputDirectory>
            <includes>
                <include>start.sh</include>
            </includes>
            <fileMode>0755</fileMode>
            <lineEnding>unix</lineEnding>
        </fileSet>
        <fileSet>
            <directory>src/main/assembly/bin</directory>
            <outputDirectory>/bin</outputDirectory>
            <includes>
                <include>start.bat</include>
            </includes>
        </fileSet>
    </fileSets>
</assembly>

【说明】

  • maven-assembly-plugin是一个强大的maven插件,使用它可以让我们自定义打包后的目录结构,其功能类似ant

  • 我们加入start.bat和start.sh,用于windows平台和linux平台直接运行该脚本来启动web应用

运行

走到这一步,我们只要运行mvn clean package打包,在项目源码路径target子目录下找到jetty-embeded-webapp-demo-standalone.zip压缩包,解压到d:\jetty-embeded-webapp-demo,进入该目录下的bin目录,运行start.bat :

D:\jetty-embeded-webapp-demo\bin>"D:\Java\jdk1.8.0_66/bin/java" -jar ..\jetty-embeded-webapp-demo.war
2016-08-06 14:00:36.276:INFO::main: Logging initialized @175ms
2016-08-06 14:00:36.345:INFO:oejs.Server:main: jetty-9.2.z-SNAPSHOT
log4j:WARN No appenders could be found for logger (org.springframework.web.servlet.DispatcherServlet).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
2016-08-06 14:00:45.124:INFO:/jetty-embeded-webapp:main: Initializing Spring FrameworkServlet 'springmvc'
2016-08-06 14:00:46.334:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@490caf5f{/jetty-embeded-webapp,file:/D:/jetty-embeded-webapp-demo/work/webapp/,AVAILABLE}{file:/D:/jetty-embeded-webapp-demo/jetty-embeded-webapp-demo.war}
2016-08-06 14:00:46.369:INFO:oejs.ServerConnector:main: Started ServerConnector@5e403b4a{HTTP/1.1}{0.0.0.0:8080}
2016-08-06 14:00:46.373:INFO:oejs.Server:main: Started @10272ms
2016-08-06 14:02:34.372:INFO:oejs.ServerConnector:Thread-0: Stopped ServerConnector@5e403b4a{HTTP/1.1}{0.0.0.0:8080}
2016-08-06 14:02:34.372:INFO:/jetty-embeded-webapp:Thread-0: Destroying Spring FrameworkServlet 'springmvc'

等待jetty服务器起来并加载了我们的应用后,打开浏览器,输入访问地址 :
http://localhost:8080/jetty-embeded-webapp/

出现”Hello World!”,即表示运行成功!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐