Jersey——30分钟速读《Java RESTful Web Service 实战》

一、Jersey入门

1.1 REST简述

REST(Representational State Transfer,表述性状态转移),源于REST之父Roy Thomas Fielding博士在2000年就读加州大学欧文分校期间发表的一篇学术论文——《 Architectural Styles and the Design of Network-based Software Architectures 》。论文中提出了REST的6个特点,分别是:客户端-服务器端模式无状态的可缓存的统一接口的分层系统按需编码

  • 客户端-服务器端模式
    通信只能由客户端单方面发起,表现为请求-响应的形式。
  • 无状态的
    对服务器端的请求应该是无状态的,完整、独立的请求不要求服务器在处理请求时检索任何类型的应用程序上下文或状态。无状态约束使服务器的变化对客户端是不可见的,因为在两次连续的请求中,客户端并不依赖于同一台服务器。一个客户端从某台服务器上收到一份包含链接的文档,当它要做一些处理时,这台服务器宕掉了,可能是硬盘坏掉而被拿去修理,可能是软件需要升级重启——如果这个客户端访问了从这台服务器接收的链接,它不会察觉到后台的服务器已经改变了。通过超链接实现有状态交互,即请求消息是自包含的(每次交互都包含完整的信息),有多种技术实现了不同请求间状态信息的传输,例如 URI 重新,cookies 和隐藏表单字段等,状态可以嵌入到应答消息里,这样一来状态在接下来的交互中仍然有效。REST 风格应用可以实现交互,但它却天然地具有服务器无状态的特征。在状态迁移的过程中,服务器不需要记录任何 Session,所有的状态都通过 URI 的形式记录在了客户端。更准确地说,这里的无状态服务器,是指服务器不保存会话状态(Session);而资源本身则是天然的状态,通常是需要被保存的;这里所指无状态服务器均指无会话状态服务器。
  • 可缓存的
    响应内容可以在通信链的某处被缓存,以改善网络效率。
  • 统一接口的
    系统中的每一个对象或是资源都可以通过一个唯一的 URI 来进行寻址,URI 的结构应该简单、可预测且易于理解,比如定义目录结构式的 URI。
    • 若要在服务器上创建资源,应该使用 POST 方法;
    • 若要检索某个资源,应该使用 GET 方法;
    • 若要更新或者添加资源,应该使用 PUT 方法;
    • 若要删除某个资源,应该使用 DELETE 方法。
  • 分层系统
    通过限制组件的行为(即,每个组件只能“看到”与其交互的紧邻层),将架构分解为若干等级的层。
  • 按需编码
    服务器能够通过向客户端传输可以执行的逻辑来临时扩展或自定义客户端的功能。 这样的示例可以包括编译的组件,例如Java applet和客户端脚本,例如JavaScript。
1.2 JAX-RS & Jersey

JAX-RS是Java领域的REST式的WEB服务的标准规范。直到Java EE6通过JCP组织的JSR 311才将REST在Java领域标准化。JSR 311,其参考实现是GlassFish项目的Jersey1.0,在时隔5年后JavaEE7包含了JSR 339,即JAX-RS2.0,JAX-RS2.0在前面的版本基础上加上了很多实用功能,比如REST客户端API的定义,异步REST等。
JAX-RS的版本对应参考实现的Jersey版本信息如小表:

JAX-RS标准JAX-RS名称JAX-RS实现JDK版本
311JAX-RS 1.0Jersey1.0Java1.6
339JAX-RS 2.0Jersey2.0Java1.7
1.3 SpringMVC是否为REST实现?

SpringMVC是REST实现,但并没有对JAX-RS标准做实现。


二、Jersey部署

2.1 Springboot + Jersey
2.1.1 Maven依赖
 	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jersey</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
2.1.2 Springboot启动类
@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }

    @Bean
    ResourceConfig resourceConfig() {
        return new ResourceConfig(HelloResource.class).register(ServiceExceptionMapper.class).register(LogProvider.class);
    }

}
2.1.3 HelloResource
package com.jersey.springboot;

import org.glassfish.jersey.server.JSONP;
import org.springframework.stereotype.Component;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Component
@Path("/demo/{id}/hello")
public class HelloResource {

    @Path("index")
    @GET
    public Response hello() {
        return Response.ok("hello world!").build();
    }

    @Path("2")
    @GET
    public String hello2() {
        return "helloworld";
    }

    @Path("3")
    @GET
    @HelloResourceBinding
    @Produces(MediaType.APPLICATION_JSON)
    public Response hello3() {
        return Response.ok(new User("kk", "10")).build();
    }

    /**
     * jersey 集成json-p例子
     *
     * @param callback
     * @return
     */
    @Path("jsonp")
    @GET
    @Produces("application/x-javascript")
    @JSONP(queryParam = JSONP.DEFAULT_QUERY)
    public Response jsonp(String callback) {
        return Response.ok(new User("kk", "10")).build();
    }

    @Path("e")
    @GET
    public Response postException() {
        throw new ServiceException("哈哈你哈哈哈哈");
    }

    public static class User {
        private String name;
        private String age;

        public User(String name, String age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }
    }
}

2.2 V5集成Jersey介绍

现系统支持JDK6及以上应用容器(Tomcat,WAS,WebLogic),由于未知原因,在我们的系统中事实上存在WAS使用Jersey1.0,而其他使用Jersey2.0的现状,对于这两种集成情况,将分WAS-Jersey1.0和Tomcat-Jersey2.0做讲述。

2.2.1 WAS-Jersey1.0
<servlet>
	<servlet-name>rest</servlet-name>
	<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> 
	<init-param>
		<param-name>javax.ws.rs.Application</param-name>
		<param-value>com.xx.yy.rest.XXRestApplication</param-value>
	</init-param> 
	<init-param>
		<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
		<param-value>com.xx.yy.rest.filter.ResponseFilter</param-value>
	</init-param>
	<init-param>
		<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
		<param-value>com.XX.yy.rest.filter.AuthorizationRequestFilter</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup> 
</servlet>
<servlet-mapping>
	<servlet-name>rest</servlet-name>
	<url-pattern>/rest/*</url-pattern>
</servlet-mapping>

在XXRestApplication类中扫描各个XXResource类达到注入各个Resource接口的目的,在此需注意,这部分代码只扫描BaseResource的子类,所以开发同学在做接口开发的时候切记切记</font>。

package com.xx.yy.rest;

import java.io.IOException;
import java.util.Set;

import javax.ws.rs.core.Application;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.logging.Log;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

import com.google.common.collect.Sets;
import com.xx.yy.common.log.CtpLogFactory;
import com.xx.yy.rest.resources.BaseResource;
import com.xx.yy.rest.resources.RestExceptionMapper;
import com.xx.yy.util.ServerDetector;

public class XXRestApplication extends Application {
    private static final Log LOG = LogFactory.getLog(XXRestApplication.class);
    private static Set<Class<?>> classes = Sets.newHashSet();
    
    static {
        LOG.info("初始化REST资源..");
        try {
          // jersey基础类
          String basePackage = ServerDetector.isWebSphere() ? "org.codehaus.jackson.jaxrs" : "com.fasterxml.jackson.jaxrs.json";
          classes.addAll(loadTopLevelClasses(basePackage));
          // V5基础类XX
          String XXPackage = "com.xx.yy.rest.resources";
          Set<Class<?>> set = loadTopLevelClasses(XXPackage);
          for (Class<?> clazz : set) {
              if (BaseResource.class.isAssignableFrom(clazz)) {
                  classes.add(clazz);
              }
          }
          classes.add(RestExceptionMapper.class);
        } catch (IOException e) {
            LOG.error("初始化REST资源异常:", e);
        }
    }
    
    @Override
    public Set<Class<?>> getClasses() {
        return classes;
    }
    
   
    private static Set<Class<?>> loadTopLevelClasses(String basePackage) throws IOException {
        Set<Class<?>> classes = Sets.newHashSet();
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX 
                + ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/*.class";
        Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
        if (ArrayUtils.isNotEmpty(resources)) {
            MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                    String className = metadataReader.getClassMetadata().getClassName();
                    if (className.indexOf("$") > -1 || className.endsWith("Test") || "package-info".equals(className)) {//不加载内部类
                        continue;
                    }
                    try {
                        Class<?> clazz = Class.forName(className);
                        classes.add(clazz);
                    } catch (ClassNotFoundException e) {
                        LOG.error("类加载异常:", e);
                    }
                }
            }
        }
        return classes;
    }
}

使用点评:
在jersey1.0的时候已经支持扫描方式注入Resource了,为什么自己开发注入Resource方式(前期是硬代码注入到XXRestApplication的),对此我表示很费解。如需更多了解Jersey1.0使用可以移步Jersey 1.x - Hello, world!

2.2.2 Tomcat-Jersey2.0

在这个使用有两处对Jersey做了配置,分别是web.xml和注解类CTPResourceConfig。

  • web.xml
	<servlet>
        <servlet-name>rest</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>
                com.xx.yy.rest.resources,com.fasterxml.jackson.jaxrs,com.fasterxml.jackson.jaxrs.json,com.fasterxml.jackson.jaxrs.xml,com.xx.yy.rest.filter
            </param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.classnames</param-name>
            <param-value>com.xx.yy.rest.filter.AuthorizationRequestFilter,com.xx.yy.rest.filter.ResponseFilter,org.glassfish.jersey.media.multipart.MultiPartFeature</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.scanning.recursive</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>4</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>rest</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
  • CTPResourceConfig
package com.xx.yy.rest;

import javax.ws.rs.ApplicationPath;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/rest/*")
public class XXResourceConfig extends ResourceConfig {
	private final static Log LOG = LogFactory.getLog(XXResourceConfig .class);
    public CTPResourceConfig() {
        packages("com.xx.yy.rest.resources");
        packages("com.fasterxml.jackson.jaxrs");
        add("com.xx.yy.rest.filter.AuthorizationRequestFilter");
        add("com.xx.yy.rest.filter.ResponseFilter");
        add("com.fasterxml.jackson.jaxrs.annotation.JacksonFeatures");
        add("org.glassfish.jersey.media.multipart.MultiPartFeature");
    }
	private void add(String className) {
		try {
			register(Class.forName(className));
		} catch (ClassNotFoundException e) {
			LOG.error(e.getLocalizedMessage(),e);
		}
	}
}

对于一个V5应用中有两个地方配置了Jersey,这就导致了在tomcat/logs/catalina.2019-06-26.log文件中输出了如下日志。

26-Jun-2019 16:40:01.543 警告 [localhost-startStop-1] org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer.addServletWithApplication Mapping conflict. A Servlet registration exists with same mapping as the Jersey servlet application, named com.xx.yy.rest.CTPResourceConfig, at the servlet mapping, /rest/*.

如果在fastjson升级到fastjson-1.2.52后,由于fastJson内部实现了JAX-RS的Provider,导致我们的数据写入和写出使用了fastjson的实现了
FastJsonProvider
注释web.xml配置
不注释web.xml配置

2.3 Jersey服务配置加载示意图

REST服务配置加载图

2.4 Jersey web.xml配置参数

对于web.xml中配置Jersey的参数请参考jersey-server-xx.jar中类org.glassfish.jersey.server.ServerProperties,具体参数及说明请参看源码doc说明

public final class ServerProperties
{
  public static final String PROVIDER_PACKAGES = "jersey.config.server.provider.packages";
  public static final String PROVIDER_SCANNING_RECURSIVE = "jersey.config.server.provider.scanning.recursive";
  public static final String PROVIDER_CLASSPATH = "jersey.config.server.provider.classpath";
  public static final String PROVIDER_CLASSNAMES = "jersey.config.server.provider.classnames";
  public static final String MEDIA_TYPE_MAPPINGS = "jersey.config.server.mediaTypeMappings";
  public static final String LANGUAGE_MAPPINGS = "jersey.config.server.languageMappings";
  public static final String HTTP_METHOD_OVERRIDE = "jersey.config.server.httpMethodOverride";
  public static final String WADL_GENERATOR_CONFIG = "jersey.config.server.wadl.generatorConfig";
  public static final String WADL_FEATURE_DISABLE = "jersey.config.server.wadl.disableWadl";
  public static final String BV_FEATURE_DISABLE = "jersey.config.beanValidation.disable.server";
  public static final String BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK = "jersey.config.beanValidation.disable.validateOnExecutableCheck.server";
  public static final String BV_SEND_ERROR_IN_RESPONSE = "jersey.config.beanValidation.enableOutputValidationErrorEntity.server";
  public static final String REDUCE_CONTEXT_PATH_SLASHES_ENABLED = "jersey.config.server.reduceContextPathSlashes.enabled";

  @PropertyAlias
  public static final String FEATURE_AUTO_DISCOVERY_DISABLE = "jersey.config.server.disableAutoDiscovery";

  @PropertyAlias
  public static final String OUTBOUND_CONTENT_LENGTH_BUFFER = "jersey.config.server.contentLength.buffer";

  @PropertyAlias
  public static final String JSON_PROCESSING_FEATURE_DISABLE = "jersey.config.server.disableJsonProcessing";

  @PropertyAlias
  public static final String METAINF_SERVICES_LOOKUP_DISABLE = "jersey.config.server.disableMetainfServicesLookup";

  @PropertyAlias
  public static final String MOXY_JSON_FEATURE_DISABLE = "jersey.config.server.disableMoxyJson";
  public static final String RESOURCE_VALIDATION_DISABLE = "jersey.config.server.resource.validation.disable";
  public static final String RESOURCE_VALIDATION_IGNORE_ERRORS = "jersey.config.server.resource.validation.ignoreErrors";
  public static final String MONITORING_ENABLED = "jersey.config.server.monitoring.enabled";
  public static final String MONITORING_STATISTICS_ENABLED = "jersey.config.server.monitoring.statistics.enabled";
  public static final String MONITORING_STATISTICS_MBEANS_ENABLED = "jersey.config.server.monitoring.statistics.mbeans.enabled";
  public static final String MONITORING_STATISTICS_REFRESH_INTERVAL = "jersey.config.server.monitoring.statistics.refresh.interval";
  public static final String APPLICATION_NAME = "jersey.config.server.application.name";
  public static final String TRACING = "jersey.config.server.tracing.type";
  public static final String TRACING_THRESHOLD = "jersey.config.server.tracing.threshold";
  public static final String RESPONSE_SET_STATUS_OVER_SEND_ERROR = "jersey.config.server.response.setStatusOverSendError";
  public static final String PROCESSING_RESPONSE_ERRORS_ENABLED = "jersey.config.server.exception.processResponseErrors";
  public static final String SUBRESOURCE_LOCATOR_CACHE_SIZE = "jersey.config.server.subresource.cache.size";
  public static final int SUBRESOURCE_LOCATOR_DEFAULT_CACHE_SIZE = 64;
  public static final String SUBRESOURCE_LOCATOR_CACHE_AGE = "jersey.config.server.subresource.cache.age";
  public static final String SUBRESOURCE_LOCATOR_CACHE_JERSEY_RESOURCE_ENABLED = "jersey.config.server.subresource.cache.jersey.resource.enabled";
  public static final String LOCATION_HEADER_RELATIVE_URI_RESOLUTION_RFC7231 = "jersey.config.server.headers.location.relative.resolution.rfc7231";
  public static final String LOCATION_HEADER_RELATIVE_URI_RESOLUTION_DISABLED = "jersey.config.server.headers.location.relative.resolution.disabled";
  }

三、REST API设计

REST式的WEB服务使用HTTP的通用方法作为统一接口的标准词汇,REST式的Web服务所提供的方法信息都在HTTP方法里。在实际开发过程中一般根据业务为其定义资源路径,以对外提供REST服务。图书资源路径下表:

资源路径接口说明HTTP方式输入输出
/book/list获取全部图书资源GET图书资源列表
/book/{bookId}通过主键获取图书资源GETbookId图书资源
/book新增图书资源POSTBook对象
/book保存图书资源PUT
/book/{bookId}删除图书资源DELETE

在V5实际开发过程中,我们只是能使用GET和POST方式,如果还有其他操作的时候,就不够使用了,笔者建议对于其他操作采用动词方式,例如:

资源路径说明
/book/{bookId}/rename重命名
/book/{bookId}/restat修改状态
/book/{bookId}/resort修改排序
3.1 Jersey 常用注解
3.1.1 @QueryParam

@QueryParam用来定义查询参数

findDepartment(@QueryParam("accountId") Long accountId, final @QueryParam("q") String q)
3.1.2 @PathParam

@PathParam注解用来定义路径参数,每个参数对应一个子资源。

  • 普通方式
@Path("{bizId}/{spaceId}/restate")
method1(@PathParam("bizId") Long bizId, @PathParam("spaceId") Long spaceId) {
  • 正则表达式
@Path("{from:\\d+}-{to:\\d+}")
method2(@PathParam("from") Integer from, @PathParam("to") Integer to)
  • 路径区间
@GET
@Path("{themeId}/{path:.+}")
public Response getThemeCss(@PathParam("themeId") Long themeId, @PathParam("path") List<PathSegment> paths) {

	StringBuilder sb = new StringBuilder();
	for (PathSegment pathSegment : paths) {
		sb.append(pathSegment.getPath()).append("/");
	}
	return Response.ok(sb.toString()).build();
}
3.1.3 @Context

@Context注解用来解析上下文参数,可以用来定义Application,Request,Response,Providers,UriInfo, HttpHeader

@GET
getByAddress(@Context Application application,
	@Context HttpServletRequest request,
	@Context HttpServletResponse response,
	@Context Providers providers,
	@Context UriInfo uriInfo,
	@Context HttpHeaders headers)	
	
3.2 HTTP 状态码

JAX-RS规定REST的WEB服务的基本异常有3类,分别对应:

  • HTTP状态码为3XX的:表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向;
  • HTTP状态码为4XX的:这些状态代码表示请求可能出错,妨碍了服务器的处理;
  • HTTP状态码为5XX的:这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错;
状态码说明异常类
200 OK服务器正常处理
201 Created创建新实体,响应头Location指定访问该实体的URL
202 Accepted服务器接收请求,处理尚未完成。可用于异步处理机制
204 No Content服务器正常响应,但响应实体为空
301 Move Permanently请求资源的地址发生永久变动,响应头Location指定新的URL
302 Found请求资源的地址发送临时变动
304 Not Modified客户端缓存资源依然有效
400 Bad Request请求信息出现语法错误BadRequestException
401 Unauthorized请求资源未授权给未验证用户NotAuthorizedException
403 Forbidden请求资源未授权给当前用户ForbiddenException
404 Not Found请求资源不存在NotFoundException
405 Method Not Allowed请求方法不匹配NotAllowedException
406 Not Acceptable请求资源的媒体类型不匹配NotAcceptableException
500 Internal Server Error服务器内部错误,意外终止响应InternalServerErrorException
501 Not Implemented服务器不支持当前请求

四、REST 请求流程

REST请求流程图
在一次URL请求的过程中会经过以上调用链最后返回将数据返回给调用方(也可能是异常),对于里面出现了非常重要的组件ContainerRequestFilter,ContainerResponseFilter, MessageBodyReader和MessageWriter,再此做简单说明。

4.1 ContainerRequestFilter
  • 用于判断用户是否是登录状态,也就是要判断session存不存在
  • 用于记录日志
4.2 ContainerResponseFilter

与ContainerRequestFilter组合使用

4.3 MessageBodyReader

将流转换指定Java类,MessageBodyReader可以与Consumers进行注释,以限制将被适合的媒体类型。

4.3 MessageWriterReader

与MessageBodyReader做的事情恰好相反,将指定的Java类转换为数据流。


5、REST 接口开发最佳实践

5.1 创建XXResource并继承CAPBaseResource类
/**
 * @Description:
 *  1. 通常情况是用json做为输入/输出(若有其他输入/输出格式请在方法上定义);
 *  2. 复写BaseResource#success解决数据为Long数据丢失精度;
 *  3. 对于返回的数据格式为非application/json类型,请使用Response自行构建
 * @Date: 2019-06-24 20:23
 */
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CAPBaseResource extends BaseResource {

    /**
     * fastjson序列化器
     *
     * @return
     */
    protected FastJsonConfig getFastJsonConfig() {
        FastJsonConfig fastJsonConfig = new FastJsonConfig();

        SerializeConfig serializeConfig = SerializeConfig.globalInstance;
        serializeConfig.put(BigInteger.class, ToStringSerializer.instance);
        serializeConfig.put(Long.class, ToStringSerializer.instance);
        serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
        serializeConfig.put(Integer.class, ToStringSerializer.instance);
        serializeConfig.put(Integer.TYPE, ToStringSerializer.instance);
        serializeConfig.put(Date.class, new DateObjectSerializer());
        fastJsonConfig.setSerializeConfig(serializeConfig);

        fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);

        return fastJsonConfig;
    }

    @Override
    protected Response success(Object object) {
        Map<String, Object> map = Maps.newHashMap();
        map.put("code", 0);
        map.put("data", object);

        FastJsonConfig fastJsonConfig = getFastJsonConfig();
        Object entity = JSON.toJSONString(map, fastJsonConfig.getSerializeConfig(), fastJsonConfig.getSerializerFeatures());

        return Response.ok(entity).build();
    }

    /**
     * 日期输出转换器
     */
    private class DateObjectSerializer implements ObjectSerializer {
        @Override
        public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
            SerializeWriter out = serializer.out;
            if (object == null) {
                out.writeNull();
            } else {
                String strVal = Datetimes.formatDatetime((Date)object);
                out.writeString(strVal);
            }
        }
    }
}

5.2 在XXResource指定Path & 在方法上指定Path和请求方式
@Path("CC4/org")
public class CC4OrgResource extends CAPBaseResource {
    private static final Log logger = XXLogFactory.getLog(CC4OrgResource .class);

    private OrganizationAdapterManager getOrganizationAdapterManager() {
        return (OrganizationAdapterManager)AppContext.getBean("organizationAdapterManager");
    }

    /**
     * 获取用户可访问单位列表
     *
     * @return
     */
    @GET
    @Path("account")
    public Response findAccessableAccounts() {
        try {
            List<V3xOrgAccount> list = getCurrentUser();
            DefaultDataSetList dataSetList = new DefaultDataSetList();
            Set<String> accountIds = Sets.newHashSet();
            for (V3xOrgAccount account : list) {
                String parentId = account.getSuperior().toString();
                Node branchNode = dataSetList.createBranchNode(account.getId().toString(), account.getName(), parentId);
                dataSetList.addNode(branchNode);
                accountIds.add(parentId);
            }
            for(Node node : dataSetList.getList()) {
                if (!accountIds.contains(node.getId())) {
                    node.setBranch(false);
                }
            }
            return success(dataSetList);
        } catch (BusinessException e) {
            logger.error("", e);
        }
        return success(ServiceResult.internalError());
    }
}

6、Jersey扩展

  • Jersey推送 & 异步通信
  • JerseyAOP
  • Jersey安全

7、参考链接

  1. HTTP各种状态码的意义(HTTP Status Code)
  2. REST in Action 《REST 实战》
  3. RESTful 架构风格概述
  4. Jersey中ContainerRequestFilter的使用
  5. REST原则六约束与Richardson成熟度模型
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值