Dropwizard抽象资源设计

时间:2016-12-30 22:48:51

标签: java design-patterns jax-rs abstract-class dropwizard

我认为这是一个更通用的java问题,但我会解释我正在尝试做什么,希望有人可以指出正确的方法;

我正在尝试创建一个我的所有资源都可以扩展的通用抽象类。

抽象类具有标准内容的基本CRUD实现

@Produces("application/vnd.api+json")
@Consumes("application/vnd.api+json")
public abstract class AbstractResource {

    static final Logger LOGGER = LoggerFactory.getLogger(AbstractResource.class);

    AbstractRepository repository;

    AbstractResource(AbstractRepository repository) {
        this.repository = repository;
    }

    @GET
    public Response getAll(@Auth User user, @QueryParam("query") String query) {
        String result = query != null ? repository.getByQuery(query) : repository.getAll();
        return Response.status(Response.Status.OK).entity(result).build();
    }

    @GET
    @Path("/{id}")
    public Response getById(@Auth User user, @PathParam("id") String id) {
        String result = repository.getById(id);
        return Response.status(Response.Status.OK).entity(result).build();
    }

    @POST
    public Response save(@Auth User user, String payload) {
        String result = repository.save(payload);
        return Response.status(Response.Status.OK).entity(result).build();
    }

    @PATCH
    @Path("/{id}")
    public Response update(@Auth User user, @PathParam("id") String id, String payload) {
        String result = repository.update(payload);
        return Response.status(Response.Status.OK).entity(result).build();
    }

    @DELETE
    @Path("/{id}")
    public Response delete(@Auth User user, @PathParam("id") String id) {
        repository.delete(id);
        return Response.status(Response.Status.NO_CONTENT).build();
    }

}

我可以毫不费力地使用它来完成

@Path("/movies")
public class MovieResource extends AbstractResource {
    public MovieResource(MovieRepository repository) {
        super(repository);
    }
}

我现在可以访问所有方法并根据需要覆盖。

我遇到问题的地方是我需要重载一个方法。以抽象类中的第一个getAll方法为例,我想仅在Movie.class

中更改参数
@Path("/movies")
public class MovieResource extends AbstractResource {

    public MovieResource(MovieRepository repository) {
        super(repository);
    }

    @GET
    public Response getAll(@Auth User user, @QueryParam("query") String query, @QueryParam("limit") String limit, @QueryParam("page") String page) {
        String result = repository.getPaginated(limit, page);
        return Response.status(Response.Status.OK).entity(result).build();
    }

}

因此getAll方法只在Movie.class中有一组不同的参数。这导致泽西岛爆发

[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public javax.ws.rs.core.Response space.cuttlefish.domain.resources.MovieResource.getAll(space.cuttlefish.domain.model.User,java.lang.String,java.lang.String,java.lang.String) and public javax.ws.rs.core.Response space.cuttlefish.domain.resources.AbstractResource.getAll(space.cuttlefish.domain.model.User,java.lang.String) at matching regular expression /movies. These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='org.glassfish.jersey.server.model.RuntimeResource@6a1ef65c']

因为摘要的原始getAll方法已经有@GET注释。

那么,我该如何解决这个问题呢?

我是否从抽象类中删除所有注释,然后必须覆盖并重新添加每个资源中的注释?这看起来很混乱,容易出错......这里必须有更好的解决方案吗?

我忽略了一些令人眼花缭乱的明显事物吗?

会喜欢一些帮助!

2 个答案:

答案 0 :(得分:1)

我建议使用Generics。

我们已经完成了类似但相当复杂的版本。在开始时做起来有点困难,但我们拥有最大的代码可重用性(使用Java)并且易于阅读/贡献代码。

public abstract class AbstractResource<T extends AbstractObject, K extends AbstractObjectDto> {

    static final Logger LOGGER = LoggerFactory.getLogger(AbstractResource.class);

    AbstractRepository<T> repository;
    // We have used modelmapper library to automatically convert DTO objects to database objects. But you can come up with your own solution for that. I.E implementing conversion logic on each DTO and database classes.
    ModelMapper modelMapper = new ModelMapper(); 

    // With Java Generics, one cannot access the class type directly by simply calling 'K.class'. So you need to pass the class types explicitly as well. That is if you're using modelmapper.
    private final Class<T> objectClass;
    private final Class<K> objectDtoClass;

    AbstractResource(AbstractRepository<T> repository, Class<T> objectClass, Class<K> objectDtoClass) {
        this.repository = repository;
        this.objectClass = objectClass;
        this.objectDtoClass = objectDtoClass;
    }

    ...

    @POST
    public K save(@Auth User user, @Valid K payload) {
        T databaseObject = modelmapper.map(payload, objectClass);
        T result = repository.save(databaseObject);
        K resultDto = modelMapper.map(result, objectDtoClass);
        retun resultDto;
    }
    ...
}

然后,您需要为每个对象类型创建一个包含savegetPaginated等必要方法的存储库类,覆盖AbstractRepository。当然,Movie应该扩展AbstractObject类,而MovieDto应该扩展AbstractObjectDto类。

public class MovieRepository extends AbstractRepository<Movie> {
    ....
    Movie save(Movie movie) {...}
}

其余的就像这样简单:

@Path("/movies")
public class MovieResource extends AbstractResource<Movie, MovieDto> {

    public MovieResource(MovieRepository repository) {
        super(repository, Movie.class, MovieDto.class);
    }
}

答案 1 :(得分:0)

它失败的原因是在您的示例中,多个方法映射到相同的URL路径。但是如果你只是覆盖一个方法,泽西不会抱怨。

我建议你在AbstractResource中使用泛型方法,你可以将@Context UriInfo uriInfo传递给你的方法,并在泛型实用程序方法中解析它的查询参数,或者通过

使用矩阵参数。
@Path("/{segment: .*}")
@GET
@Produces("application/json")
public Response getAll(@PathParam("segment") PathSegment segment)
...

并通过通用默认方法或两者的组合再次解析它们。

通过这种方式,您可以在许多情况下默认使用公共端点,或者执行自定义预处理并委托常规解析方法来处理典型用例。

如果我找到了你想要的东西在下面的项目中尝试了:https://github.com/researchgate/restler (免责声明:我是那里的贡献者)