What is Spring MVC?

The Spring Web model-view-controller (MVC) framework is designed around a DispatcherServlet that dispatches requests to handlers, with configurable handler mappings, view resolution, locale and theme resolution as well as support for uploading files. The default handler is based on the @Controller and @RequestMapping annotations, offering a wide range of flexible handling methods.

  • The Model encapsulates the application data and in general, they will consist of POJOs.
  • The View is responsible for rendering the model data and in general it generates HTML output that the client’s browser can interpret.
  • The Controller is responsible for processing user requests and building an appropriate model and passes it to the view for rendering.

Spring’s web MVC framework is, like many other web MVC frameworks, request-driven, designed around a central Servlet that dispatches requests to controllers and offers other functionality that facilitates the development of web applications. Spring’s DispatcherServlet however, does more than just that. It is completely integrated with the Spring IoC container and as such allows you to use every other feature that Spring has.

The request processing workflow of the Spring Web MVC DispatcherServlet is illustrated in the following diagram

Image for post
Image for post

Note: The DispatcherServlet is an actual Servlet (it inherits from the HttpServlet base class), and as such is declared in the web.xml of your web application.

Below is the code based equivalent of the above web.xml example:

<web-app>

<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>

</web-app>

The @Controller annotation indicates that a particular class serves the role of a controller. Spring does not require you to extend any controller base class or reference the Servlet API. However, you can still reference Servlet-specific features if you need to.

The @Controller annotation marks this class as Controller.

The @Requestmapping annotation is used to map the class with the specified URL name.

package com.example;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;@Controllerpublic class HelloController {@RequestMapping(“/”)public String display(){return “index”;}}

Entity Classes

To be able to store information about developers and their skills, we will need to define two entity classes: “Developer” and “Skill”.

Both of these are defined as plain Java classes with some annotations. By adding “@Entity” before the classes, we are making their instances available to JPA. This will make it easier to store and retrieve instances from the persistent data store when needed. Additionally, the “@Id” and “@GeneratedValue” annotations allow us to indicate the unique ID field for the entity and have its value generated automatically when stored in the database.

As a developer can have many skills, we can define a simple many-to-many relationship using the “@ManyToMany” annotation.

@Entity
public class Developer {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
private String email;
@ManyToMany
private List<Skill> skills;

public Developer() {
super();
}

public Developer(String firstName, String lastName, String email,
List<Skill> skills) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.skills = skills;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public List<Skill> getSkills() {
return skills;
}

public void setSkills(List<Skill> skills) {
this.skills = skills;
}

public boolean hasSkill(Skill skill) {
for (Skill containedSkill: getSkills()) {
if (containedSkill.getId() == skill.getId()) {
return true;
}
}
return false;
}

}
@Entity
public class Skill {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String label;
private String description;

public Skill() {
super();
}

public Skill(String label, String description) {
super();
this.label = label;
this.description = description;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getLabel() {
return label;
}

public void setLabel(String label) {
this.label = label;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

}

Repositories

With JPA we can define a very useful DeveloperRepository interface and SkillRepository interface, which allow for easy CRUD operations. These interfaces will allow us to access stored developers and skills through simple method calls, such as:

  • “respository.findAll()”: returns all developers
  • “repository.findOne(id)”: returns developer with given ID

To create these interfaces, all we need to do is extend the CrudRepository interface.

public interface DeveloperRepository extends CrudRepository<Developer, Long> {}
public interface SkillRepository extends CrudRepository<Skill, Long> {
public List<Skill> findByLabel(String label);
}

Functionality for the additional method “findByLabel “declared here will be provided automatically by JPA.

Controller

Next, we can work on the controller for this application. The controller will map request URIs to view templates and perform all necessary processing in between.

@Controller
public class DevelopersController {

@Autowired
DeveloperRepository repository;

@Autowired
SkillRepository skillRepository;

@RequestMapping("/developer/{id}")
public String developer(@PathVariable Long id, Model model) {
model.addAttribute("developer", repository.findOne(id));
model.addAttribute("skills", skillRepository.findAll());
return "developer";
}

@RequestMapping(value="/developers",method=RequestMethod.GET)
public String developersList(Model model) {
model.addAttribute("developers", repository.findAll());
return "developers";
}

@RequestMapping(value="/developers",method=RequestMethod.POST)
public String developersAdd(@RequestParam String email,
@RequestParam String firstName, @RequestParam String lastName, Model model) {
Developer newDeveloper = new Developer();
newDeveloper.setEmail(email);
newDeveloper.setFirstName(firstName);
newDeveloper.setLastName(lastName);
repository.save(newDeveloper);

model.addAttribute("developer", newDeveloper);
model.addAttribute("skills", skillRepository.findAll());
return "redirect:/developer/" + newDeveloper.getId();
}

@RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST)
public String developersAddSkill(@PathVariable Long id, @RequestParam Long skillId, Model model) {
Skill skill = skillRepository.findOne(skillId);
Developer developer = repository.findOne(id);

if (developer != null) {
if (!developer.hasSkill(skill)) {
developer.getSkills().add(skill);
}
repository.save(developer);
model.addAttribute("developer", repository.findOne(id));
model.addAttribute("skills", skillRepository.findAll());
return "redirect:/developer/" + developer.getId();
}

model.addAttribute("developers", repository.findAll());
return "redirect:/developers";
}

}

Mapping of URIs to methods is done via simple “@RequestMapping” annotations. In this case, every method of the controller is mapped to a URI.

The model parameter of these methods allows data to be passed to the view. In essence, these are simple maps of keys to values.

Each controller method either returns the name of the Thymeleaf template to be used as view, or a URL in a specific pattern (“redirect:”) to redirect to. For example, the methods “developer” and “_developersList_” returns the name of a template, while “developersAdd” and “developersAddSkill” return URLs to redirect to.

Within the controller, the “@Autowired” annotations automatically assigns a valid instance of our defined repository in the corresponding field. This allows access to relevant data from within the controller without having to deal with a lot of boilerplate code.

Views

Finally, we need to define some templates for the views to be generated. For this, we are using Thymeleaf, a simple templating engine. The model we used in controller methods is available directly within the templates, i.e. when we enter a contract into “contract” key in a model, we will be able to access the name field as “contract.name” from within the template.

Thymeleaf contains some special elements and attributes that control the generation of HTML. They are very intuitive and straightforward. For example, to populate the contents of a span element with the name of a skill, all you need to do is define the following attribute (assuming that the key “skill” is defined in the model):

<span th:text="${skill.label}"></span>

Similarly to set the “href” attribute of an anchor element, the special attribute “th:href” can be used.

In our application, we will need two simple templates. For clarity, we will skip all style and class attributes (namely Bootstrap ones) here in the embedded template code.

Developer List Html with Thymelaf template

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Developers database</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Developers</h1>
<table>
<tr>
<th>Name</th>
<th>Skills</th>
<th></th>
</tr>
<tr th:each="developer : ${developers}">
<td th:text="${developer.firstName + ' ' + developer.lastName}"></td>
<td>
<span th:each="skill,iterStat : ${developer.skills}">
<span th:text="${skill.label}"/><th:block th:if="${!iterStat.last}">,</th:block>
</span>
</td>
<td>
<a th:href="@{/developer/{id}(id=${developer.id})}">view</a>
</td>
</tr>
</table>
<hr/>
<form th:action="@{/developers}" method="post" enctype="multipart/form-data">
<div>
First name: <input name="firstName" />
</div>
<div>
Last name: <input name="lastName" />
</div>
<div>
Email: <input name="email" />
</div>
<div>
<input type="submit" value="Create developer" name="button"/>
</div>
</form>
</body>
</html>

Developer Detail Html with Thymelaf template

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Developer</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Developer</h1>
Name: <b th:text="${developer.firstName}" /> <b th:text="${developer.lastName}" /><br/>
Email: <span th:text="${developer.email}" /><br/>
Skills:
<span th:each="skill : ${developer.skills}">
<br/>&nbsp;&nbsp;<span th:text="${skill.label}" /> - <span th:text="${skill.description}" />
</span>
<form th:action="@{/developer/{id}/skills(id=${developer.id})}" method="post" enctype="multipart/form-data" >
<select name="skillId">
<option th:each="skill : ${skills}"
th:value="${skill.id}"
th:text="${skill.description}">Skill</option>
</select>
<input type="submit" value="Add skill"/>
</form>
</body>
</html>

Running the Server

Spring contains a boot module. This allows us to start the server easily from the command line as a command-line Java application:

@SpringBootApplication
public class Application implements CommandLineRunner {

@Autowired
DeveloperRepository developerRepository;

@Autowired
SkillRepository skillRepository;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}

Since we are using an in-memory database, it makes sense to bootstrap the database with some predefined data at launch. That way we will have at least some data in the database when the server is up and running.

@Override
public void run(String... args) throws Exception {
Skill javascript = new Skill("javascript", "Javascript language skill");
Skill ruby = new Skill("ruby", "Ruby language skill");
Skill emberjs = new Skill("emberjs", "Emberjs framework");
Skill angularjs = new Skill("angularjs", "Angularjs framework");

skillRepository.save(javascript);
skillRepository.save(ruby);
skillRepository.save(emberjs);
skillRepository.save(angularjs);

List<Developer> developers = new LinkedList<Developer>();
developers.add(new Developer("John", "Smith", "john.smith@example.com",
Arrays.asList(new Skill[] { javascript, ruby })));
developers.add(new Developer("Mark", "Johnson", "mjohnson@example.com",
Arrays.asList(new Skill[] { emberjs, ruby })));
developers.add(new Developer("Michael", "Williams", "michael.williams@example.com",
Arrays.asList(new Skill[] { angularjs, ruby })));
developers.add(new Developer("Fred", "Miller", "f.miller@example.com",
Arrays.asList(new Skill[] { emberjs, angularjs, javascript })));
developers.add(new Developer("Bob", "Brown", "brown@example.com",
Arrays.asList(new Skill[] { emberjs })));
developerRepository.save(developers);
}

Conclusion

Spring is a versatile framework that allows building MVC applications. Building a simple application with Spring is quick and transparent. The application can also be integrated with a database easily using JPA.

Let’s Have Coffee & Code it

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store