jersey put 服务
这是Project Student的一部分。 其他职位包括带有Jersey的Webservice Client , 业务层和带有Spring Data的持久性 。
RESTful Web应用程序洋葱的第二层是Web服务服务器。 它应该是一个薄层,用于包装对业务层的调用,但不对其自身进行大量处理。 这篇文章有很多代码,但主要是测试类。
设计决策
泽西岛 -我将泽西岛用于REST服务器。 我考虑了替代方案-Spring MVC , Netty等,但出于与客户相同的原因,决定选择Jersey。 它轻巧,不会限制开发人员。
依赖注入 –我需要依赖注入,这意味着我需要确定一个框架:Spring,EJB3,Guice等。我已经知道我将在持久层中使用Spring Data ,因此使用它很容易春天的框架。 我仍然会谨慎地最小化该框架上的任何依赖关系(ha!),以实现最大的灵活性。
局限性
球衣 –我不知道球衣将如何处理高负荷。 这是REST服务器必须是业务层的薄包装的关键原因-如果有必要,更改库将相对容易。
用户权限 –没有尝试将对某些方法的访问限制为特定用户或主机。 这应该由业务层来处理,而安全性异常将由REST服务器转换为FORBIDDEN状态代码。
泽西REST服务器
REST API是我们早期的设计文档之一。 对于服务器,这意味着我们从REST服务器开始而不是从业务层API开始实现该层。 实际上,REST服务器在业务层API中定义了必要的方法。
与标准的REST CRUD API有一个小的偏差:对象是使用POST而不是PUT创建的,因为后者的语义是完全按照提供的方式创建了对象。 我们无法做到这一点–出于安全原因,我们从不公开内部ID,也不得接受用户定义的UUID。 这意味着我们将违反REST API合同,因此我们改用POST。
还有一个小作弊:CRUD合同仅需要具有创建或更新对象的能力。 这意味着我们只需给出路径就可以找出所需的操作–我们不需要添加特定的“操作”字段。 随着我们将实现扩展到不仅仅包括CRUD动作,这可能会改变。
继续执行代码...
@Service
@Path("/course")
public class CourseResource extends AbstractResource {
private static final Logger log = Logger.getLogger(CourseResource.class);
private static final Course[] EMPTY_COURSE_ARRAY = new Course[0];
@Context
UriInfo uriInfo;
@Context
Request request;
@Resource
private CourseService service;
/**
* Default constructor.
*/
public CourseResource() {
}
/**
* Unit test constructor.
*
* @param service
*/
CourseResource(CourseService service) {
this.service = service;
}
/**
* Get all Courses.
*
* @return
*/
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public Response findAllCourses() {
log.debug("CourseResource: findAllCourses()");
Response response = null;
try {
List<Course> courses = service.findAllCourses();
List<Course> results = new ArrayList<Course>(courses.size());
for (Course course : courses) {
results.add(scrubCourse(course));
}
response = Response.ok(results.toArray(EMPTY_COURSE_ARRAY)).build();
} catch (Exception e) {
if (!(e instanceof UnitTestException)) {
log.info("unhandled exception", e);
}
response = Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
return response;
}
/**
* Create a Course.
*
* @param req
* @return
*/
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public Response createCourse(Name req) {
log.debug("CourseResource: createCourse()");
final String name = req.getName();
if ((name == null) || name.isEmpty()) {
return Response.status(Status.BAD_REQUEST).entity("'name' is required").build();
}
Response response = null;
try {
Course course = service.createCourse(name);
if (course == null) {
response = Response.status(Status.INTERNAL_SERVER_ERROR).build();
} else {
response = Response.created(URI.create(course.getUuid())).entity(scrubCourse(course)).build();
}
} catch (Exception e) {
if (!(e instanceof UnitTestException)) {
log.info("unhandled exception", e);
}
response = Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
return response;
}
/**
* Get a specific Course.
*
* @param uuid
* @return
*/
@Path("/{courseId}")
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public Response getCourse(@PathParam("courseId") String id) {
log.debug("CourseResource: getCourse()");
Response response = null;
try {
Course course = service.findCourseByUuid(id);
response = Response.ok(scrubCourse(course)).build();
} catch (ObjectNotFoundException e) {
response = Response.status(Status.NOT_FOUND).build();
} catch (Exception e) {
if (!e instanceof UnitTestException)) {
log.info("unhandled exception", e);
}
response = Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
return response;
}
/**
* Update a Course.
*
* FIXME: what about uniqueness violations?
*
* @param id
* @param req
* @return
*/
@Path("/{courseId}")
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public Response updateCourse(@PathParam("courseId") String id, Name req) {
log.debug("CourseResource: updateCourse()");
final String name = req.getName();
if ((name == null) || name.isEmpty()) {
return Response.status(Status.BAD_REQUEST).entity("'name' is required").build();
}
Response response = null;
try {
final Course course = service.findCourseByUuid(id);
final Course updatedCourse = service.updateCourse(course, name);
response = Response.ok(scrubCourse(updatedCourse)).build();
} catch (ObjectNotFoundException exception) {
response = Response.status(Status.NOT_FOUND).build();
} catch (Exception e) {
if (!(e instanceof UnitTestException)) {
log.info("unhandled exception", e);
}
response = Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
return response;
}
/**
* Delete a Course.
*
* @param id
* @return
*/
@Path("/{courseId}")
@DELETE
public Response deleteCourse(@PathParam("courseId") String id) {
log.debug("CourseResource: deleteCourse()");
Response response = null;
try {
service.deleteCourse(id);
response = Response.noContent().build();
} catch (ObjectNotFoundException exception) {
response = Response.noContent().build();
} catch (Exception e) {
if (!(e instanceof UnitTestException)) {
log.info("unhandled exception", e);
}
response = Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
return response;
}
}
该实现告诉我们,我们需要三件事:
- 服务API(CourseService)
- 请求参数类(名称)
- 洗涤器(scrubCourse)
我没有显示完整的日志记录。 必须清除请求参数,以避免日志污染。 。 作为一个简单的示例,请考虑使用写入SQL数据库的记录器,以简化分析。 这个记录器的一个简单的实现-不使用位置参数-将允许通过精心设计的请求参数进行SQL注入!
OWASP ESAPI包含可用于日志清理的方法。 我没有包含在这里,因为设置起来有些麻烦。 (应该很快就会在签入代码中。)
为什么要登录到数据库? 一种好的做法是记录到达服务器层的所有未处理的异常-您永远都不想依靠用户来报告问题,而且写入日志文件的错误很容易被忽略。 相反,使用简单工具可以轻松检查写入数据库的报告。
发生未处理的异常时,高级开发人员甚至可以创建新的错误报告。 在这种情况下,至关重要的是维护一个单独的异常数据库,以避免提交重复的条目和使开发人员不知所措。 (数据库可以包含每个异常的详细信息,但错误报告系统每个异常类+堆栈跟踪仅应有一个错误报告。)
服务API
CRUD操作的服务API很简单。
public interface CourseService {
List<Course> findAllCourses();
Course findCourseById(Integer id);
Course findCourseByUuid(String uuid);
Course createCourse(String name);
Course updateCourse(Course course, String name);
void deleteCourse(String uuid);
}
该API还包含一个ObjectNotFoundException。 (这应该扩展为包括找不到的对象的类型。)
public class ObjectNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final String uuid;
public ObjectNotFoundException(String uuid) {
super("object not found: [" + uuid + "]");
this.uuid = uuid;
}
public String getUuid() {
return uuid;
}
}
如上所述,我们最终还需要一个UnauthorizedOperationException。
请求参数
请求参数是封装了POST负载的简单POJO。
@XmlRootElement
public class Name {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
学生和教师也需要电子邮件地址。
@XmlRootElement
public class NameAndEmailAddress {
private String name;
private String emailAddress;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}
最终的应用程序将具有大量的请求参数类。
洗涤塔
洗涤塔具有三个目的。 首先,它删除了不应提供给客户端的敏感信息,例如内部数据库标识符。
其次,它可以防止由于引入集合而导致大量的数据库转储。 例如,一个学生应包括当前部分的列表,但每个部分都有已注册的学生和教师的列表。 这些学生和教师中的每一个都有自己的当前部分列表。 进行泡沫,冲洗,重复,最终将整个数据库转储以响应单个查询。
解决方案是仅包含有关每个可以独立查询的对象的浅层信息。 例如,一个学生将拥有当前部分的列表,但是这些部分将仅包含UUID和名称。 一条很好的经验法则是,清理后的集合应完全包含将在下拉列表和表示表中使用的信息,仅此而已。 演示列表可以包含链接(或AJAX操作),以根据需要提取其他信息。
最后,这是执行HTML编码和清理的好地方。 应该清除返回的值,以防止跨站点脚本(CSS)攻击 。
public abstract class AbstractResource {
/**
* Scrub 'course' object.
*
* FIXME add HTML scrubbing and encoding for string values!
*/
public Course scrubCourse(final Course dirty) {
final Course clean = new Course();
clean.setUuid(dirty.getUuid());
clean.setName(dirty.getName());
// clean.setSelf("resource/" + dirty.getUuid());
return clean;
}
}
配置类
我们有两个配置类。 第一个始终由服务器使用,第二个仅在集成测试期间由服务器使用。 后者的配置(和引用的类)位于集成测试源树中。
我更喜欢使用配置类(在Spring 3.0中引入),因为它们提供了最大的灵活性-例如,我可以根据运行应用程序或环境变量的用户有条件地定义bean-并允许我仍然包括标准配置文件。
@Configuration
@ComponentScan(basePackages = { "com.invariantproperties.sandbox.student.webservice.server.rest" })
@ImportResource({ "classpath:applicationContext-rest.xml" })
// @PropertySource("classpath:application.properties")
public class RestApplicationContext {
@Resource
private Environment environment;
}
Spring 3.1引入了配置文件。 它们可以工作-但是我正在使用的具有弹簧意识的jersey servlet似乎无法正确设置活动概要文件。
@Configuration
//@Profile("test")
public class RestApplicationContextTest {
@Bean
StudentService studentService() {
return new DummyStudentService();
}
}
web.xml
现在,我们有足够的资源来实现我们的Web服务器。 使用的servlet是启用了spring的Jersey servlet,它使用contextClass参数中给出的配置类。 (也可以使用配置文件,但不能使用配置类和文件的组合。)
该Servlet还包含spring.profiles.active的定义。 目的是通过spring 3.1 @Profile注释有条件地在RestApplicationContextTest中包含定义,但我无法使其正常工作。 我把它留给以后参考。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>Project Student Webservice</display-name>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
com.invariantproperties.sandbox.student.webservice.server.config.RestApplicationContext
com.invariantproperties.sandbox.student.webservice.server.config.RestApplicationContextTest
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>REST dispatcher</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>spring.profiles.active</param-name>
<param-value>test</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>REST dispatcher</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
单元测试
单元测试很简单。
public class CourseResourceTest {
private Course physics = new Course();
private Course mechanics = new Course();
@Before
public void init() {
physics.setId(1);
physics.setName("physics");
physics.setUuid(UUID.randomUUID().toString());
mechanics.setId(1);
mechanics.setName("mechanics");
mechanics.setUuid(UUID.randomUUID().toString());
}
@Test
public void testFindAllCourses() {
final List<Course> expected = Arrays.asList(physics);
final CourseService service = Mockito.mock(CourseService.class);
when(service.findAllCourses()).thenReturn(expected);
final CourseResource resource = new CourseResource(service);
final Response response = resource.findAllCourses();
assertEquals(200, response.getStatus());
final Course[] actual = (Course[]) response.getEntity();
assertEquals(expected.size(), actual.length);
assertNull(actual[0].getId());
assertEquals(expected.get(0).getName(), actual[0].getName());
assertEquals(expected.get(0).getUuid(), actual[0].getUuid());
}
@Test
public void testFindAllCoursesEmpty() {
final List<Course> expected = new ArrayList<>();
final CourseService service = Mockito.mock(CourseService.class);
when(service.findAllCourses()).thenReturn(expected);
final CourseResource resource = new CourseResource(service);
final Response response = resource.findAllCourses();
assertEquals(200, response.getStatus());
final Course[] actual = (Course[]) response.getEntity();
assertEquals(0, actual.length);
}
@Test
public void testFindAllCoursesFailure() {
final CourseService service = Mockito.mock(CourseService.class);
when(service.findAllCourses()).thenThrow(
new UnitTestException();
final CourseResource resource = new CourseResource(service);
final Response response = resource.findAllCourses();
assertEquals(500, response.getStatus());
}
@Test
public void testGetCourse() {
final Course expected = physics;
final CourseService service = Mockito.mock(CourseService.class);
when(service.findCourseByUuid(expected.getUuid())).thenReturn(expected);
final CourseResource resource = new CourseResource(service);
final Response response = resource.getCourse(expected.getUuid());
assertEquals(200, response.getStatus());
final Course actual = (Course) response.getEntity();
assertNull(actual.getId());
assertEquals(expected.getName(), actual.getName());
assertEquals(expected.getUuid(), actual.getUuid());
}
@Test
public void testGetCourseMissing() {
final CourseService service = Mockito.mock(CourseService.class);
when(service.findCourseByUuid(physics.getUuid())).thenThrow(
new ObjectNotFoundException(physics.getUuid()));
final CourseResource resource = new CourseResource(service);
final Response response = resource.getCourse(physics.getUuid());
assertEquals(404, response.getStatus());
}
@Test
public void testGetCourseFailure() {
final CourseService service = Mockito.mock(CourseService.class);
when(service.findCourseByUuid(physics.getUuid())).thenThrow(
new UnitTestException();
final CourseResource resource = new CourseResource(service);
final Response response = resource.getCourse(physics.getUuid());
assertEquals(500, response.getStatus());
}
@Test
public void testCreateCourse() {
final Course expected = physics;
final Name name = new Name();
name.setName(expected.getName());
final CourseService service = Mockito.mock(CourseService.class);
when(service.createCourse(name.getName())).thenReturn(expected);
final CourseResource resource = new CourseResource(service);
final Response response = resource.createCourse(name);
assertEquals(201, response.getStatus());
final Course actual = (Course) response.getEntity();
assertNull(actual.getId());
assertEquals(expected.getName(), actual.getName());
}
@Test
public void testCreateCourseBlankName() {
final Course expected = physics;
final Name name = new Name();
final CourseService service = Mockito.mock(CourseService.class);
final CourseResource resource = new CourseResource(service);
final Response response = resource.createCourse(name);
assertEquals(400, response.getStatus());
}
/**
* Test handling when the course can't be created for some reason. For now
* the service layer just returns a null value - it should throw an
* appropriate exception.
*/
@Test
public void testCreateCourseProblem() {
final Course expected = physics;
final Name name = new Name();
name.setName(expected.getName());
final CourseService service = Mockito.mock(CourseService.class);
when(service.createCourse(name.getName())).thenReturn(null);
final CourseResource resource = new CourseResource(service);
final Response response = resource.createCourse(name);
assertEquals(500, response.getStatus());
}
@Test
public void testCreateCourseFailure() {
final Course expected = physics;
final Name name = new Name();
name.setName(expected.getName());
final CourseService service = Mockito.mock(CourseService.class);
when(service.createCourse(name.getName())).thenThrow(
new UnitTestException();
final CourseResource resource = new CourseResource(service);
final Response response = resource.createCourse(name);
assertEquals(500, response.getStatus());
}
@Test
public void testUpdateCourse() {
final Course expected = physics;
final Name name = new Name();
name.setName(mechanics.getName());
final Course updated = new Course();
updated.setId(expected.getId());
updated.setName(mechanics.getName());
updated.setUuid(expected.getUuid());
final CourseService service = Mockito.mock(CourseService.class);
when(service.findCourseByUuid(expected.getUuid())).thenReturn(expected);
when(service.updateCourse(expected, name.getName()))
.thenReturn(updated);
final CourseResource resource = new CourseResource(service);
final Response response = resource.updateCourse(expected.getUuid(),
name);
assertEquals(200, response.getStatus());
final Course actual = (Course) response.getEntity();
assertNull(actual.getId());
assertEquals(mechanics.getName(), actual.getName());
assertEquals(expected.getUuid(), actual.getUuid());
}
/**
* Test handling when the course can't be updated for some reason. For now
* the service layer just returns a null value - it should throw an
* appropriate exception.
*/
@Test
public void testUpdateCourseProblem() {
final Course expected = physics;
final Name name = new Name();
name.setName(expected.getName());
final CourseService service = Mockito.mock(CourseService.class);
when(service.updateCourse(expected, name.getName())).thenReturn(null);
final CourseResource resource = new CourseResource(service);
final Response response = resource.createCourse(name);
assertEquals(500, response.getStatus());
}
@Test
public void testUpdateCourseFailure() {
final Course expected = physics;
final Name name = new Name();
name.setName(expected.getName());
final CourseService service = Mockito.mock(CourseService.class);
when(service.updateCourse(expected, name.getName())).thenThrow(
new UnitTestException();
final CourseResource resource = new CourseResource(service);
final Response response = resource.createCourse(name);
assertEquals(500, response.getStatus());
}
@Test
public void testDeleteCourse() {
final Course expected = physics;
final CourseService service = Mockito.mock(CourseService.class);
doNothing().when(service).deleteCourse(expected.getUuid());
final CourseResource resource = new CourseResource(service);
final Response response = resource.deleteCourse(expected.getUuid());
assertEquals(204, response.getStatus());
}
@Test
public void testDeleteCourseMissing() {
final Course expected = physics;
final Name name = new Name();
name.setName(expected.getName());
final CourseService service = Mockito.mock(CourseService.class);
doThrow(new ObjectNotFoundException(expected.getUuid())).when(service)
.deleteCourse(expected.getUuid());
final CourseResource resource = new CourseResource(service);
final Response response = resource.deleteCourse(expected.getUuid());
assertEquals(204, response.getStatus());
}
@Test
public void testDeleteCourseFailure() {
final Course expected = physics;
final CourseService service = Mockito.mock(CourseService.class);
doThrow(new UnitTestException()).when(service)
.deleteCourse(expected.getUuid());
final CourseResource resource = new CourseResource(service);
final Response response = resource.deleteCourse(expected.getUuid());
assertEquals(500, response.getStatus());
}
}
整合测试
问题: REST服务器集成测试是否应该使用实时数据库?
答:这是一个技巧问题。 我们都需要。
总体架构包含三个Maven模块。 我们前面介绍了student-ws-client,今天介绍了Student-ws-server。 每个都创建一个.jar文件。 第三个模块-student-ws-webapp-创建实际的.war文件。 学生-ws-服务器模块的集成测试应使用虚拟服务层,而学生-ws-webapp模块的集成测试应使用完整堆栈。
我们从集成测试开始,该集成测试反映了客户端模块中的单元测试。
public class CourseRestServerIntegrationTest {
CourseRestClient client = new CourseRestClientImpl(
"http://localhost:8080/rest/course/");
@Test
public void testGetAll() throws IOException {
Course[] courses = client.getAllCourses();
assertNotNull(courses);
}
@Test(expected = ObjectNotFoundException.class)
public void testUnknownCourse() throws IOException {
client.getCourse("missing");
}
@Test
public void testLifecycle() throws IOException {
final String physicsName = "Physics 201";
final Course expected = client.createCourse(physicsName);
assertEquals(physicsName, expected.getName());
final Course actual1 = client.getCourse(expected.getUuid());
assertEquals(physicsName, actual1.getName());
final Course[] courses = client.getAllCourses();
assertTrue(courses.length > 0);
final String mechanicsName = "Newtonian Mechanics 201";
final Course actual2 = client.updateCourse(actual1.getUuid(),
mechanicsName);
assertEquals(mechanicsName, actual2.getName());
client.deleteCourse(actual1.getUuid());
try {
client.getCourse(expected.getUuid());
fail("should have thrown exception");
} catch (ObjectNotFoundException e) {
// do nothing
}
}
}
我们还需要一个虚拟服务类,该类可以实现足以支持我们的集成测试的功能。
public class DummyCourseService implements CourseService {
private Map cache = Collections.synchronizedMap(new HashMap<String, Course>());
public List<Course> findAllCourses() {
return new ArrayList(cache.values());
}
public Course findCourseById(Integer id) {
throw new ObjectNotFoundException(null);
}
public Course findCourseByUuid(String uuid) {
if (!cache.containsKey(uuid)) {
throw new ObjectNotFoundException(uuid);
}
return cache.get(uuid);
}
public Course createCourse(String name) {
Course course = new Course();
course.setUuid(UUID.randomUUID().toString());
course.setName(name);
cache.put(course.getUuid(), course);
return course;
}
public Course updateCourse(Course oldCourse, String name) {
if (!cache.containsKey(oldCourse.getUuid())) {
throw new ObjectNotFoundException(oldCourse.getUuid());
}
Course course = cache.get(oldCourse.getUuid());
course.setUuid(UUID.randomUUID().toString());
course.setName(name);
return course;
}
public void deleteCourse(String uuid) {
if (cache.containsKey(uuid)) {
cache.remove(uuid);
}
}
}
pom.xml
pom.xml文件应包含一个用于运行嵌入式码头或tomcat服务器的插件。 作为集成测试的一部分,高级用户可以旋转和拆除嵌入式服务器-请参阅更新。
<build>
<plugins>
<!-- Run the application using "mvn jetty:run" -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.16</version> <!-- ancient! -->
<configuration>
<!-- Log to the console. -->
<requestLog implementation="org.mortbay.jetty.NCSARequestLog">
<!-- This doesn't do anything for Jetty, but is a workaround for a
Maven bug that prevents the requestLog from being set. -->
<append>true</append>
</requestLog>
<webAppConfig>
<contextPath>/</contextPath>
<extraClasspath>${basedir}/target/test-classes/</extraClasspath>
</webAppConfig>
</configuration>
</plugin>
</plugins>
</build>
更新资料
经过更多研究之后,我进行了配置以在集成测试期间设置和拆除码头服务器。 此配置使用非标准端口,因此我们无需关闭同时运行的另一个码头或tomcat实例即可运行它。
<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">
<build>
<plugins>
<!-- Run the application using "mvn jetty:run" -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.1.0.v20131115</version>
<configuration>
<webApp>
<extraClasspath>${basedir}/target/test-classes/</extraClasspath>
</webApp>
<scanIntervalSeconds>10</scanIntervalSeconds>
<stopPort>18005</stopPort>
<stopKey>STOP</stopKey>
<systemProperties>
<systemProperty>
<name>jetty.port</name>
<value>18080</value>
</systemProperty>
</systemProperties>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<scanIntervalSeconds>0</scanIntervalSeconds>
<daemon>true</daemon>
</configuration>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
源代码
翻译自: https://www.javacodegeeks.com/2014/01/project-student-webservice-server-with-jersey.html
jersey put 服务