简体   繁体   English

Dropwizard抽象资源设计

[英]Dropwizard abstract resource design

I think this is a more generic java question, but I'll explain what I'm trying to do and hopefully someone can point me the right way; 我认为这是一个更通用的Java问题,但是我将解释我正在尝试做的事情,并希望有人可以为我指出正确的方法;

I'm trying to create a generic abstract class that all my resources can extend from. 我试图创建一个通用的抽象类,我所有的资源都可以从中扩展。

The abstract class has basic CRUD implementations for the standard stuff 抽象类具有用于标准内容的基本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();
    }

}

I can use this without a problem simply doing 我可以简单地使用它

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

and I can now access all the methods and override as required. 现在,我可以访问所有方法并根据需要进行覆盖。

Where I run into problems is when I need to overload a method. 我遇到问题的地方是何时需要重载方法。 Take the first getAll method from the abstract class as example, I want to change the parameters in only the Movie.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();
    }

}

So the getAll method has a different set of parameters in just the Movie.class . 因此, getAll方法在Movie.class具有一组不同的参数。 This causes Jersey to blow up with 这导致泽西炸毁

[[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']

Because the original getAll method of the abstract already has the @GET annotation. 因为摘要的原始getAll方法已经具有@GET批注。

So, how do I go about solving this? 那么,我该如何解决呢?

Do I remove all the annotations from the abstract class, and then have to override and re-add the annotations in each resource? 我是否要从抽象类中删除所有注释,然后必须重写并在每个资源中重新添加注释? That just seems messy and prone to error... There must be a better solution here? 这似乎很混乱并且容易出错...这里必须有更好的解决方案吗?

Is there something blindingly obvious I've just overlooked? 有什么东西我只是被我忽略了吗?

Would love some help! 希望有帮助!

I recommend using Generics. 我建议使用泛型。

We have accomplished a similar but rather complex version of this. 我们已经完成了与此类似但相当复杂的版本。 It was a bit hard to get it right in the beginning, but we had maximum code reusability (with Java) and easy to read/contribute code. 一开始很难做到这一点,但是我们拥有最大的代码可重用性(使用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;
    }
    ...
}

Then you need to create a repository class that has the necessary methods like save , getPaginated etc. for each object type, overriding AbstractRepository . 然后,您需要为每个对象类型创建一个存储库类,该类具有必需的方法,如savegetPaginated等,从而覆盖AbstractRepository And of course, Movie should extend AbstractObject class, and MovieDto should extend AbstractObjectDto class. 当然, Movie应该扩展AbstractObject类,而MovieDto应该扩展AbstractObjectDto类。

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

And the rest is as simple as this: 其余的就是这样简单:

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

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

The reason why it fails for you is that in your example, multiple methods map to the same URL path. 它对您失败的原因是在您的示例中,多个方法映射到相同的URL路径。 But if you just override a method Jersey won't complain. 但是,如果您只是重写方法,Jersey不会抱怨。

I would recommend to have generic methods in your AbstractResource, where either you pass @Context UriInfo uriInfo to your method and parse its query params in a generic utility method, or use something like matrix parameters via 我建议在AbstractResource中使用通用方法,在该方法中,您可以将@Context UriInfo uriInfo传递给您的方法,并在通用实用程序方法中解析其查询参数,或者通过

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

and parse them again via a generic default method, or combination of both. 并通过通用默认方法或两者的组合再次解析它们。

In such a way, you can default to common endpoint in many cases, or do custom preprocessing and delegate to common parsing methods for typical use cases. 这样,在许多情况下,您可以默认为公共端点,或者对典型的用例进行自定义预处理并委托给常见的解析方法。

If I got you right something like you wanted was attempted in the following project: https://github.com/researchgate/restler (Disclaimer: I'm a contributor there) 如果我做对了,则在以下项目中尝试了您想要的操作: https : //github.com/researchgate/restler (免责声明:我是那里的贡献者)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM