Spring MVC

Change the guestbook directory structure as shown below.
before_after

webapp/resources/

I made /webapp/resources as a directory for the static resources of the web application.
Static resources in a web application are images, stylesheets, JavaScript, and HTML files.

webapp/WEB-INF/views/

I made /webapp/WEB-INF/views as a directory for jsp files.
So I moved the guestbook.jsp file from /webapp to /webapp/WEB-INF/views/guestbook.

For Spring MVC, you need to do the following:

  • Adding Spring Dependencies to pom.xml
  • Add dispatcher servlet configuration to web.xml
  • [Add encoding filter to web.xml to support multiple languages]
  • Create a Spring configuration file based on the dispatcher servlet name (eg, dispatcher servlet name-servlet.xml)
pom.xml
<properties>
  <!--  omit -->
  <spring.version>5.2.1.RELEASE</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_3_1.xsd" version="3.1">
  
  <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>

To support multiple languages, I added a filter that encodes the request string in UTF-8.
I added the dispatcher servlet as guestbook and mapped it to '/'.
I removed the welcome-file-list setting and the guestbook servlet in web.xml.
The functionality of the removed servlet will be implemented in the Spring Controller method.

Since the dispatcher servlet is named guestbook, create a Spring configuration file named guestbook-servlet.xml in the location where the web.xml file is located.

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. It is a setting for handling requests for static content (stylesheets, images, JavaScript, etc.) in /webapp/resources or its subdirectories.
  2. This is the setting for driving the spring based on the annation.
  3. This setting is required to scan components from the specified package and automatically register them in the container.
  4. The 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 prifix 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.

In order for the controller to work properly, you should delete the servlets classes(GuestbookServlet.java, SignGuestbookServlet.java). You should also delete the Java classes for testing in the src/test/java/ directory.

Modify guestbook.jsp

Now that the style sheet file location has changed, modify the stylesheet path in guestbook.jsp as follows.

<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.
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