12 Aug 2013
What is spring-data-rest?
spring-data-rest, a recent addition to the spring-data project, is a framework that helps you expose your entities directly as RESTful webservice endpoints. Unlike rails, grails or roo it does not generate any code achieving this goal. spring data-rest supports JPA, MongoDB, JSR-303 validation, HAL and many more. It is really innovative and lets you setup your RESTful webservice within minutes. In this example i'll give you a short overview of what spring-data-rest is capable of.
Initial Configuration
I'm gonna use the new Servlet 3 Java Web Configuration instead of an ancient web.xml. Nothing really special here.
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{AppConfiguration.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfiguration.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
WebAppInitializer.java on github
I am initializing hibernate as my database abstraction layer in the AppConfiguration class. I'm using an embedded database (hsql), since i want to keep this showcase simple stupid. Still, nothing special here.
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
public class AppConfiguration {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.HSQL);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getClass().getPackage().getName());
factory.setDataSource(dataSource());
return factory;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
AppConfiguration.java on github
Now to the application servlet configuration: WebConfiguration
@Configuration
public class WebConfiguration extends RepositoryRestMvcConfiguration {
}
WebConfiguration.java on github
Oh, well thats a bit short isnt it? Not a single line of code required for a complete setup. This is a really nice application of the convention over configuration paradigm. We can now start creating spring-data-jpa repositories and they will be exposed as RESTful resources automatically. And we can still add custom configuration to the WebConfiguration class later if needed.
The Initialization was really short and easy. We didn't have to code anything special. The only thing we did was setting up a database connection and hibernate, which is obviously inevitable. Now, that we have setup our "REST Servlet" and persistence, lets move on to the application itself, starting with the model.
The Model
I'll keep it really simple creating only two related entities.
@Entity
public class Book {
@Id
private String isbn;
private String title;
private String language;
@ManyToMany
private List<Author> authors;
}
Book.java on github
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
private String name;
@ManyToMany(mappedBy = "authors")
private List<Book> books;
}
Author.java on github
To finally make the entities persistent and exposed as a RESTful webservice, we need spring-data repositories. A repository is basically a DAO. It offers CRUD functionality for our entities. spring-data takes away most of your programming effort creating such repositories. We just have to define an empty interface, spring-data does everything else out of the box. Still, it is easily customizable thanks to its design by convention over configuration!
The Actual Repositories
@RestResource(path = "books", rel = "books")
public interface BookRepository extends PagingAndSortingRepository<Book, Long> {
}
BookRepository.java on github
@RestResource(path = "authors", rel = "authors")
public interface AuthorRepository extends PagingAndSortingRepository<Author, Integer> {
}
AuthorRepository.java on github
Again, there is nearly no code needed. Even the @RestResource annotation could be left out. But if i did, the path and rel would be named after the entity, which i dont want. A REST resource that contains multiple children should be named plural though.
Accessing The Result
Our RESTful webservice is now ready for deployment. Once run, it lists all available resources on the root, so you can navigate from there.
GET http://localhost:8080/
{
"links" : [ {
"rel" : "books",
"href" : "http://localhost:8080/books"
}, {
"rel" : "authors",
"href" : "http://localhost:8080/authors"
} ],
"content" : [ ]
}
Fine! Now lets create an author and a book.
POST http://localhost:8080/authors
Response
201 Created
Location: http://localhost:8080/authors/1
PUT http://localhost:8080/books/0132350882
{
"title": "Clean Code",
"authors": [
{
"rel": "authors",
"href": "http://localhost:8080/authors/1"
}
]
}
Response
201 Created
Noticed how i used PUT to create the book? This is because its id is the actual isbn. I have to tell the server which isbn to use since he cant guess it. I used POST for the author as his id is just an incremental number that is generated automatically. Also, i used a link to connect both, the book (/books/0132350882) and the author (/authors/1). This is basically what hypermedia is all about: Links are used for navigation and relations between entities.
Now, lets see if the book was created accordingly.
GET http://localhost:8080/books
{
"links" : [ ],
"content" : [ {
"links" : [ {
"rel" : "books.Book.authors",
"href" : "http://localhost:8080/books/0132350882/authors"
}, {
"rel" : "self",
"href" : "http://localhost:8080/books/0132350882"
} ],
"title" : "Clean Code"
} ],
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 1
}
}
Fine!
Here is an Integration Test, following these steps automatically. It is also available in the example on github.
public class BookApiIT {
private final RestTemplate restTemplate = new RestTemplate();
private final String authorsUrl = "http://localhost:8080/authors";
private final String booksUrl = "http://localhost:8080/books";
@Test
public void testCreateBookWithAuthor() throws Exception {
final URI authorUri = restTemplate.postForLocation(authorsUrl, sampleAuthor()); // create Author
final URI bookUri = new URI(booksUrl + "/" + sampleBookIsbn);
restTemplate.put(bookUri, sampleBook(authorUri.toString())); // create Book linked to Author
Resource<Book> book = getBook(bookUri);
assertNotNull(book);
final URI authorsOfBookUri = new URI(book.getLink("books.Book.authors").getHref());
Resource<List<Resource<Author>>> authors = getAuthors(authorsOfBookUri);
assertNotNull(authors.getContent());
assertFalse(authors.getContent().isEmpty()); // check if /books/0132350882/authors contains an author
}
private String sampleAuthor() {
return "{\"name\":\"Robert C. Martin\"}";
}
private final String sampleBookIsbn = "0132350882";
private String sampleBook(String authorUrl) {
return "{\"title\":\"Clean Code\",\"authors\":[{\"rel\":\"authors\",\"href\":\"" + authorUrl + "\"}]}";
}
private Resource<Book> getBook(URI uri) {
return restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference<Resource<Book>>() {
}).getBody();
}
private Resource<List<Resource<Author>>> getAuthors(URI uri) {
return restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference<Resource<List<Resource<Author>>>>() {
}).getBody();
}
}
BookApiIT.java on github
Conclusion
We have created a complete RESTful webservice without much coding effort. We just defined our entities and database connection. spring-data-rest stated that everything else is just boilerplate, and i agree.
To consume the webservices manually, consider the rest-shell. It is a command-shell making the navigation in your webservice as easy and fun as it could be. Here is a screenshot:
The complete example is available on my github
https://github.com/gregorriegler/babdev-spring/tree/master/spring-data-rest
https://github.com/gregorriegler/babdev-spring
02 Aug 2013
Redeem yourself from hot deployments and OutOfMemoryException. spring-loaded is an opensource classreloader and a promising alternative to JRebel. It does not offer as many features as JRebel does, and it does not support any framework just yet. Nevertheless, its a great tool that can save the time you are waiting for that servletcontainer to restart again.
What it can do:
- add/modify/delete methods/fields/constructors
- modify annotations on types/methods/fields/constructors
- add/remove/change values in enum types
What it cant:
- support framework specific changes like Spring MVC @RequestMappings
- change log configuration on the fly
It is not getting the attention that it deserves, so give it a try.
Just download from github already, and add the following to your JVM parameters:
java -javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify
20 Jul 2013
In this post i want to dig into spring mvc a little, revealing what happens behind the scenes when a request is converted to your parameter object and vice versa. Before we start, i want to explain the purpose of these annotations.
What are @RequestBody and @ResponseBody for?
They are annotations of the spring mvc framework and can be used in a controller to implement smart object serialization and deserialization. They help you avoid boilerplate code by extracting the logic of messageconversion and making it an aspect. Other than that they help you support multiple formats for a single REST resource without duplication of code. If you annotate a method with @ResponseBody, spring will try to convert its return value and write it to the http response automatically. If you annotate a methods parameter with @RequestBody, spring will try to convert the content of the incoming request body to your parameter object on the fly.
Here is an example
@Controller
@RequestMapping(value = "/bookcase")
public class BookCaseController {
private BookCase bookCase;
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public BookCase getBookCase() {
return this.bookCase;
}
@RequestMapping(method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void setBookCase(@RequestBody BookCase bookCase) {
this.bookCase = bookCase;
}
}
So what is Spring doing behind the scenes when we are using those Annotations?
Depending on your configuration, spring has a list of HttpMessageConverters registered in the background. A HttpMessageConverters responsibility is to convert the request body to a specific class and back to the response body again, depending on a predefined mime type. Every time an issued request is hitting a @RequestBody or @ResponseBody annotation spring loops through all registered HttpMessageConverters seeking for the first that fits the given mime type and class and then uses it for the actual conversion.
How can i add a custom HttpMessageConverter?
By adding @EnableWebMvc respectively <mvc:annotation-driven />, spring registers a bunch of predefined messageconverters for JSON/XML and so on. You can add a custom converter like the following
@Configuration
@EnableWebMvc
@ComponentScan
public class WebConfiguration extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
httpMessageConverters.add(new BookCaseMessageConverter(new MediaType("text", "csv")));
}
}
In this example i've written a converter that handles the conversion of a BookCase, which is basically a List of Books. The converter is able to convert csv content to a BookCase and vice versa. I used opencsv to parse the text.
Here is the model
public class Book {
private String isbn;
private String title;
public Book(String isbn, String title) {
this.isbn = isbn;
this.title = title;
}
// ...
}
public class BookCase extends ArrayList<Book> {
public BookCase() {
}
public BookCase(Collection<? extends Book> c) {
super(c);
}
}
and the actual converter
public class BookCaseMessageConverter extends AbstractHttpMessageConverter<BookCase> {
public BookCaseMessageConverter() {
}
public BookCaseMessageConverter(MediaType supportedMediaType) {
super(supportedMediaType);
}
public BookCaseMessageConverter(MediaType... supportedMediaTypes) {
super(supportedMediaTypes);
}
@Override
protected boolean supports(Class<?> clazz) {
return BookCase.class.equals(clazz);
}
@Override
protected BookCase readInternal(Class<? extends BookCase> clazz, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
CSVReader reader = new CSVReader(new InputStreamReader(httpInputMessage.getBody()));
List<String[]> rows = reader.readAll();
BookCase bookCase = new BookCase();
for (String[] row : rows) {
bookCase.add(new Book(row[0], row[1]));
}
return bookCase;
}
@Override
protected void writeInternal(BookCase books, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
CSVWriter writer = new CSVWriter(new OutputStreamWriter(httpOutputMessage.getBody()));
for (Book book : books) {
writer.writeNext(new String[]{book.getIsbn(), book.getTitle()});
}
writer.close();
}
}
The Result
We can now issue text/csv requests to our Resource along with application/json and xml which are basically supported out of the box.
1.)
PUT /bookcase
Content-Type: text/csv
"123","Spring in Action"
"456","Clean Code"
Response
204 No Content
2.)
GET /bookcase
Accept: text/csv
Response
200 OK
"123","Spring in Action"
"456","Clean Code"
Thanks to the design of spring mvc, which is following the single responsibility principle, our controller stays thin. We don't have to add a single line if we want to support new media types.
The complete example is available on my github