Wen

用Intellij和Tomcat实现RESTful服务(Jersey)并远程部署

用了三天的假期时间终于搭建了RESTful服务环境,代码不多主要时间都在踩坑,踩完坑赶紧记录下来造福后人。本文涉及代码可从Github下载。

背景


简单说一下背景,这次是心血来潮想搭建一个RESTful服务。一年前搭建过一个,当时是多人合作,不想代码deploy的太随意,所以当时的流程是:代码Review过了push到了GitHub,Github会自动同步服务器上的代码,最后自己再运行一个脚本,在服务器上compile并且deploy。但这次是自己用,想尽可能简化流程,我能想到的最简单的流程就是,在IDE里点击运行,然后代码自动deploy到远程服务器。然后这次的踩坑经历就朝着这个方向开始了。

本地用的Mac开发,服务器用的DigitalOcean,IDE用的Intellij Ultimate版,所有软件和库都用的当时最新版(2017.1.1).

目标


细分一下自己的目标:

  1. 在DigitalOcean搭建Tomcat服务器,并测试可用。
  2. 搭建本地Tomcat服务器,并测试可用。
  3. 在本地用Intellij和Tomcat及RESTful库(Jersey)集成。
  4. Intellij实现部署到远程服务器。

DigitalOcean搭建Tomcat服务


DigitalOcean是性价比比较高的VPS,最便宜的服务器是每个月$5,而且网上教程较多,所以服务器用选了它家的,关于它的注册和配置请自行搜索,OS我选的是CentOS7。

创建并配置好一个Droplets后,根据官方的Tutorial来安装Tomcat 8:

How To Install Apache Tomcat 8 on CentOS 7

我的过程中和Tutorial不同的是:

  1. Java用的1.8.0_111
  2. Tomcat用的8.5.9
  3. Tutorial的最后一步,在Tomcat欢迎页中进入Manager App,我并没有做成,不过不影响后面,只要能看到Tomcat欢迎页就好了。

搭建本地Tomcat服务


本地用的Mac,这个教程网上很多这里就不多说了,注意Java和Tomcat的版本最好和服务器保持一致。

Intellij集成Tomcat及RESTful库


到这里才算进入正题,我首先参考了这篇文章:

Starting out with Jersey & Apache Tomcat using IntelliJ

推荐阅读,但我这里也会涵盖里面用得到的内容。注意只有Intellij的Ultimate版才支持和Tomcat的集成,免费的社区版是不具备这个功能的,比较好的是Ultimate版本对于学生是免费的,我用学校的邮箱注册所以把这笔钱省了。

Terminology

JAX-RS

JAX-RS (Java API for RESTful Web Services) 是Oracle定义的一套Java API,主要是通过标注(annotation,比如@GET,@PUT,@POST等)将一个资源类或POJOJava类,封装为Web资源。有很多库实现了这套接口,比如Jersey, Apache CXF, RESTeasy等,这里用Jersey,原因是网上教程比较多。

Jersey

Jersey最新版本是Jersey2.x,实现的是JAX-RS 2.0规范,与 Jersey1.x 相比有很大的改变,JSON转换也有不同。上面参考的文章用的是Jersey1.x,我这里用2.x版本。

Maven & The Central Repository

Maven是管理项目dependencies的工具,它可以自动从Central Repository下载项目所需的包。

Apache Tomcat

Apache Tomcat is a popular, open source implementation of the Java Servlet API. A servlet is “a Java class used to extend the capabilities of servers.”

创建项目

打开IntelliJ,选择New Project,然后在左侧栏中选择Java Enterprise,右侧栏中选择RESTful Web Service,然后点击下面的Configure..,所有的jar都uncheck,因为会后面通过Maven来管理,所以就不用在这里下载了。最后效果如下图。

添加Maven支持

一般Maven是在命令行模式下运行的,就像npm一样,但如果在Intellij中添加了Maven支持,就可以彻底摆脱命令行实现全自动化的依赖管理。在Project左侧栏右击项目名,然后选择Add Framework Support。弹出的对话框中选择Maven,然后会自动创建一个pom.xml文件,groupId就写你的package name,这里我用io.zhaowen.jerseydemo。点击Event对话框,会有提示你Enable Auto-Import,这里我们Enable一下。

添加Web支持

跟上一步一样,打开Add Frameworks Support对话框,选择Web Application 并且选中Create web.xml

添加Jersey

现在需要把Jersey库通过Maven添加进来,方式就是修改pom.xml文件,最后就是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?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>io.zhaowen.jerseydemo</groupId>
<artifactId>JerseyDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version.jdk>1.8</version.jdk>
<version.jersey>2.25</version.jersey>
<version.servlet.api>3.1.0</version.servlet.api>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${version.servlet.api}</version>
<scope>provided</scope>
</dependency>
<!-- Jersey -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${version.jersey}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${version.jersey}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>${version.jersey}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${version.jdk}</source>
<target>${version.jdk}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

创建测试Java类

src/main/java目录下创建一个新package,名字要和之前定义的groupId一致。然后在这个包里创建一个HelloWorld.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package io.zhaowen.jerseydemo;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/hello")
public class HelloWorld {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getMessage() {
return "Hello world!";
}
}

配置Tomcat

现在要来在Intellij里配置一下Tomcat,在Intellij中选择Run > Edit Configurations… > “+” > Tomcat Server > Local。然后选到Deployment, 点击+号, 点击Artifact…,应该只有一个item显示出来,我这里是JerseyDemo:war exploded,点击OK

配置Servlet

终于到最后一步了。之前添加Web支持那一步中产生的web/WEB-INF/web.xml修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>jerseyServlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>io.zhaowen.jerseydemo</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jerseyServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

最后告诉Tomcat去deploy哪些类。点击File > Project Structure > Artifacts. 选中右边所有的item,然后点击右键,选择Put Into Output Root

Run

大胆去点击那个绿色的小三角来运行吧!浏览器会自动打开http://localhost:8080/,然后给你一个HTTP Status 404 - Not Found。不要害怕!那是因为在HelloWorld.java里,定义的path是/hello,所以请打开http://localhost:8080/hello,你会看到最亲切的Hello World.

用Intellij实现远程部署


这一步主要参考自这篇文章并加以改动:

在idea中部署远程Tomcat

修改环境变量,添加Tomcat启动参数

如果“在DigitalOcean搭建Tomcat服务器“那一步你是按照本文给出的link来操作的,那么Tomcat的安装目录是/opt/tomcat,先进去:

1
cd /opt/tomcat

然后添加Tomcat启动参数到catalina.sh

1
vi bin/catalina.sh

在里面添加如下代码:

1
CATALINA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=10.0.0.1"

注意将最后的ip地址(-Djava.rmi.server.hostname)换成你host的ip地址。

清空Tomcat部署目录

部署后的文件都会放在webapps这个文件夹内,先清空:

1
2
rm -rf ./webapps/*
rm -rf ./work/Catalina/localhost/*

启动Tomcat服务

1
sudo systemctl restart tomcat

更改tomcat账户

Deploy时要用到服务器上的tomcat账户,但由于之前官方Tutorial上把这个账户设置为不能shell登录,这里还需要改一下,而且要为它设置密码。下面命令在root账户下运行):

1
2
usermod -s /bin/bash tomcat
passwd tomcat

本地Intellij添加远端配置

  1. 新建Tomcat Remote configuration

  2. 弹出的对话框里在Tomcat Server Settings那里,Type选择sftp,意思是用ssh的协议来进行本地和服务器的文件传输,而不用新开别的文件传输服务,比如ftp等。

  3. Host那里点击...,弹出新对话框,User Name就写tomat,密码就是刚刚设置的密码。

  4. 切换到Deployment选项卡,点击+号,选Artifact,完成后点ok。

运行

注意打开的url应该是

1
http://[ip address]:8080/JerseyDemo_war_exploded/hello

大功告成!注意到在ip地址后面还跟了一个JerseyDemo_war_exploded,这个是Project的Artifact的名字,如果想更改可以去File > Project Structure > Artifacts。本质上是因为Intellij的deploy会上传一个与Artifact名同名的文件夹到服务器上,如果你登录到服务器你会发现在webapps这个文件夹下就是一个叫JerseyDemo_war_exploded的文件夹,文件夹里面是编译好的代码。如果想在url里去掉这个名字,可以把Artifact改成ROOT,因为Tomcat会忽略叫ROOT的文件夹。

用Intellij实现远程调试Debug


参考了文章:

使用IntelliJ IDEA进行远程调试

因为比较简单就不贴图了,具体见上文,简单来说就两步:

  1. 在本地Intellij中,打开上一节添加远端配置中的对话框,上一节是添加的Tomcat Server,这里要选择Remote。在接下来的对话框中,只需要设置HostPortHost就是远程服务器的地址,Port可以随便写,但要记住在第二步要用到。
  2. 在远程服务器中,打开之前提到的catalina.sh,在CATALINA_OPTS=那一行末尾 (引号内) 添加如下代码,注意address的值就是你在第一步中设置的Port值。
    1
    -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000


404 Error

这种情况大部分是因为url输入的不对,比如url中少了文件夹的名字,可以看看webapps文件夹下是否有相应的路径。

ClassNotFoundException和404

如果404 Error不是因为上面那个原因,或者遇到了ClassNotFoundException,就去File > Project Structure > Artifacts里看看右侧的Available Elements是不是有可以点开的三角号,如果有就说明Build出来的class没有放在deploy的文件夹,选中右边所有的item,然后点击右键,选择Put Into Output Root

ClassNotFoundException

如果还有ClassNotFoundException,那很有可能是因为Intellij的一个bug:

Add Option to Force Clean Artifact build

Intellij中存放要deploy内容的文件夹(out文件夹),竟然不会随着Rebuild Project清空,也就是说,Intellij会一直保存着你从一开始编译的类和添加过的dependency,并把它们一起放到服务器上!无故增加deploy负担不说,如果你升级了某个dependency,新旧版本会同时存在服务器上,必然会导致出错。Solution就是手动把out文件夹删掉。这个坑我踩了好久。。。

转载请注明出处:http://zhaowen.io/post/IntelliJ_Tomcat_Jersey_DigitalOcean/