在Java中,从1.5开始,我们就可以使用泛型了(generic),这看上去很像C++ Template,但是实际上它们是不同的。在这里我不想过多的描述细节,你可以从Google上搜索一下。 但是,泛型已经变得如此复杂,以至于已经有500多页的 FAQ

 

我们长话短说:泛型提供了编译时类型安全,所以也消除了类型转换的(cast)的需要。它是通过被称为类型消除(type erasure)的编译时技术来实现的。 泛型FAQ解释了所有的细节,对我来说它就是Java泛型的圣经。

在有些情况下,我们需要从JAXRS的Response类的资源方法中饭后参数化的类型。 因为类型消除,使得此种情况下Jersey运行环境需要特殊的处理来决定为泛型类型选择相应的MessageBodyWriter

对于使用Jersey的JAX-RS的开发者而言有很多可供选择的技术,下面我将详细的讨论其中的几个。

让我们看一个简单的域模型:Employee。

package net.javasight;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "employee")
public class EmployeeBean {
        private Long id;
        private String firstName;
        private String lastName;

        public EmployeeBean() {
                // required for JAXB
        }

        public EmployeeBean(Long id, String firstName, String lastName) {
                this.id = id;
                this.firstName = firstName;
                this.lastName = lastName;
        }

        public Long getId() {
                return id;
        }

        public void setId(Long id) {
                this.id = id;
        }

        public String getFirstName() {
                return firstName;
        }

        public void setFirstName(String firstName) {
                this.firstName = firstName;
        }

        public String getLastName() {
                return lastName;
        }

        public void setLastName(String lastName) {
                this.lastName = lastName;
        }
}

employee资源的一个实现如下:

@Path("/employees")
public class EmployeeResource {
        @GET
        public Collection<EmployeeBean> getEmployees() {
                EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
                return Collections.singletonList(emp);
        }
}

在这种情况下,我们从资源方法中返回了EmployeeBean的Collection集合。如果你从如下地址(http://localhost:9998/employees)访问该资源,将会产生如下的XML输出:

<? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ?>
<employeeBeans>
        <employee>
                <firstName>John</firstName>
                <id>1</id>
                <lastName>Doe</lastName>
        </employee>
</employeeBeans>

我希望看到employee在<employees>标签中,而不是在<employeeBeans>中。这里需要做一些额外的配置来产生该格式。那么让我们修改下Employee的POJO来包含该集合。

package net.javasight;

import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Employees {
        private List<EmployeeBean> employee;

        public Employees(List<EmployeeBean> employee) {
                this.employee = employee;
        }

        public Employees() {
                // required for JAXB
        }

        public List<EmployeeBean> getEmployee() {
                return employee;
        }

        public void setEmployee(List<EmployeeBean> employee) {
                this.employee = employee;
        }
}

让我们完成EmployeeResource的方法来使用自定义的POJO来产生相关的XML。

package net.javasight;

@Path("/employees")
public class EmployeeResource {
        @GET
        @Path("test1")
        public Employees getEmployees1() {
                EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
                Employees employees = new Employees(Collections.singletonList(emp));
                return employees;
        }

        @GET
        @Path("test2")
        public Response getEmployees2() {
                EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
                Employees employees = new Employees(Collections.singletonList(emp));
                return Response.ok(employees).build();
        }
}

现在访问http://localhost:9988/employees/test1或http://localhost:9998/employees/test2应该会产生如下的XML:

<? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ?>
<employees>
        <employee>
                <firstName>John</firstName>
                <id>1</id>
                <lastName>Doe</lastName>
        </employee>
</employees>

但是,难道我们需要这样糟糕的方法来产生该输出吗?已经不需要了,在Jersey1.2发布后有了一些改善。可以启用资源配置的FEATURE_XMLROOTELEMENT_PROCESSING 特性后,应当可以自动的产生该输出。因此,访问http://localhost:9998/employees/test1应该产生这种的格式XML。该属性默认是禁用的。

现在让我们看看在JAX-RS Response中返回参数化类型会遇到的实际的问题。我在EmployeeResource中添加了另一个方法。

        @GET
        @Path("test3")
        @Produces(MediaType.APPLICATION_XML)
        public Response getEmployees3() {
                EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
                List<EmployeeBean> list = new ArrayList<EmployeeBean>();
                list.add(emp);
                return Response.ok(list).build();
        }

现在通过http://localhost:9998/employees/test3访问该方法应该会产生如下异常。我相信该异常在Jersey/JAX-RS用户中应该很常见。

SEVERE: A message body writer for Java class java.util.ArrayList, and Java type class java.util.ArrayList, and MIME media type application/xml was not found
Jul 24 , 2010 11 : 58 : 55 PM com.sun.jersey.spi.container.ContainerResponse write
SEVERE: The registered message body writers compatible with the MIME media type are:
application/xml ->
com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$App
com.sun.jersey.core.impl.provider.entity.DocumentProvider
com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter
com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$App
com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App
*/* ->
com.sun.jersey.core.impl.provider.entity.FormProvider
com.sun.jersey.server.impl.template.ViewableMessageBodyWriter
com.sun.jersey.core.impl.provider.entity.StringProvider
com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
com.sun.jersey.core.impl.provider.entity.FileProvider
com.sun.jersey.core.impl.provider.entity.InputStreamProvider
com.sun.jersey.core.impl.provider.entity.DataSourceProvider
com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
com.sun.jersey.core.impl.provider.entity.ReaderProvider
com.sun.jersey.core.impl.provider.entity.DocumentProvider
com.sun.jersey.core.impl.provider.entity.StreamingOutputProvider
com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter
com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
Jul 24 , 2010 11 : 58 : 55 PM com.sun.jersey.spi.container.ContainerResponse traceException
SEVERE: Mapped exception to response: 500 (Internal Server Error)
javax.ws.rs.WebApplicationException

要修复该问题,我们需要告诉JAX-RS运行时响应实体的类型,在我们的情况下是Employee的集合。JAX-RS APIGenericEntity正是用于该目的。GenericEntity可以用于反映响应实体中的泛型类型。 EmployeeResource方法被更新在返回集合类型时使用GenericEntity。

        @GET
        @Path("test4")
        @Produces(MediaType.APPLICATION_XML)
        public Response getEmployees4() {
                EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
                List<EmployeeBean> list = new ArrayList<EmployeeBean>();
                list.add(emp);
                GenericEntity entity = new GenericEntity<List<EmployeeBean>>(list) {
                };
                return Response.ok(entity).build();
        }

访问http://localhost:9998/employees/test4应该会产生我们所需要的输出。

除了这种方式,Jersey1.2引入了新的APIJResponse来支持这个,并且更优。JResponse是类型安全的Response,它保留了响应实体的类型信息,因此不需要额外的使用GenericEntity。

使用JResponse更新后的方法如下:

        @GET
        @Path("test5")
        @Produces(MediaType.APPLICATION_XML)
        public JResponse<List<EmployeeBean>> getEmployees5() {
                EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
                List<EmployeeBean> list = new ArrayList<EmployeeBean>();
                list.add(emp);
                return JResponse.ok(list).build();
        }

访问http://localhost:9998/employees/test5应该会产生我们所需要的输出.

这两种方式实现起来都很容易。主要的不同是GenericEntity是JAX-RS API然而JResponse是Jersey API,它可能不能在其他的JAX-RS实现中使用,因此不具备移植性。 如果你仅仅使用Jersey,那么JResponse是更好的选择,因为它是类型安全的并与Response兼容。

下面是完整的服务端代码:

package net.javasight;

import com.sun.grizzly.http.SelectorThread;
import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory;
import com.sun.jersey.core.util.FeaturesAndProperties;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

public class Main {
        private static int getPort(int defaultPort) {
                String port = System.getenv("JERSEY_HTTP_PORT");
                if (null != port) {
                        try {
                                return Integer.parseInt(port);
                        } catch (NumberFormatException e) {
                        }
                }
                return defaultPort;
        }

        private static URI getBaseURI() {
                return UriBuilder.fromUri("http://localhost/").port(getPort(9998))
                                .build();
        }

        public static final URI BASE_URI = getBaseURI();

        protected static SelectorThread startServer() throws IOException {
                final Map<String, String> initParams = new HashMap<String, String>();
                initParams.put("com.sun.jersey.config.property.packages",
                                "com.employee.resources");
                initParams.put(FeaturesAndProperties.FEATURE_XMLROOTELEMENT_PROCESSING,
                                "true");
                System.out.println("Starting grizzly...");
                SelectorThread threadSelector = GrizzlyWebContainerFactory.create(
                                BASE_URI, initParams);
                return threadSelector;
        }

        public static void main(String[] args) throws IOException {
                SelectorThread threadSelector = startServer();
                System.out
                                .println(String
                                                .format(
                                                                "Jersey app started with WADL available at "
                                                                                + "%sapplication.wadl\nTry out %shelloworld\nHit enter to stop it...",
                                                                BASE_URI, BASE_URI));
                System.in.read();
                threadSelector.stopEndpoint();
        }
}

 

以上内容转自http://javasight.net/2011/05/generified-collections-in-jersey/,感谢作者,但是作者只提供了服务端的泛型处理,并没有提供客户端如何使用泛型。

个人解决客户端接受泛型方法:

 

服务端代码:

@GET
    @Path("test4")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getEmployees4() {
            EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
            EmployeeBean emp1 = new EmployeeBean(2L, "zhangsan", "lisi");
            List<EmployeeBean> list = new ArrayList<EmployeeBean>();
            list.add(emp);
            list.add(emp1);
            GenericEntity<List<EmployeeBean>> entity = new GenericEntity<List<EmployeeBean>>(list) {
            };
            return Response.ok(entity).build();
    }

 

客户端代码:

GenericType<List<EmployeeBean>> gt = new GenericType<List<EmployeeBean>>(){};
  Object obj = wr.path("/message/test4").get(gt);
  System.out.println(obj);

加红字体是rest服务地址 wr是webResource对象。

如果有疑问的,可以留言给我,第一次发博文,不当之处,请多关照。