Exploring Spring Controller with JSTL view
Let’s improve our previous Spring JDBC application with some more exploration on Spring MVC’s Controller development. I will show another exercise of writing a new Controller that processes a HTML form and use JSTL tags in JSP view pages.
To enable JSTL in Spring MVC application, you would need to add the following to the WebAppConfig
config class. Let’s move it outside of WebApp.java
and into it’s own top level class file in src/main/java/springweb/WebAppConfig.java
.
package springweb; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @EnableWebMvc @ComponentScan("springweb.controller") public class WebAppConfig { @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver result = new InternalResourceViewResolver(); result.setPrefix("/"); result.setSuffix(".jsp"); return result; } }
Inside the InternalResourceViewResolver
bean, you define where to find your JSP pages that may have JSTL tags in them. The prefix
setter is a path in relative to your src/webapp
location. This allow you to hide your JSP files completely if you want to. For example, by setting it to "/WEB-INF/jsp"
then you may move and store all JSP files into src/webapp/WEB-INF/jsp
which is private in the web application. The suffix
is just the file extension. These two values allow you to return a view name inside the controller with just the basename of your JSP file, which can be short as “/myform” or “/index” etc.
If you are to use Tomcat as your web container, you would need to add JSTL jar dependency as well, since the Tomcat server doesn’t come with standard tag library! So add this into the pom.xml
file now.
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>
While you’re at the pom.xml
file, you might want to add the Tomcat maven plugin so you can type less in command line when running your web application.
<project> ... <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> </plugin> </plugins> </build> ... </project>
With that, you should able to run mvn tomcat7:run
in root of your project without plugin prefix.
So what does JSTL bring to your application? Well, quite a bit actually. It lets you use some standard JSP tags that’s frequently used when writing JSP views. I will demonstrate this with a set of Controller and views to capture user comments from the application. Note that I will try to show you how to work with Spring Controller in the most basic way only. The Spring actually comes with a custom form
JSP tag that is much more powerful to use. I will reserve that as another article in another time. Today let us focus on learning more about basic Spring Controller and JSTL, and a bit more on Spring JDBC data service as well.
We want to capture user comment, so let’s add a database table to store that information. Add the following DDL into your src/main/resources/schema.sql
file. Again, this is for H2 database per last article project setup.
CREATE TABLE COMMENT ( ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, TEXT VARCHAR(10240) NOT NULL, FROM_USER VARCHAR(15) NULL, FROM_USER_IP VARCHAR(15) NULL, FROM_URL VARCHAR(1024) NULL, TAG VARCHAR(1024) NULL, TS DATETIME NOT NULL );
This time, we will write a data model class to match this table. Let’s add src/main/java/springweb/data/Comment.java
package springweb.data; import java.util.Date; public class Comment { Long id; String text; String fromUrl; String fromUser; String fromUserIp; String tag; Date ts; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getFromUrl() { return fromUrl; } public void setFromUrl(String fromUrl) { this.fromUrl = fromUrl; } public String getFromUser() { return fromUser; } public void setFromUser(String fromUser) { this.fromUser = fromUser; } public String getFromUserIp() { return fromUserIp; } public void setFromUserIp(String fromUserIp) { this.fromUserIp = fromUserIp; } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public Date getTs() { return ts; } public void setTs(Date ts) { this.ts = ts; } private String getTrimedComment(int maxLen) { if (text == null) return null; if (text.length() <= maxLen) return text; return text.substring(0, maxLen); } @Override public String toString() { return "Comment{" + "id=" + id + ", ts=" + ts + ", text='" + getTrimedComment(12) + '\'' + '}'; } public static Comment create(String commentText) { Comment result = new Comment(); result.setText(commentText); result.setTs(new Date()); return result; } }
Just as previous artcicle, we will write a data service to handle insert and retrieve of the data model. We add a new src/main/java/springweb/data/CommentService.java
file
package springweb.data; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import javax.sql.DataSource; import java.util.HashMap; import java.util.List; import java.util.Map; @Repository public class CommentService { public static Log LOG = LogFactory.getLog(CommentService.class); private JdbcTemplate jdbcTemplate; private SimpleJdbcInsert insertActor; private RowMapper<Comment> commentBeanRowMapper = new BeanPropertyRowMapper<Comment>(Comment.class); @Autowired public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("COMMENT") .usingGeneratedKeyColumns("ID"); } public void insert(Comment comment) { LOG.info("Inserting Comment + " + comment); Map<String, Object> parameters = new HashMap<String, Object>(2); parameters.put("TEXT", comment.getText()); parameters.put("FROM_USER", comment.getFromUser()); parameters.put("FROM_USER_IP", comment.getFromUserIp()); parameters.put("FROM_URL", comment.getFromUrl()); parameters.put("TAG", comment.getTag()); parameters.put("TS", comment.getTs()); Number newId = insertActor.executeAndReturnKey(parameters); comment.setId(newId.longValue()); LOG.info("New Comment inserted. Id=" + comment.getId()); } public List<Comment> findComments() { String sql = "SELECT " + "ID as id, " + "TEXT as text, " + "TAG as tag, " + "TS as ts, " + "FROM_USER as fromUser, " + "FROM_USER_IP as fromUserIp, " + "FROM_URL as fromUrl " + "FROM COMMENT ORDER BY TS"; List<Comment> result = jdbcTemplate.query(sql, commentBeanRowMapper); LOG.info("Found " + result.size() + " Comment records."); return result; } }
Since we did not use any fancy ORM but just plain JDBC, we will have to write SQL in the data service. But thanks to Spring goodies, it makes life much easier with helpers such as SimpleJdbcInsert
, which handles DB insert and retrieval of auto generated key etc. And also notice that in query, we use the Spring’s BeanPropertyRowMapper
to automatically convert JDBC resultset into a java bean Comment
object! Simple, direct and quick.
Now we add the Spring controller in src/main/java/springweb/controller/CommentController.java
to handle web requests.
package springweb.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import springweb.data.Comment; import springweb.data.CommentService; import javax.servlet.http.HttpServletRequest; import java.util.List; @Controller public class CommentController { @Autowired private CommentService commentService; @RequestMapping(value="/comments") public ModelAndView comments() { List<Comment> comments = commentService.findComments(); ModelAndView result = new ModelAndView("/comments"); result.addObject("comments", comments); return result; } @RequestMapping(value="/comment") public String comment() { return "comment"; } @RequestMapping(value="/comment", method = RequestMethod.POST) public ModelAndView postComment(HttpServletRequest req, @RequestParam String commentText) { String fromUrl = req.getRequestURI(); String user = req.getRemoteUser(); String userIp = req.getRemoteAddr(); Comment comment = Comment.create(commentText); comment.setFromUserIp(userIp); comment.setFromUser(user); comment.setFromUrl(fromUrl); commentService.insert(comment); ModelAndView result = new ModelAndView("comment-posted"); result.addObject("comment", comment); return result; } }
In this controller we map /comment
URL to handle display of the HTML form, which returns the comment.jsp
view. The method default to handle a HTTP GET
. Note that we remapped same /comment
URL to handle HTTP POST
on a separated postComment()
method! See how clean the Spring Controller can be in this demo to handle HTTP request. Pay very close attention to the parameters declared in the postComment()
method. Spring automatically handles the HTTP request object and map to your method just based upon the types you declared! On some cases you need to be explicit with the help of annotation such as @RequestParam
, but Spring does the parsing of HTTP request and extraction for you! This saves you tons of repeated boiler plate code if we were to write a direct Servlet code.
Now let’s see the view and how to use JSTL. The /comments
URL is mapped to src/main/webapp/comments.jsp
view file, which will list all Comment
model objects.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:choose> <c:when test="${empty comments}"> <p>There are no comments in system yet.</p> </c:when> <c:otherwise> <table border="1"> <tr> <td>INDEX</td> <td>TIME</td> <td>FROM</td> <td>COMMENT</td> </tr> <c:forEach items="${comments}" var="comment" varStatus="status"> <tr valign="top"> <td>${status.index}</td> <td>${comment.ts}</td> <td>${comment.fromUserIp}</td> <%-- The c:out will escape html/xml characters. --%> <td><pre><c:out value="${comment.text}"/></pre></td> </tr> </c:forEach> </table> </c:otherwise> </c:choose>
Pretty standard stuff on JSTL. Next is the HTML form to post comment in src/main/webapp/comment.jsp
file.
<form action="comment" method="POST"> <textarea name="commentText" rows="20" cols="80"></textarea> <br/> <input type="submit" value="Post"/> </form>
When form is posted and processed successful, we simply return to a new page in src/main/webapp/comment-posted.jsp
file as output.
<p>Your comment has been posted. Comment ID=${comment.id}</p>
If you’ve done these right, you should able to run mvn tomcat7:run
and browse http://localhost:8080/spring-web-annotation/comment
to see the form. Go to /comments
URL to verify all the comments posted.
Note that despite I used Spring Controller as backend, all the views are in basic JSTL, and even the form are just basic HTML elements! I did this so you can see how flexible Spring Controller can be.
I know these are lot of code to post as a blog article today, but I wanted to be complete and try to show a working demo with tutorial notes. I choose to make it into a single post with file content instead of having you download a project somewhere else. It makes my notes and explanation easier to match onto the code.
And that will conclude our tutorial for today. Please leave a note if you find this helpful.