Dropwizard抽象资源设计

Dropwizard abstract resource design

我认为这是一个更笼统的 java 问题,但我会解释我正在尝试做的事情,希望有人能指出正确的方法;

我正在尝试创建一个通用摘要 class,我的所有资源都可以从中扩展。

摘要 class 具有标准内容的基本 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);
    }
}

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

我 运行 遇到问题的地方是我需要重载一个方法。以抽象class中的第一个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注解。

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

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

有什么我刚刚忽略的非常明显的东西吗?

需要一些帮助!

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

我建议在您的 AbstractResource 中使用通用方法,您可以将 @Context UriInfo uriInfo 传递给您的方法并在通用实用方法中解析其查询参数,或者通过 [=13= 使用矩阵参数之类的东西]

@Path("/{segment: .*}")
@GET
@Produces("application/json")
public Response getAll(@PathParam("segment") PathSegment segment)
...

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

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

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

我推荐使用泛型。

我们已经完成了一个类似但相当复杂的版本。一开始做对有点难,但我们有最大的代码可重用性(使用 Java)并且易于 read/contribute 编码。

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;
    }
    ...
}

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

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);
    }
}