REST with Spring ebook
REST with Spring ebook
Introduction
Over the course of this material, we’ll explore how to build a RESTful Service using Spring 3.
We’ll illustrate how to secure this service using various mechanisms – starting with the form-based login –
standard not necessarily well suited for REST, and moving to more RESTful solutions – Basic and Digest
Authentication.
Next, we’ll dig into Discoverability and the HATEOAS Constraint, and we’ll touch on more advanced subjects
such as implementing ETags functionality in Spring and error handling for a REST API.
Finally, we’ll discuss best practices for how to evolve and version a RESTful Service and we’ll end with a high
level look on how to test the API.
Table of Contents
I. Bootstrap the basic Web Project with Spring 3
II. Build the REST API with Spring 3 and Java Config
III. Security for REST – via Login Form
IV. Security for REST – via Basic Authentication
V. Security for REST – via Digest Authentication
VI. Security for REST – Basic and Digest Authentication together
VII. REST API Discoverability and HATEOAS
VIII. HATEOAS for a Spring REST Service
IX. ETags for REST with Spring
X. REST Pagination in Spring
XI. Error Handling for REST with Spring 3
XII. Versioning a REST API
XIII. Testing REST with multiple MIME types
REST with Spring ebook
I. Bootstrap a basic Web Project with Spring 3
1. Overview
The section will focus on bootstrapping the initial web application, discussing how to make the jump from XML
to Java without having to completely migrate the entire XML configuration.
2. The Maven pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<
project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org</groupId>
<artifactId>rest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>${cglib.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<finalName>rest</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<spring.version>3.2.2.RELEASE</spring.version>
?
REST with Spring ebook
2.1. Justification of the cglib dependency
You may wonder why cglib is a dependency – it turns out there is a valid reason to include it – the entire
configuration cannot function without it. If removed, Spring will throw:
Caused by: java.lang.IllegalStateException: CGLIB is required to process @Configuration classes. Either add
CGLIB to the classpath or remove the following @Configuration bean definitions
The reason this happens is explained by the way Spring deals with @Configuration classes. These classes are
effectively beans, and because of this they need to be aware of the Context, and respect scope and other bean
semantics. This is achieved by dynamically creating a cglib proxy with this awareness for each @Configuration
class, hence the cglib dependency.
Also, because of this, there are a few restrictions for Configuration annotated classes:
Configuration classes should not be final
They should have a constructor with no arguments
2.2. The cglib dependency in Spring 3.2
Starting with Spring 3.2, it is no longer necessary to add cglib as an explicit dependency. This is because
Spring is in now inlining cglib – which will ensure that all class based proxying functionality will work out of the
box with Spring 3.2.
The new cglib code is placed under the Spring package: org.springframework.cglib (replacing the original
net.sf.cglib). The reason for the package change is to avoid conflicts with any cglib versions already existing on
the classpath.
Also, the new cglib 3.0 is now used, upgraded from the older 2.2 dependency (see this JIRA issue for more
details).
3. The Java based web configuration
First, the @Configuration annotation – this is the main artifact used by the Java based Spring configuration; it is
itself meta-annotated with @Component, which makes the annotated classes standard beans and as such, also
candidates for component scanning. The main purpose of @Configuration classes is to be sources of bean
53
54
55
56
<cglib.version>2.2.2</cglib.version>
</properties>
</project>
1
2
3
4
5
6
7
8
9
10
11
@Configuration
@ImportResource( { "classpath*:/rest_config.xml" } )
@ComponentScan( basePackages = "org.rest" )
@PropertySource({ "classpath:rest.properties", "classpath:web.properties" })
public class AppConfig{
@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
return new PropertySourcesPlaceholderConfigurer();
}
}
?
REST with Spring ebook
definitions for the Spring IoC Container. For a more detailed description, see the official docs.
Then, @ImportResource is used to import the existing XML based Spring configuration. This may be
configuration which is still being migrated from XML to Java, or simply legacy configuration that you wish to keep.
Either way, importing it into the Container is essential for a successful migration, allowing small steps without to
much risk. The equivalent XML annotation that is replaced is:
<import resource=”classpath*:/rest_config.xml” />
Moving on to @ComponentScan – this configures the component scanning directive, effectively replacing the
XML:
As of Spring 3.1, the @Configuration are excluded from classpath scanning by default – see this JIRA issue.
Before Spring 3.1 though, these classes should have been excluded explicitly:
The @Configuration classes should not be autodiscovered because they are already specified and used by the
Container – allowing them to be rediscovered and introduced into the Spring context will result in the following
error:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified
bean name ‘webConfig’ for bean class [org.rest.spring.AppConfig] conflicts with existing, non-compatible bean
definition of same name and class [org.rest.spring.AppConfig]
And finally, using the @Bean annotation to configure the properties support –
PropertySourcesPlaceholderConfigurer is initialized in a @Bean annotated method, indicating it will produce a
Spring bean managed by the Container. This new configuration has replaced the following XML:
For a more in depth discussion on why it was necessary to manually register the
PropertySourcesPlaceholderConfigurer bean, see the Properties with Spring Tutorial.
3.1. The web.xml
<context:component-scan base-package="org.rest" />
excludeFilters = { @ComponentScan.Filter( Configuration.class ) }
1
2
3
<context:property-placeholder
location="classpath:persistence.properties, classpath:web.properties"
ignore-unresolvable="true"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?
xml
version="1.0" encoding="UTF-8"?>
<web-app xmlns="
http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="rest" version="3.0">
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
?
?
?
?
REST with Spring ebook
Unlike XmlWebApplicationContext, it assumes no default configuration class locations, so the
“contextConfigLocation” init-param for the Servlet must be set. This will point to the java package where the
@Configuration classes are located; the fully qualified name(s) of the classes are also supported.
Next, the DispatcherServlet is configured to use the same kind of context, with the only difference that it’s loading
configuration classes out of a different package.
Other than this, the web.xml doesn’t really change from a XML to a Java based configuration.
4. Conclusion
The presented approach allows for a smooth migration of the Spring configuration from XML to Java, mixing
the old and the new. This is important for older projects, which may have a lot of XML based configuration that
cannot be migrated all at once. This way, the web.xml and bootstrapping of the application is the first step in a
migration, after which the remaining XML beans can be ported in small increments.
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>org.rest.spring.root</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>org.rest.spring.rest</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>rest</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file />
</welcome-file-list>
</web-app>
First, the root context is defined and configured to use AnnotationConfigWebApplicationContext instead of the
default XmlWebApplicationContext. The newer AnnotationConfigWebApplicationContext accepts @Configuration
annotated classes as input for the Container configuration and is needed in order to set up the Java based
context.