Spring MVC

Change the guestbook directory structure by referring to the below.

before_after

webapp/resources/

/webapp/resources is the directory for our web application's static resources. The static resources of a web application are images, style sheets, JavaScript, and HTML files.

webapp/WEB-INF/views/

/webapp/WEB-INF/views is the directory for JSP files. Note that the guestbook.jsp file has been moved to /webapp/WEB-INF/views/guestbook.

For Spring MVC projects, you need to do the following:

  • Adding Spring Dependencies to pom.xml
  • Adding dispatcher servlet configuration to web.xml
  • [Adding encoding filter to web.xml to support multiple languages]
  • Creating a Spring configuration file based on the dispatcher servlet name (e.g., "dispatcher servlet name"-servlet.xml)
pom.xml
<properties>
  <!--  omit -->
  <spring.version>5.3.33</spring.version>
</properties>

<!--  omit -->

<!-- [START Spring_Dependencies] -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>${spring.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>${spring.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>${spring.version}</version>
</dependency>
<!-- [END Spring_Dependencies] -->

<!--  omit -->
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0">
  
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <!-- [START Objectify] -->
	
  <!--  omit -->
    
  <!-- [END Objectify] -->
  <servlet>
    <servlet-name>guestbook</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>guestbook</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

Added to the guestbook archetype's web.xml

CharacterEncodingFilter: It encodes the request string in UTF-8 to support multiple languages.

DispatcherServlet: Named guestbook in servlet declaration and mapped to / in servlet mapping.

Removed from the guestbook archetype's web.xml

  • The welcome-file-list element
  • The guestbook servlet declaration and its mapping

We will implement the functionality of the guestbook servlet in a method of a Spring Controller.

Since the name of the dispatcher servlet is a guestbook, create a Spring configuration file called guestbook-servlet.xml in the location of the web.xml file.

guestbook-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:mvc="http://www.springframework.org/schema/mvc" 
  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">
        
  <!--1.Handling requests for static resources-->    
  <mvc:resources mapping="/resources/**" location="/resources/" />
  <!--2.To drive the spring based on the annation-->
  <mvc:annotation-driven />
  <!--3.Component scan-->
  <context:component-scan base-package="net.java_school.guestbook" />
  <!--4.View resolver-->
  <bean id="internalResourceViewResolver" 
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="viewClass">
        <value>org.springframework.web.servlet.view.JstlView</value>
      </property>
      <property name="prefix">
        <value>/WEB-INF/views/</value>
      </property>
      <property name="suffix">
        <value>.jsp</value>
      </property>
  </bean>
</beans>
  1. This configuration causes the static content handler inside Spring to handle requests for static content (stylesheets, images, JavaScript, HTML, etc.) located in /webapp/resources or subdirectories.
  2. This configuration drives the Spring based on the annotation.
  3. This configuration causes Spring to scan components from the specified package and automatically register them in the container.
  4. This configuration is for a view resolver. A view resolver interprets the string from the controller to determine the view. If the controller returns "guestbook/guestbook," the view resolver set above will use prefix and suffix to interpret the view as /WEB-INF/views/guestbook/guestbook.jsp.

Create a controller dedicated to the guestbook.

GuestbookController.java
package net.java_school.guestbook;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.googlecode.objectify.ObjectifyService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class GuestbookController {

  //1."/guestbook" reqeust handler
  @GetMapping("/guestbook")
  public String guestbook(String guestbookName, Model model) {
    model.addAttribute("guestbookName", guestbookName);
    return "guestbook/guestbook";
  }
	
  //2."/guestbook/sign" request handler
  @PostMapping("/guestbook/sign")
  public String sign(HttpServletRequest req, HttpServletResponse resp) {
    Greeting greeting;
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    String guestbookName = req.getParameter("guestbookName");
    String content = req.getParameter("content");
    if (user != null) {
      greeting = new Greeting(guestbookName, content, user.getUserId(), user.getEmail());
    } else {
      greeting = new Greeting(guestbookName, content);
    }
    // Use Objectify to save the greeting and now() is used to make the call synchronously as we
    // will immediately get a new page using redirect and we want the data to be present.
    ObjectifyService.ofy().save().entity(greeting).now();
    return "redirect:/guestbook/?guestbookName=" + guestbookName;
  }
}
  1. The guestbook() method is responsible for the /guestbook request of the GET method.
  2. The sign() method is responsible for the /guestbook/sign request of the POST method. The implementation of the sign() method is the same as that of the SignGuestbookServlet.java servlet.

For the controller to work correctly, you should delete the servlets classes(GuestbookServlet.java, SignGuestbookServlet.java). You should also delete the Java classes for tests in the src/test/java/ directory.

Modify guestbook.jsp

Because the style sheet file location has changed, modify the stylesheet path in the guestbook.jsp like below.

<link type="text/css" rel="stylesheet" href="/resources/stylesheets/main.css"/>  

Modify the form action attribute in guestbook.jsp to match the request handler in GuestbookController as follows.

<form action="/guestbook/sign" method="post">
<form action="/guestbook" method="get">

Since the location of guestbook.jsp has changed, the methods createLogoutURL() and createLoginURL() in guestbook.jsp will return /WEB-INF/views/guestbook/guestbook.jsp and eventually occur a 404 error. So, modify the arguments of the two methods from request.getRequestURI() to "/guestbook/?GuestbookName=" + guestbookName.

<p>Hello, ${fn:escapeXml(user.nickname)}! (You can
  <a href="<%= userService.createLogoutURL("/guestbook/?guestbookName=" + guestbookName) %>">sign out</a>.)</p>
<%
  } else {
%>
<p>Hello!
  <a href="<%= userService.createLoginURL("/guestbook/?guestbookName=" + guestbookName) %>">Sign in</a>
  to include your name with greetings you post.</p>
<%
  }
%>

To run an app in the Java 8 environment, you need the following configuration in appengine-web.xml:

<runtime>java8</runtime>

Local Test

mvn clean
mvn appengine:run

Visit http://localhost:8080/guestbook.

Remote Test

mvn appengine:deploy

Visit http://your-app-id.appspot.com/guestbook.

References