2016-12-30 7 views
0

Я думаю, что это более общий вопрос Java, но я объясню, что я пытаюсь сделать, и, надеюсь, кто-то может указать мне правильный путь;Дизайн абстрактных ресурсов Dropwizard

Я пытаюсь создать общий абстрактный класс, из которого могут простираться все мои ресурсы.

Абстрактный класс имеет базовые реализации 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='[email protected]'] 

Поскольку исходный getAll метод реферата уже имеет @GET аннотацию.

Итак, как мне решить это?

Удалить все аннотации из абстрактного класса, а затем переопределить и повторно добавить аннотации в каждый ресурс? Это просто кажется грязным и подверженным ошибкам ... Здесь должно быть лучшее решение?

Есть ли что-то ослепительно очевидное, я только что забыл?

Хотел бы помочь!

+0

Если вы не хотите, чтобы все подклассы иметь метод GETALL с короткой подписью, он не должен быть в абстрактном базовом классе. Унаследованный метод, безусловно, вызывает проблемы. Вы можете добавить тег Джерси или JAX-RS к вопросу. – JayK

+0

Спасибо за отзыв тега; Хм, это то, что я сделал на данный момент, кажется глупым, хотя копирование одного и того же кода на 20 разных классов для решения в случае, когда только один класс обрабатывает разные вещи. –

+0

Механизм микширования был бы хорош, конечно. Очень расплывчатым ответом будет «состав над наследованием», но поскольку я до сих пор не использовал JAX-RS, я не знаю, можно ли применить эту мантру здесь красиво. – JayK

ответ

1

Я рекомендую использовать дженериков.

Мы выполнили аналогичную, но довольно сложную версию этого.Вначале было немного сложно, но у нас была максимальная возможность повторного использования кода (с 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; 
    } 
    ... 
} 

Затем вам нужно создать хранилище класса, который имеет необходимые методы, такие как save, getPaginated и т.д. для каждого типа объекта, перекрывая 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); 
    } 
} 
+0

Спасибо, это то, что я надеялся быть возможным –

0

Причина, по которой вам это не удается, заключается в том, что в вашем примере несколько методов сопоставляются с одним и тем же URL-адресом. Но если вы просто переоцените метод, Джерси не будет жаловаться.

Я бы рекомендовал иметь общие методы в вашем AbstractResource, где либо вы передаете @Context UriInfo uriInfo в ваш метод и разобрать его параметры запроса в обобщенном методе полезности, или использовать что-то вроде матричных параметров с помощью

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

и синтаксического анализа их снова через общий метод по умолчанию или комбинацию обоих.

Таким образом, вы можете по умолчанию использовать общую конечную точку во многих случаях или выполнять выборочную предварительную обработку и делегировать общие методы анализа для типичных случаев использования.

Если я вас правильно что-то, как вы хотели была предпринята в следующем проекте: https://github.com/researchgate/restler (Отказ от ответственности: Я вкладчик там)

Смежные вопросы