2011-08-29 12 views
13

Estoy intentando construir una API RESTful utilizando Spring MVC. Estoy buscando un código limpio y manejable donde la estructura del paquete sigue la estructura de la url.Cómo heredar RequestMappings en una Spring 3 MVC REST API

Así que aquí es lo que tengo:

// com.test.api.library 
@RequestMapping("/library/{libraryId}") 
public Library getLibrary(@PathVariable long libraryId) { 
    return service.getLibraryById(libraryId); 
} 

// com.test.api.library.book 
@RequestMapping("/library/{libraryId}/book/{bookId}") 
public Book getBook(@PathVariable long libraryId, @PathVariable long bookId) { 
    Library library service.getLibraryById(libraryId); 
    return library.getBookById(bookId); 
} 

Aunque esto funciona, me resulta complicado y propenso a errores que tiene que repetir "/ Library/{} libraryId "en todos los @RequestMappings heredados,/es probable que la biblioteca sea la raíz de una gran parte de la API y debe escribirse una vez y reutilizarse en lugar de estar escrita en todas partes.

me gustaría volver a escribir el libro de clase a algo como esto:

// com.test.api.library.book 
@RequestMapping("/book/{bookId}") 
public Book getBook(@PathVariable long bookId) { 
    // long libraryId magically given to me from the library-class's getLibrary() 

    Library library service.getLibraryById(libraryId); 
    return library.getBookById(bookId); 
} 

¿Hay alguna manera de primavera me puede ayudar aquí? Es aceptable para mí usar la herencia java normal, la anotación de primavera o cualquier otra cosa que me ayude a no escribir "/ library/{libraryId}" como parte de cada url que escribo.

Respuesta

1

No creo que sea posible. Pero puede tener la anotación @RequestMapping en la clase misma, por lo que le ahorrará al menos algo de tipeo.

+0

Sí, soy consciente de esa posibilidad y es mucho mejor que simplemente anotar los métodos, pero aún está lejos de ser perfecto. –

+0

En base a su respuesta, que está muy lejos de ser perfecto, no está claro para mí lo que desea. ¿Puede mostrar las URL completas de un par de recursos y describir qué controlador (es) quiere que maneje? – SingleShot

+0

Bien, este podría ser un ejemplo extraño pero ... '/ country/{countryCode}/state/{stateCode}/city/{cityCode}/street/{streetCode}/number/{streetNumber}' cada uno de estos pasos (es decir, país, estado, ciudad, calle y número) deben ir a su propio Controlador y cada uno tiene un subconjunto de verbos en ellos. La pregunta principal es cuando hago el control numérico que no voy a repetir la url todo el camino desde el país y no quiero "saber" que countryCode es la clave de este recurso. Solo quiero "tenerlo". –

-1
@Controller 
@RequestMapping("/library/{libraryId}") 
public class HelloWorldController { 

    @RequestMapping(value="/book/{bookId}") 
    public ModelAndView helloWorld() { 
     .... 
    } 

} 
+0

Eso no responde mi pregunta. Soy muy consciente de esta posibilidad (si nada más se señaló en la respuesta dada por Bozho). Hacer esto significa que tengo que poner todos los subcursos en la Biblioteca en el mismo Controlador. Eso es posible, por supuesto, pero no es lo que estoy pidiendo. Gracias de todos modos. –

4

Creo que esta pregunta se ha hecho & respondieron delante: Spring MVC @RequestMapping Inheritance

Dicho esto, aquí es una manera de reducir la cantidad de información duplicada. En realidad, no hago esto en mi propio código porque creo que tener el URI justo al lado del código es más fácil de mantener, incluso si eso significa una pequeña duplicación.

@RequestMapping(URI_LIBRARY) 
public interface LibraryNamespace { 
    public static String URI_LIBRARY = "/library/{libraryId}"; 
} 

@RequestMapping(URI_BOOK) 
public interface BookNamespace { 
    public static String URI_BOOK = LibraryNamespace.URI_LIBRARY + "/book/{bookId}"; 
} 

@Controller 
public class LibraryController implements LibraryNamespace { 
    @RequestMapping("") 
    public Library get(@PathVariable long libraryId) { 
    return service.getLibraryById(libraryId); 
    } 
} 

@Controller 
public class BookController implements BookNamespace { 
    @RequestMapping("") 
    public Book get(@PathVariable long libraryId, @PathVariable long bookId) { 
    Library library service.getLibraryById(libraryId); 
    return library.getBookById(bookId); 
    } 
} 

Como yo no tomaría este enfoque yo mismo, ¡en realidad no he probado esta solución! Según mi comprensión de Spring, creo que debería funcionar ...

+0

Estoy empezando a pensar que la respuesta es "no, no es posible". Su idea agrega un nivel de abstracción pero aún no resuelve el problema principal que está manejando el "@PathVariable long libraryId" en algún lugar más cercano a la clase de la biblioteca. Viniendo de java "normal", estoy acostumbrado a la herencia y a dejar que la clase padre maneje sus propias variables y deje que las subclases manejen lo que es específico para ellas. Gracias de todos modos. –

+0

No es posible. Lo siento, no lo he dejado más claro aquí. Pensé que la pregunta/respuesta a la que me había vinculado era lo suficientemente clara. – jtoberon

+0

He hecho esto. Si combina este enfoque con el enfoque padre polimórfico, entonces puede obtener rutas DRY y argumentos de separación de preocupaciones. Desenterraré mi código y publicaré una respuesta. – Alex

3

Utilice un enfoque padre polimórfico.

@Controller 
public class CommentsController { 
    @RequestMapping(value="/comments", method = RequestMethod.GET) 
    public @ResponseBody String index() { 
     /* kludge to allow optional path parameters */ 
     return index(null, null); 
    } 

    @RequestMapping(value="/{parent_collection}/{parent_id}/comments", method = RequestMethod.GET) 
    public @ResponseBody String index(@PathVariable("parent_collection") String parentCollection, @PathVariable("parent_id") String parentId) { 
     if (parentCollection == null) { 
      return "all comments"; 
     } 
     else if ((parentCollection != null) && (parentCollection.equals("posts"))) { 
      /* get parent, then get comments for parent */ 
      return "comments for single post"; 
     } 
     else if ((parentCollection != null) && (parentCollection.equals("customers"))) { 
      /* get parent, then get comments for parent */ 
      return "comments for single customer"; 
     } 
     else if ((parentCollection != null) && (parentCollection.equals("movies"))) { 
      /* get parent, then get comments for parent */ 
      return "comments for single movie"; 
     } 
    } 

    @RequestMapping(value = "/comments/{id}", method = RequestMethod.GET) 
    public @ResponseBody String show(@PathVariable Integer id) { 
     /* kludge to allow optional path parameters */ 
     return show(null, null, id); 
    } 

    @RequestMapping(value = "/{parent_collection}/{parent_id}/comments/{id}", method = RequestMethod.GET) 
    public @ResponseBody String show(@PathVariable("parent_collection") String parentCollection, @PathVariable("parent_id") String parentId, @PathVariable Integer id) { 
     /* get comment, then get parent from foreign key */ 

     if (parentCollection == null) { 
      return "single comment"; 
     } 
     else if ((parentCollection != null) && (parentCollection.equals("posts"))) { 
      return "single comment for single post"; 
     } 
     else if ((parentCollection != null) && (parentCollection.equals("customers"))) { 
      return "single comment for single customer"; 
     } 
     else if ((parentCollection != null) && (parentCollection.equals("movies"))) { 
      return "single comment for single movie"; 
     } 
    } 
} 

Además, se puede usar un controlador de base para encaminar el prefijo URI de recursos para padres (/libraries/{library_id}/../..), añadir los modelos de padres para el ámbito de la petición, y luego dejar que la solicitud se asignaciones regulares manejan el resto de la URI recursos secundarios (/../../books/1). No tengo un ejemplo de esto fuera de la mano.

Nota al margen. Los recursos anidados singulares se consideran generalmente como un antipatrón para el diseño de URI. Un controlador debe manejar sus propios recursos. Las implementaciones más comunes hacen que la clave para el recurso anidado singular sea única, es decir, no dependa de su recurso padre. Por ejemplo, una clave primaria de registro de base de datos. Sin embargo, hay situaciones en las que la clave puede no ser única, como un valor ordinal o de posición (por ejemplo, libro 1, capítulo 1, capítulo 2) o incluso una clave natural (por ejemplo, ISBN de libro, SSN de persona, dirección de correo electrónico , nombre de usuario, nombre de archivo).

Ejemplo de URIs canónicas para los recursos anidados:

  • /articles => ArticlesController # índice
  • /articles/1 => ArticlesController # muestran
  • /articles/1/comments => CommentsController # índice
  • /articles/1/comments/2 => CommentsController # mostrar (está bien, pero no es el preferido)
  • /comments/2 => ComentariosController # show (preferido)
Cuestiones relacionadas