Spring with Kotlin - Spring MVC

I switched back to IntelliJ! Agree, Eclipse is used by many organizations because of its open-source nature, but given an option, I would always go for IntelliJ, period. Especially if you are using Kotlin, IntelliJ is an obvious choice to work with. As I continued my Spring & Hibernate course, the project setup is probably the biggest part as rightly pointed out by the instructor.

I am not going to bore you with project setup, you can just use this guide. In nutshell, I installed the Smart Tomcat plugin for IntelliJ and continued using JSP/JSTL as a View template. The course uses Eclipse with all the Spring projects as JAR dependencies. But it would be efficient if we just include the dependencies that we actually need and so I set up a Maven project and defined all the required dependencies in pom.xml - 

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax/javaee-api -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
view raw pom.xml hosted with ❤ by GitHub

With the basic project setup ready, now was time for framework-specific configs. Any Spring MVC project needs following 5 configurations steps before start writing actual code for Model, View, and Controller - 

    in WEB-INF/web.xml:

Step 1: Configure DispatcherServlet: DispatcherServlet is a front controller and is part of Spring Framework, already developed by Spring folks. We only need to configure it in the web.xml file.

Step 2: Setup URL mappings to DispatcherServlet: You need to map all incoming web requests to DispatcherServlet to handle.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="3.1">
<display-name>spring-mvc-demo</display-name>
<absolute-ordering />
<!-- Spring MVC Configs -->
<!-- Step 1: Configure Spring MVC Dispatcher Servlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc-demo-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Step 2: Set up URL mapping for Spring MVC Dispatcher Servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
view raw web.xml hosted with ❤ by GitHub

    in WEB-INF/spring-mvc-demo-servlet.xml file: Similar to the Spring Context file we discussed in the previous Spring Core blog. We need to use this to configure the remaining steps:

Step 3: Add support for Spring component scanning.

Step 4: Add support for conversion, formatting, validation of the form data.

Step 5: Configure Spring MVC view resolver: Points where the view pages are located with prefix and suffix.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!-- Step 3: Add support for component scanning -->
<context:component-scan base-package="com.springmvcone" />
<!-- Step 4: Add support for conversion, formatting and validation support -->
<mvc:annotation-driven/>
<!-- Step 5: Define Spring MVC view resolver -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- Addtional step - to read from properties file-->
<util:properties id="countryOptions" location="classpath:../countries.properties" />
</beans>

Note - There's an additional step to configure reading from a properties file.  

Now, note that this is all XML config. If we want to get rid of web.xml and spring-mvc-demo-servlet.xml, we can perform the same steps with Kotlin coding as well. For the purpose of this blog, we will stick to XML config.

Great, I was finally ready to code a few examples! I prepared the following code snippets along with random code comments/notes which are pretty much self-explanatory:

Example 1 - Reading HTML form data: Reading HTML form data is quite straightforward. Let's say you have a form with an input type text and a submit button in helloworld-form.jsp If you want to read the text in helloworld.jsp (confirmation page), you just need to call the name of the attribute in the text input field as ${param.name}.

Example 2 - Adding data to the Spring model: Let's say you want to modify text in your controller and then pass it to the confirmation page, it can be done in 2 ways. You can either use Java EE HttpServletRequest to get request information or simply use Spring Framework's @RequestParam annotation. Once you have the text, you can add it to the model which is passed along to the confirmation form.

both Example 1 and 2 are covered in the following code - 

<%-- helloworld-form.jsp --%>
<%-- You can use processFormVersionThree/processFormVersionTwo for form action --%>
<%-- We are using request method = GET for convenience --%>
<!DOCTYPE html>
<html>
<head>
<title>Hello World - Input Form</title>
</head>
<body>
<form action="processFormVersionThree" method="GET">
<input type="text" name="studentName" placeHolder="What's your name?" />
<input type="submit" />
</form>
</body>
</html>

<%-- helloworld.jsp --%>
<%-- ${param.studentName} reads the form data as it is --%>
<%-- ${message} attribute is what we add to the model --%>
<!DOCTYPE html>
<html>
<body>
Hello World of Spring!
<br>
Student Name : ${param.studentName}
<br>
Message : ${message}
</body>
</html>
view raw helloworld.jsp hosted with ❤ by GitHub

package com.springmvcone
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import javax.servlet.http.HttpServletRequest
@Controller
class HelloWorldController {
@RequestMapping("/")
fun mainMenu() : String {
return "main-menu"
}
@RequestMapping("/showForm")
fun showForm() : String {
return "helloworld-form"
}
// adding data to spring model from http request
@RequestMapping("/processFormVersionTwo")
fun letsShoutDude(request : HttpServletRequest, model : Model) : String {
val name : String = request.getParameter("studentName")
model.addAttribute("message", "Hey! ".plus(name.toUpperCase()))
return "helloworld"
}
// binding request params
@RequestMapping("/processFormVersionThree")
fun letsShoutDudeTwo(@RequestParam("studentName") name : String, model : Model) : String {
model.addAttribute("message", "Hey! ".plus(name.toUpperCase()))
return "helloworld"
}
}

Here's what form looks like on UI - 


Hit 'Submit' button and you can see confirmation page reading form data and model data 
 
Example 3 - Spring Form Tags and Data Binding: Spring's form tag library is integrated with Spring Web MVC, giving the tags access to the object and reference data your controller deals with. Form tags make JSPs easier to develop, read, and maintain.

Example 4 - Form validations: When you use Spring form tags, you can validate the fields by inbuilt annotations and custom annotations of your own.

both Example 3 and 4 are covered in the following code -

package com.springmvcone
import javax.validation.constraints.*
/*
Model class having student info params such as firstName, lastName, postalCode, Country, etc.
-lastName is being validated as Not Null and size more than 1 character
-postalCode is being vaidated with regex pattern that accepts only 5 characters/numbers
-gpa is being validated as Not Null and upper bound and lower bound limits
*/
class Student {
var firstName : String? = null
@NotNull(message = "is required")
@Size(message = "is required", min = 1)
var lastName : String? = null
@Pattern(regexp = "^[a-zA-Z0-9]{5}", message = "Only 5 chars/digits")
var postalCode : String? = null
var country : String? = null
var favoriteLang : String? = null
var operatingSystem : Array<String>? = null
@NotNull
@Min(value = 0, message = "GPA can't be less than 0")
@Max(value = 4, message = "GPA can't be more than 4")
var gpa : Float? = null
}
view raw Student.kt hosted with ❤ by GitHub

package com.springmvcone
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.validation.BindingResult
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.RequestMapping
import javax.validation.Valid
@Controller
@RequestMapping("/student")
class StudentController {
// loads Country options from countries.properties file
@Value("#{countryOptions}")
lateinit var countryMap : Map<String,String>
// returns student-form.jsp, need to map an empty Student object and countryMap to it.
@RequestMapping("/showForm")
fun showForm(theModel : Model) : String {
val student = Student()
theModel.addAttribute("student", student)
theModel.addAttribute("theCountryMap", countryMap)
return "student-form"
}
// BindingResult param should be placed nright after validated student model.
// If bindingResult has any error, student-form is loaded again with error details
// If there are no errors, student-cofirmtation page is loaded with filled info.
@RequestMapping("/processStudentForm")
fun processForm(@Valid @ModelAttribute("student") student : Student,
bindingResult : BindingResult, theModel: Model) : String {
if(bindingResult.hasErrors()) {
theModel.addAttribute("theCountryMap", countryMap)
return "student-form"
}
else
return "student-confirmation"
}

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Student Registration Form</title>
<style>
.error {
color: red
}
</style>
</head>
<body>
<form:form action="processStudentForm" modelAttribute="student">
First Name: <form:input path="firstName"/>
<br><br>
Last Name (*): <form:input path="lastName"/>
<form:errors path="lastName" cssClass="error" />
<br><br>
Country:
<form:select path="country">
<form:options items="${student.countryMap}"/>
</form:select>
<br><br>
Postal Code: <form:input path="postalCode"/>
<form:errors path="postalCode" cssClass="error" />
<br><br>
Favorite Language:
<form:radiobutton path="favoriteLang" value="Java" label="Java" />
<form:radiobutton path="favoriteLang" value="Kotlin" label="Kotlin" />
<form:radiobutton path="favoriteLang" value="Groovy" label="Groovy" />
<form:radiobutton path="favoriteLang" value="Scala" label="Scala" />
<br><br>
Operating Systems:
Linux <form:checkbox path="operatingSystem" value="Linux" />
MS Windows <form:checkbox path="operatingSystem" value="MS Windows" />
Mac OS <form:checkbox path="operatingSystem" value="Mac OS" />
<br><br>
GPA: <form:input path="gpa"/>
<form:errors path="gpa" cssClass="error" />
<br><br>
<input type="submit" value="Submit" />
</form:form>
</body>
</html>

<%@ taglib uri = "http://java.sun.com/jsp/jstl/core" prefix = "c" %>
<!DOCTYPE html>
<html>
<head>
<title>Student Registration Form</title>
</head>
<body>
Student is confirmed : ${student.firstName} ${student.lastName}
<br><br>
Country : ${student.country}
<br><br>
Postal Code : ${student.postalCode}
<br><br>
Favorite Language: ${student.favoriteLang}
<br><br>
Operating Systems:
<ul>
<c:forEach var="temp" items="${student.operatingSystem}">
<li> ${temp} </li>
</c:forEach>
</ul>
<br><br>
GPA: ${student.gpa}
</body>
</html>

BR=Brazil
IN=India
GE=Germany
FR=France

Input form on UI - 

If validations are passed, confirmation should show the following - 
If the form has validation errors, confirmation should show the following - 

Here are a few important points take away from my experience about coding in everything in Kotlin:

  • Don't use lateinit on your model class fields. It will throw InvalidPropertyException error while loading the form. Instead, use nullable types.
  • Avoid defining parameters in primary constructors of a Kotlin class as validation annotations won't work. You need to define parameters inside the class separately.
Thanks for reading and stay tuned for more!

Comments

Popular Posts