X.d 笔记

小Web,大世界

0%

使用Jersey搭建Restful服务,问题记录

最近新开一个项目,一部分页面,大部分属于API,新开当然严格要求下Restful规范,要求技术栈是Java,对比了一下目前的几个框架,觉得 Jersey 和 Spring MVC 都不错,但是 Jersey支持 JSR311 规范,所以就没随主流使用 Spring MVC 了。

关于框架怎么搭,看下官方文档其实很快的,这里记录一搭建时遇到的一些问题。

技术栈说明

首先,使用 Maven 做为依赖管理工具,另外,除子很的的 Restful API 之外,我的项目里面还有一部分 Web 页面。所以多引入了 jersey-mvc-jsp 扩展,使用 jersey-media-json-jackson 返回 json 风格的数据。

另外,使用 Jetty 做为开发用的服务器,感觉比 Tomcat 实在方便太多。直接在 Maven 里面配置即可。

最后,我的前端页面使用了 Sass 进行 CSS 编译,使用 gulp、webpack、babel 进行 Js 编译与自动化。

静态文件Lock

这个是 Jetty 的问题,由于我有一部分静态页面要开发,所以必须不能被 Lock ,不然我的 js,css 文件都无法生成。

如果是用Jetty8的话,需要把connector切换到bio模式,在pom.xml里面加入以下配置即可。

<configuration>
<connectors>
<connector implementation="org.eclipse.jetty.server.bio.SocketConnector">
<port>80</port>
</connector>
</connectors>
</configuration>

但Jetty9不支持BIO了,没有办法通过connector参数解决,我的解决如下:在web.xml上面设置org.eclipse.jetty.servlet.Default.useFileMappedBuffer为false,覆盖掉jetty默认的值,虽然有污染代码,但对功能没什么影响,因为Jetty只是开发用,生产环境不是Jettty,如果生产环境也是Jetty的话,最好搞清楚再加。

<!-- web.xml -->
<context-param>
        <param-name>org.eclipse.jetty.servlet.Default.useFileMappedBuffer</param-name>
        <param-value>false</param-value>
</context-param>

WebSocket

如果使用Jersey全局拦截的话,ws请求大半情况是404,你的 @ServerEndpoint永远找不到。但我又不是太想用Jersey自带的Server-Sent Events (SSE)功能,感觉太不主流,也不想用Atmosphere这样的插件,感觉加重代码复杂度增加。目前我的解决方案如下:

  1. 按原生的WebSocket先写着,代码不贴了,这里路径是 @ServerEndpoint("/_")
  2. 自定义Fitter
import org.glassfish.jersey.servlet.ServletContainer;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 开放WebSocket请求
 */
public class XDFilter extends ServletContainer {
    @Override
    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        if(request.getRequestURI().endsWith("/_")){
            chain.doFilter(request,response);
        }else{
            super.doFilter(request, response, chain);
        }
    }
}
  1. 把web.xml里面先前的
<filter>
    <filter-name>xdnote</filter-name>
    <!-- 先前的jersey提供的filter注释掉,用我自己的filter-->
    <!--<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>-->
    <filter-class>com.xdnote.fiter.XDFilter</filter-class>
</filter>
  1. OK了,使用JavaScript里面的 new WebSocket(ws://localhost/_)试一试,应该是可以成功了。
  2. 注意以下几点
  • 虽然Jetty7就支持WebSocket,但以Maven插件形式的Jetty需要Jetty9才支持WebSocket
  • 我的解决方案(最终War包)在Tomcat7 上 不行,在Tomcat8 上可以

配置

我是一个有洁癖的人,肯定是不想像官方那样写一个启动类的,如果配置能弄好就不手写一个class了,最后我的Fitter配置如下。

<filter>
    <filter-name>xdnote</filter-name>
    <!-- 说明:如果不使用websocket可以直接用jersey的Filter--->
    <!--<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>-->
    <filter-class>com.xdnote.fiter.XDFilter</filter-class>
    <init-param>
        <param-name>jersey.config.server.mvc.templateBasePath.jsp</param-name>
        <param-value>/WEB-INF/jsp</param-value>
    </init-param>
    <!-- 自动扫描包 这样我就不用写配置类了,自动扫描我所有的控制器-->
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>com.xdnote</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.server.provider.scanning.recursive</param-name>
        <param-value>true</param-value>
    </init-param>
    <!-- 自动注册配置类 :这里自己写了一个走向通用错误页面的provider -->
    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>
org.glassfish.jersey.server.mvc.jsp.JspMvcFeature,
org.glassfish.jersey.media.multipart.MultiPartFeature,
org.glassfish.jersey.server.spring.scope.RequestContextFilter,
com.xdnote.provider.AppExceptionMapper
        </param-value>
    </init-param>
    <!-- 禁用WADL -->
    <init-param>
        <param-name>jersey.config.server.wadl.disableWadl</param-name>
        <param-value>true</param-value>
    </init-param>
    <!-- 静态文件正则表达式 -->
    <init-param>
        <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
        <param-value>/(((images|css|js|fonts)/.*)|(favicon\.ico))</param-value>
    </init-param>
    <!-- MIME Type -->
    <init-param>
        <param-name>jersey.config.server.mediaTypeMappings</param-name>
        <param-value>json:application/json,js:application/javascript</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>xdnote</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Spring 不能使用最新的 4.x 版本

首先看是不是必须,如果没有强迫症什么都要用最新的版本的话,Jersey 默认会引入3.x 系列的 Spring Core 做为核心依赖。

网上很多使用 Maven excude include 的方法,我也试过,可以,但会有一些莫名奇妙的 Bug,所以这里不推荐使用。

其它

Jersey的坑还有很多的,有一些在配置里面解决了,还有一些坑应该都比较简单,Google一下就OK了。