|
|
@@ -23,6 +23,9 @@ Add the [Spring MVC](TODO) and [Jackson](http://jackson.codehaus.org) JSON libra
|
|
23
|
23
|
|
|
24
|
24
|
### Maven \[[copy complete `pom.xml` to clipboard](start/pom.xml)\]
|
|
25
|
25
|
|
|
|
26
|
+Add the following within the `<project>` section of your pom.xml file:
|
|
|
27
|
+
|
|
|
28
|
+`pom.xml`
|
|
26
|
29
|
```xml
|
|
27
|
30
|
<dependencies>
|
|
28
|
31
|
<dependency>
|
|
|
@@ -55,6 +58,9 @@ Add the [Spring MVC](TODO) and [Jackson](http://jackson.codehaus.org) JSON libra
|
|
55
|
58
|
|
|
56
|
59
|
### Gradle \[[copy complete `build.gradle` to clipboard](start/build.gradle)\]
|
|
57
|
60
|
|
|
|
61
|
+Add the following within the `dependencies { }` section of your build.gradle file:
|
|
|
62
|
+
|
|
|
63
|
+`build.gradle`
|
|
58
|
64
|
```groovy
|
|
59
|
65
|
compile 'org.springframework:spring-webmvc:3.2.2.RELEASE'
|
|
60
|
66
|
compile 'com.fasterxml.jackson.core:jackson-core:2.1.4'
|
|
|
@@ -114,11 +120,11 @@ public class HelloWorldWebAppInitializer extends AbstractAnnotationConfigDispatc
|
|
114
|
120
|
}
|
|
115
|
121
|
```
|
|
116
|
122
|
|
|
117
|
|
-By extending [`AbstractAnnotationConfigDispatcherServletInitializer`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.html), our web application initializer will get a `DispatcherServlet` that is configured with [`@Configuration`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/context/annotation/Configuration.html)-annotated classes. All we must do is tell it where those configuration classes are and what path(s) to map `DispatcherServlet` to.
|
|
|
123
|
+By extending [`AbstractAnnotationConfigDispatcherServletInitializer`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.html), our web application initializer will get a `DispatcherServlet` that is configured with [`@Configuration`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/context/annotation/Configuration.html)-annotated classes. All we must do is tell it where those configuration classes are and what path(s) to map `DispatcherServlet` to.
|
|
118
|
124
|
|
|
119
|
125
|
With regard to the servlet path mappings, `getServletMappings()` returns a single-entry array of `String` specifying that `DispatcherServlet` should be mapped to "/".
|
|
120
|
126
|
|
|
121
|
|
-The `getRootConfigClasses()` and `getServletConfigClasses()` methods specify the configuration classes. The `Class` array returned from `getRootConfigClasses()` specifies the classes for the root context provided to [`ContextLoaderListener`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/context/ContextLoaderListener.html). Similarly, the `Class` array returned from `getServletConfigClasses()` specifies the classes for the servlet application context provided to `DispatcherServlet`.
|
|
|
127
|
+The `getRootConfigClasses()` and `getServletConfigClasses()` methods specify the configuration classes. The `Class` array returned from `getRootConfigClasses()` specifies the classes for the root context provided to [`ContextLoaderListener`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/context/ContextLoaderListener.html). Similarly, the `Class` array returned from `getServletConfigClasses()` specifies the classes for the servlet application context provided to `DispatcherServlet`.
|
|
122
|
128
|
|
|
123
|
129
|
For our purposes there will only be a servlet application context, so `getRootConfigClasses()` returns `null`. `getServletConfigClasses()`, however, specifies `HelloWorldConfiguration` as the only configuration class.
|
|
124
|
130
|
|
|
|
@@ -127,7 +133,7 @@ Creating a Representation Class
|
|
127
|
133
|
-------------------------------
|
|
128
|
134
|
With the essential Spring MVC configuration out of the way, it's time to get to the nuts and bolts of our REST service by creating a resource representation class and an endpoint controller.
|
|
129
|
135
|
|
|
130
|
|
-Before we get too carried away with building the endpoint controller, we need to give some thought to what our API will look like.
|
|
|
136
|
+Before we get too carried away with building the endpoint controller, we need to give some thought to what our API will look like.
|
|
131
|
137
|
|
|
132
|
138
|
What we want is to handle GET requests for /hello-world, optionally with a name query parameter. In response to such a request, we'd like to send back JSON, representing a greeting, that looks something like this:
|
|
133
|
139
|
|
|
|
@@ -198,17 +204,130 @@ public class HelloWorldController {
|
|
198
|
204
|
}
|
|
199
|
205
|
```
|
|
200
|
206
|
|
|
201
|
|
-The key difference between a human-facing controller and a REST endpoint controller is in how the response is created. Rather than rely on a view (such as JSP) to render model data in HTML, an endpoint controller simply returns the data to be written directly to the body of the response.
|
|
|
207
|
+The key difference between a human-facing controller and a REST endpoint controller is in how the response is created. Rather than rely on a view (such as JSP) to render model data in HTML, an endpoint controller simply returns the data to be written directly to the body of the response.
|
|
202
|
208
|
|
|
203
|
209
|
The magic is in the [`@ResponseBody`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/bind/annotation/ResponseBody.html) annotation. `@ResponseBody` tells Spring MVC to not render a model into a view, but rather to write the returned object into the response body. It does this by using one of Spring's message converters. Because Jackson 2 is in the classpath, this means that [`MappingJackson2HttpMessageConverter`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.html) will handle the conversion of Greeting to JSON if the request's `Accept` header specifies that JSON should be returned.
|
|
204
|
210
|
|
|
205
|
|
-Building and Running the REST Service
|
|
|
211
|
+
|
|
|
212
|
+Creating an executable main class
|
|
|
213
|
+---------------------------------
|
|
|
214
|
+_**TODO**: explain._
|
|
|
215
|
+
|
|
|
216
|
+`src/main/java/hello/Main.java`
|
|
|
217
|
+```java
|
|
|
218
|
+package hello;
|
|
|
219
|
+
|
|
|
220
|
+import java.util.HashSet;
|
|
|
221
|
+
|
|
|
222
|
+import org.apache.catalina.Context;
|
|
|
223
|
+import org.apache.catalina.core.AprLifecycleListener;
|
|
|
224
|
+import org.apache.catalina.core.StandardServer;
|
|
|
225
|
+import org.apache.catalina.startup.Tomcat;
|
|
|
226
|
+import org.springframework.web.SpringServletContainerInitializer;
|
|
|
227
|
+
|
|
|
228
|
+public class Main {
|
|
|
229
|
+
|
|
|
230
|
+ public static void main(String[] args) throws Exception {
|
|
|
231
|
+ Tomcat tomcat = new Tomcat();
|
|
|
232
|
+ tomcat.setPort(8080);
|
|
|
233
|
+ tomcat.setBaseDir(".");
|
|
|
234
|
+ tomcat.getHost().setAppBase("/");
|
|
|
235
|
+
|
|
|
236
|
+ // Add AprLifecycleListener
|
|
|
237
|
+ StandardServer server = (StandardServer)tomcat.getServer();
|
|
|
238
|
+ AprLifecycleListener listener = new AprLifecycleListener();
|
|
|
239
|
+ server.addLifecycleListener(listener);
|
|
|
240
|
+
|
|
|
241
|
+ Context context = tomcat.addWebapp("/", "/");
|
|
|
242
|
+ HashSet<Class<?>> classes = new HashSet<Class<?>>();
|
|
|
243
|
+ classes.add(HelloWorldWebAppInitializer.class);
|
|
|
244
|
+ context.addServletContainerInitializer(new SpringServletContainerInitializer(), classes);
|
|
|
245
|
+ tomcat.start();
|
|
|
246
|
+ tomcat.getServer().await();
|
|
|
247
|
+ }
|
|
|
248
|
+}
|
|
|
249
|
+```
|
|
|
250
|
+
|
|
|
251
|
+
|
|
|
252
|
+
|
|
|
253
|
+Building an executable JAR
|
|
|
254
|
+--------------------------
|
|
|
255
|
+
|
|
|
256
|
+Add the following to the <build><plugins> section of your pom.xml file:
|
|
|
257
|
+
|
|
|
258
|
+`pom.xml`
|
|
|
259
|
+```xml
|
|
|
260
|
+<plugin>
|
|
|
261
|
+ <groupId>org.apache.maven.plugins</groupId>
|
|
|
262
|
+ <artifactId>maven-shade-plugin</artifactId>
|
|
|
263
|
+ <version>1.6</version>
|
|
|
264
|
+ <configuration>
|
|
|
265
|
+ <createDependencyReducedPom>true</createDependencyReducedPom>
|
|
|
266
|
+ <filters>
|
|
|
267
|
+ <filter>
|
|
|
268
|
+ <artifact>*:*</artifact>
|
|
|
269
|
+ <excludes>
|
|
|
270
|
+ <exclude>META-INF/*.SF</exclude>
|
|
|
271
|
+ <exclude>META-INF/*.DSA</exclude>
|
|
|
272
|
+ <exclude>META-INF/*.RSA</exclude>
|
|
|
273
|
+ </excludes>
|
|
|
274
|
+ </filter>
|
|
|
275
|
+ </filters>
|
|
|
276
|
+ </configuration>
|
|
|
277
|
+ <executions>
|
|
|
278
|
+ <execution>
|
|
|
279
|
+ <phase>package</phase>
|
|
|
280
|
+ <goals>
|
|
|
281
|
+ <goal>shade</goal>
|
|
|
282
|
+ </goals>
|
|
|
283
|
+ <configuration>
|
|
|
284
|
+ <transformers>
|
|
|
285
|
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
|
|
286
|
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
|
|
287
|
+ <mainClass>hello.Main</mainClass>
|
|
|
288
|
+ </transformer>
|
|
|
289
|
+ </transformers>
|
|
|
290
|
+ </configuration>
|
|
|
291
|
+ </execution>
|
|
|
292
|
+ </executions>
|
|
|
293
|
+</plugin>
|
|
|
294
|
+```
|
|
|
295
|
+
|
|
|
296
|
+The following will produce a single executable JAR file containing all necessary dependency classes:
|
|
|
297
|
+
|
|
|
298
|
+```
|
|
|
299
|
+$ mvn package
|
|
|
300
|
+```
|
|
|
301
|
+
|
|
|
302
|
+_**TODO:** this produces a bunch of the following. Fix?_
|
|
|
303
|
+```
|
|
|
304
|
+[WARNING] We have a duplicate javax/el/ExpressionFactory$4.class in /Users/cbeams/.m2/repository/org/apache/tomcat/tomcat-el-api/7.0.39/tomcat-el-api-7.0.39.jar
|
|
|
305
|
+```
|
|
|
306
|
+
|
|
|
307
|
+
|
|
|
308
|
+Running the Service
|
|
206
|
309
|
-------------------------------------
|
|
207
|
|
->**NOTE**: This section is a very important section, because it shows the user how all of the work done up to this point comes together and runs. The challenge here, however, is that there's no *easy* way to run this application. Gradle's Jetty plugin seems easy and natural, but it uses an older, non-Servlet 3 version of Jetty, so the application initializer will not work. There is a Gradle Tomcat plugin, but it's quite involved setup-wise. And loading this into any IDE and running it is far more involved than either of the Gradle-based options. It would be really nice to leverage Spring Bootstrap/Catalyst for running the sample. That's likely what will happen, but at this point it's too risky of an option until bootstrap/catalyst stabilizes.
|
|
208
|
310
|
|
|
209
|
|
-Next Steps
|
|
210
|
|
-----------
|
|
211
|
|
-Congratulations! You have just developed a simple REST service using Spring. This is a basic foundation for building a complete REST API in Spring.
|
|
|
311
|
+```
|
|
|
312
|
+$ java -jar target/gs-rest-service-0.0.1-SNAPSHOT.jar
|
|
|
313
|
+
|
|
|
314
|
+... service comes up ...
|
|
|
315
|
+```
|
|
|
316
|
+
|
|
|
317
|
+_**TODO:** this fails with the following. Fix._
|
|
|
318
|
+```
|
|
|
319
|
+Caused by: java.lang.IllegalArgumentException: Failed to register servlet with name 'dispatcher'.Check if there is another servlet registered under the same name.
|
|
|
320
|
+ at org.springframework.util.Assert.notNull(Assert.java:112)
|
|
|
321
|
+ at org.springframework.web.servlet.support.AbstractDispatcherServletInitializer.registerDispatcherServlet(AbstractDispatcherServletInitializer.java:98)
|
|
|
322
|
+```
|
|
|
323
|
+
|
|
|
324
|
+_**TODO:** exercise the service in some meaningful way, e.g. with `curl`._
|
|
|
325
|
+
|
|
|
326
|
+Congratulations! You have just developed a simple REST service using Spring. This is a basic foundation for building a complete REST API in Spring.
|
|
|
327
|
+
|
|
|
328
|
+
|
|
|
329
|
+Related Resources
|
|
|
330
|
+-----------------
|
|
212
|
331
|
|
|
213
|
332
|
There's more to building REST services than is covered here. You may want to continue your exploration of Spring and REST with the following Getting Started guides:
|
|
214
|
333
|
|