Pages

Sunday, February 9, 2014

Modern Spring 4 MVC Hello World Tutorial

I was browsing reddit as I am wont to do at pretty much any hour of any day and came across a Spring 4 MVC tutorial. At work I use Spring 4 and was curious what a beginner tutorial would look like. Even though it was a small tutorial I expected to see things like @RestController and JavaConfig. I even hoped to see Gradle, but alas it was all XML configuration and a maven build with even more XML. No wonder people get such a bad impression of Spring and Java. Below I've quickly put together a tutorial using techniques I use working with Spring 4. I'm glossing over some things so if something doesn't make sense just leave a comment. It's super simple and some things are overkill but it's just so you can get a flavor of what working with Java and Spring 4 can really be like.

All the code can be found on it's github repository.

The build file

The most important parts are the repositories section and the dependencies section. The repositories section defines where to find the dependencies of your project designated in the dependencies section. In this case we can get everything from Maven central. The ext section defines some variables used for the specific versions of your dependencies. The buildscript section allows us to add functionality into gradle. In this case we are adding a tomcat plugin so we can launch a tomcat instance directly from the gradle build.

apply plugin: "eclipse"
apply plugin: "idea"
apply plugin: "war"
apply plugin: "tomcat"
war { baseName='hello-world' }
sourceCompatibility = 1.7
buildscript {
repositories {
jcenter()
}
dependencies { classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:1.0' }
}
repositories {
mavenCentral()
}
ext {
jacksonVersion="2.2.3"
springVersion="4.0.1.RELEASE"
tomcatVersion="7.0.50"
}
dependencies {
providedCompile "javax.servlet:javax.servlet-api:3.1.0", "javax.servlet.jsp:jsp-api:2.1"
runtime ("javax.servlet:jstl:1.2") {
exclude group: "javax.servlet", module: "javax.servlet-api"
exclude group: "javax.servlet", module: "jsp-api"
}
compile "org.springframework:spring-context:$springVersion"
compile "org.springframework:spring-core:$springVersion"
compile "org.springframework:spring-web:$springVersion"
compile "org.springframework:spring-webmvc:$springVersion"
compile "com.fasterxml.jackson.core:jackson-core:$jacksonVersion"
compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
compile "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion"
testCompile "junit:junit:4.11"
testCompile "org.springframework:spring-test:$springVersion"
tomcat "org.apache.tomcat.embed:tomcat-embed-core:$tomcatVersion",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:$tomcatVersion"
tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:$tomcatVersion") {
exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
}
}
view raw build.gradle hosted with ❤ by GitHub

The Configuration

We will be using a completely XML-less configuration. This requires a server that supports servlet 3.0 and above. This means >= Tomcat 7 or >= Jetty 8. Major takeaway is that we will put our controller in the com.hello.controller package so we need to scan that package.

package com.hello;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "com.hello.controller", "com.hello.config" })
public class AppConfig {
}
view raw AppConfig.java hosted with ❤ by GitHub
package com.hello.config;
import javax.servlet.Filter;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.filter.HttpPutFormContentFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.hello.AppConfig;
import com.hello.WebConfig;
public class ServletInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { AppConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setInitParameter("dispatchOptionsRequest", "true");
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter charFilter = new CharacterEncodingFilter();
charFilter.setEncoding("UTF-8");
charFilter.setForceEncoding(true);
return new Filter[] { new HiddenHttpMethodFilter(), charFilter,
new HttpPutFormContentFilter() };
}
}
package com.hello;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "com.hello.controller")
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
view raw WebConfig.java hosted with ❤ by GitHub

The Controller

Spring 4 introduced a new controller annotation @RestController. This is just a convenience annotation that means the class is an @Controller class and all the methods are @ResponseBody methods. This is a nice addition when building out REST webservices using Spring MVC.

package com.hello.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping(method = RequestMethod.GET, value = "/{name}")
public String sayHello(@PathVariable("name") String name) {
return name + " from the server";
}
}

The view

We are using AngularJS to interact with our controller. The way most of the webapps I've been writing lately work is having a Java backend provide a REST webservice to a javascript frontend. There are pros and cons to this approach which can be debated but I've been having fun and been productive using this method.
/*jshint unused: false */
/*global angular:true */
// Declare app level module
var App = angular.module('App', []);
(function() {
'use strict';
App.controller("HelloCtrl", ['$scope', '$http',
function($scope, $http) {
$scope.name = "User";
$scope.message = "";
$scope.getName = function() {
$http.get('hello/' + $scope.name).then(function(response) {
$scope.message = "Hello " + response.data;
});
};
}
]);
})();
view raw app.js hosted with ❤ by GitHub
<!DOCTYPE html>
<html ng-app="App">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello</title>
</head>
<body ng-controller="HelloCtrl">
<input type="text" ng-model="name"></input><button ng-click="getName()">Submit</button>
<div>{{message}}</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.js"></script>
<script src="js/app.js"></script>
</body>
</html>
view raw index.html hosted with ❤ by GitHub

The Controller Test

Spring has really nice support for testing your controllers as well.

package com.hello.controller;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.hello.AppConfig;
import com.hello.WebConfig;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = { AppConfig.class, WebConfig.class })
public class HelloControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void helloTest() throws Exception {
String name = "Test";
mockMvc
.perform(get("/hello/{name}", name).accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(
content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(content().string(name + " from the server"));
}
}


Result

Make sure you have JDK 7 installed and then run it yourself by grabbing the code and from the command line run ./gradlew tomcatRun for OSX/Linux or gradlew.bat tomcatRun if you are on Windows. Gradle should take care of everything else for you.

No comments:

Post a Comment