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 -
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 -
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%-- 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%-- 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 -
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%@ 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%@ 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
-
- 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
Post a Comment