- Spring MVC Cookbook
- Alex Bretet
- 1669字
- 2021-07-16 13:03:21
Displaying a model in the View, using the JSTL
This recipe shows how to populate the Spring MVC View with data and how to render this data within the View.
Getting ready
At this point, we don't have any real data to be displayed in our View. For this purpose, we have created three DTOs and two service layers that are injected from their Interface into the controller.
There are two dummy service implementations that are designed to produce a fake set of data. We will use the Java Server Tags Library (JSTL) and the JSP Expression Language (JSP EL) to render the server data in the right places in our JSP.
How to do it...
- After checking out the
v2.x.x
branch (in the previous recipe), a couple of new components are now showing-up in the cloudstreetmarket-core module: two interfaces, two implementations, one enum, and three DTOs. The code is as follows:public interface IMarketService { DailyMarketActivityDTO getLastDayMarketActivity(String string); List<MarketOverviewDTO> getLastDayMarketsOverview(); } public interface ICommunityService { List<UserActivityDTO> getLastUserPublicActivity(int number); }
As you can see they refer to the three created DTOs:
public class DailyMarketActivityDTO { String marketShortName; String marketId; Map<String, BigDecimal> values; Date dateSnapshot; ... //and constructors, getters and setters } public class MarketOverviewDTO { private String marketShortName; private String marketId; private BigDecimal latestValue; private BigDecimal latestChange; ... //and constructors, getters and setters } public class UserActivityDTO { private String userName; private String urlProfilePicture; private Action userAction; private String valueShortId; private int amount; private BigDecimal price; private Date date; ... //and constructors, getters and setters }
This last DTO refers to the
Action
enum:public enum Action { BUY("buys"), SELL("sells"); private String presentTense; Action(String present){ presentTense = present; } public String getPresentTense(){ return presentTense; } }
Also, the previously created
DefaultController
incloudstreetmarket-webapp
has been altered to look like:@Controller public class DefaultController { @Autowired private IMarketService marketService; @Autowired private ICommunityService communityService; @RequestMapping(value="/*", method={RequestMethod.GET,RequestMethod.HEAD}) public String fallback(Model model) { model.addAttribute("dailyMarketActivity", marketService.getLastDayMarketActivity("GDAXI")); model.addAttribute("dailyMarketsActivity", marketService.getLastDayMarketsOverview()); model.addAttribute("recentUserActivity", communityService.getLastUserPublicActivity(10)); return "index"; } }
And there are the two dummy implementations:
@Service public class DummyMarketServiceImpl implements IMarketService { private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); public DailyMarketActivityDTO getLastDayMarketActivity(String string){ Map<String, BigDecimal> map = new HashMap<>(); map.put("08:00", new BigDecimal(9523)); map.put("08:30", new BigDecimal(9556)); ... map.put("18:30", new BigDecimal(9758)); LocalDateTime ldt = LocalDateTime.parse("2015-04-10 17:00", formatter); return new DailyMarketActivityDTO("DAX 30","GDAXI", map, Date.from(ldt.toInstant(ZoneOffset.UTC))); } @Override public List<MarketOverviewDTO> getLastDayMarketsOverview() { List<MarketOverviewDTO> result = Arrays.asList( new MarketOverviewDTO("Dow Jones-IA", "DJI", new BigDecimal(17810.06), new BigDecimal(0.0051)), ... new MarketOverviewDTO("CAC 40", "FCHI", new BigDecimal(4347.23), new BigDecimal(0.0267)) ); return result; } } @Service public class DummyCommunityServiceImpl implements ICommunityService { private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); public List<UserActivityDTO> getLastUserPublicActivity(int number){ List<UserActivityDTO> result = Arrays.asList( new UserActivityDTO("happyFace8", "img/young-lad.jpg", Action.BUY, "NXT.L", 6, new BigDecimal(3), LocalDateTime.parse("2015-04-10 11:12", formatter)), ... new UserActivityDTO("userB", null, Action.BUY, "AAL.L", 7, new BigDecimal(7), LocalDateTime.parse("2015-04-10 13:29", formatter)) ); return result; } }
The
index.jsp
has been altered with the addition of the following section below the graph container:<p class='morrisTitle'> <fmt:formatDate value="${dailyMarketActivity.dateSnapshot}" pattern="yyyy-MM-dd"/> </p> <select class="form-control centeredElementBox"> <option value="${dailyMarketActivity.marketId}"> ${dailyMarketActivity.marketShortName} </option> </select>
The market overview table, especially the body, has been added:
<c:forEach var="market" items="${dailyMarketsActivity}"> <tr> <td>${market.marketShortName}</td> <td style='text-align: right'> <fmt:formatNumber type="number" maxFractionDigits="3" value="${market.latestValue}"/> </td> <c:choose> <c:when test="${market.latestChange >= 0}"> <c:set var="textStyle" scope="page" value="text-success"/> </c:when> <c:otherwise> <c:set var="textStyle" scope="page" value="text-error"/> </c:otherwise> </c:choose> <td class='${textStyle}' style='text-align: right'> <b><fmt:formatNumber type="percent" maxFractionDigits="2" value="${market.latestChange}"/> </b> </td> </tr> </c:forEach>
The container for the community activity has been added:
<c:forEach var="activity" items="${recentUserActivity}"> <c:choose> <c:when test="${activity.userAction == 'BUY'}"> <c:set var="icoUpDown" scope="page" value="ico-up-arrow actionBuy"/> </c:when> <c:otherwise> <c:set var="icoUpDown" scope="page" value="ico-down- arrow actionSell"/> </c:otherwise> </c:choose> <c:set var="defaultProfileImage" scope="page" value=""/> <c:if test="${activity.urlProfilePicture == null}"> <c:set var="defaultProfileImage" scope="page" value="ico-user"/> </c:if> <li> <p class="itemTitle"> <p class="listUserIco ${defaultProfileImage}"> <c:if test="${activity.urlProfilePicture != null}"> <img src='${activity.urlProfilePicture}'> </c:if> </p> <span class="ico-white ${icoUpDown} listActionIco"></span> <a href="#">${activity.userName}</a> ${activity.userAction.presentTense} ${activity.amount} <a href="#">${activity.valueShortId}</a> at $${activity.price} <p class="itemDate"> <fmt:formatDate value="${activity.date}" pattern="dd/MM/yyyy hh:mm aaa"/> </p> </p> </li> </c:forEach>
At the bottom of the file, a hardcoded set of JavaScript data is now populated from the server:
<script> var financial_data = []; <c:forEach var="dailySnapshot" items="${dailyMarketActivity.values}"> financial_data.push({"period": '<c:out value="${dailySnapshot.key}"/>', "index": <c:out value='${dailySnapshot.value}'/>}); </c:forEach> </script> <script> $(function () { Morris.Line({ element: 'landingGraphContainer', hideHover: 'auto', data: financial_data, ymax: <c:out value="${dailyMarketActivity.maxValue}"/>, ymin: <c:out value="${dailyMarketActivity.minValue}"/>, pointSize: 3, hideHover:'always', xkey: 'period', xLabels: 'month', ykeys: ['index'], postUnits: '', parseTime: false, labels: ['Index'], resize: true, smooth: false, lineColors: ['#A52A2A'] }); }); </script>
How it works...
These changes don't produce fundamental UI improvements but they shape the data supply for our View layer.
The approach to handle our data
We are going to review here the server side of the data-supply implementation.
Injection of services via interfaces
Forecasting application needs to feed the frontpage in dynamic data, the choice has been made to inject two service layers marketService
and communityService
into the controller. The problem was that we don't yet have a proper Data Access layer. (This will be covered in Chapter 4, Building a REST API for a Stateless Architecture!). We need the controller to be wired to render the front page though.
Wiring the controller needs to be loosely coupled to its service layers. With the idea of creating dummy Service implementations in this chapter, the wiring has been designed using interfaces. We then rely on Spring to inject the expected implementations in the service dependencies, typed with the relevant Interfaces.
@Autowired private IMarketService marketService; @Autowired private ICommunityService communityService;
Note the types IMarketService
and ICommunityService
, which are not DummyCommunityServiceImpl
nor DummyMarketServiceImpl
. Otherwise, we would be tied to these types when switching to real implementations.
How does Spring choose the dummy implementations?
It chooses these implementations in the cloudstreetmarket-core Spring context file: csmcore-config.xml
. We have defined the beans earlier:
<context:annotation-config/> <context:component-scan base-package="edu.zipcloud.cloudstreetmarket.core" />
Spring scans all the types matching the root package edu.zipcloud.cloudstreetmarket.core
to find stereotypes and configuration annotations.
In the same way that DefaultController
is marked with the @Controller
annotation, our two dummy implementation classes are marked with @Service
, which is a Spring Stereotype. Among the detected stereotypes and beans, the dummy implementations are the only ones available for the injection configuration:
@Autowired private IMarketService marketService; @Autowired private ICommunityService communityService;
With only one respective match per field, Spring picks them up without any extra-configuration.
DTOs to be used in View layer
We have made use of DTOs for the variables fetched in our JSPs. Exposed DTOs can be particularly useful in web services when it comes to maintaining several versions simultaneously. More generally, DTOs are implemented when the target and destination objects differ significantly.
We will implement Entities later. It is better not to make use of these Entities in the rendering or version-specific logic, but instead defer them to a layer dedicated to this purpose.
Although, it must be specified that creating a DTO layer produces a fair amount of boilerplate code related to type conversion (impacting both sides, other layers, tests, and so on).
Dummy service implementations
The DummyMarketServiceImpl
implementation with the getLastDayMarketActivity
method builds an activity map (made of static daily times associated to values for the market, the index). It returns a new DailyMarketActivityDTO
instance (built from this map), it is in the end a wrapper carrying the daily activity for one single market or Index such as DAX 30.
The getLastDayMarketsOverview
method returns a list of MarketOverviewDTOs
also constructed from hardcoded data. It emulates an overview of daily activities for a couple of markets (indices).
The DummyCommunityServiceImpl
implementation with its getLastUserPublicActivity
method returns a list of instantiated UserActivityDTO
, which simulates the last six logged user activities.
Populating the Model in the controller
Presenting the possible method-handler arguments in the first recipe of this chapter, we have seen that it can be injected-as-argument a Model. This Model can be populated with data within the method and it will be transparently passed to the expected View.
That is what we have done in the fallback
method-handler. We have passed the three results from the Service layers into three variables dailyMarketActivity
, dailyMarketsActivity
, and recentUserActivity
so they can be available in the View.
Rendering variables with the JSP EL
The JSP Expression Language allows us to access application data stored in JavaBeans components. The notation ${…}
used to access variables such as ${recentUserActivity}
or ${dailyMarketActivity.marketShortName}
is typically a JSP EL notation.
An important point to remember when we want to access the attributes of an object (like marketShortName
for dailyMarketActivity
) is that the object class must offer JavaBeans standard getters for the targeted attributes.
In other words, dailyMarketActivity.marketShortName
refers in the MarketOverviewDTO
class to an expected:
public String getMarketShortName() { return marketShortName; }
Implicit objects
The JSP EL also offers implicit objects, usable as shortcuts in the JSP without any declaration or prepopulation in the model. Among these implicit objects, the different scopes pageScope, requestScope, sessionScope, and applicationScope reflect maps of attributes in the related scope.
For example, consider the following attributes:
request.setAttribute("currentMarket", "DAX 30"); request.getSession().setAttribute("userName", "UserA"); request.getServletContext().setAttribute("applicationState", "FINE");
These could respectively be accessed in the JSP with:
${requestScope["currentMarket"]} ${sessionScope["username"]} ${applicationScope["applicationState"]}
Other useful implicit objects are the map of request headers: header
(that is, ${header["Accept-Encoding"]}
), the map of request cookies: cookies
(that is, ${cookie["SESSIONID"].value}
), the map of request parameters: param
(that is, ${param["paramName"]}
) or the map of context initialization parameters (from web.xml
) initParam
(that is, ${initParam["ApplicationID"]}
).
Finally, the JSP EL provides a couple of basic operators:
- Arithmetic:
+
,-
(binary),*
,/
andp
,%
andmod
,-
(unary). - Logical:
and
,&&
,or
,||
,not
,!
. - Relational:
==
,eq
,!=
,ne
,<
,lt
,>
,gt
,<=
,ge
,>=
,le
.
Comparisons can be made against other values, or against Boolean, String, integer, or floating point literals.
- Empty: The empty operator is a prefix operation that can be used to determine whether a value is null or empty.
- Conditional:
A ? B : C
.
Evaluate B
or C
, depending on the result of the evaluation of A
.
This description of operators comes from the JavaEE 5 tutorial.
Rendering variables with the JSTL
The JSP Standard Tag Library (JSTL) is a collection of tools for JSP pages. It is not really a brand new feature of Java web but it is still used.
The tags the most used are probably Core and I18N when we need a display logic, or when we need to format data or to build a hierarchy in the View layer.
These presented tags are not the only capabilities of the JSTL, visit the Java EE tutorial for more details:
http://docs.oracle.com/javaee/5/tutorial/doc/bnakc.html
Taglib directives in JSPs
If we plan to make use of one or the other of the above tags, we first need to include the suited directive(s) in the JSP page:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
There's more...
More about JSP EL
There is more features covered by the JSP EL. Feel free to read the Oracle tutorials such as http://docs.oracle.com/javaee/5/tutorial/doc/bnahq.html.
More about the JavaBeans standard
We have talked about the expected JavaBean standard when using the JSP EL. More information about JavaBeans can be found in the Oracle tutorial again:
http://docs.oracle.com/javaee/5/tutorial/doc/bnair.html
More about the JSTL
As announced, you can discover more modules of the JSTL on the Java EE tutorial:
- JBoss Weld CDI for Java Platform
- Cocos2d Cross-Platform Game Development Cookbook(Second Edition)
- Android開發精要
- Rust編程:入門、實戰與進階
- 深入淺出Java虛擬機:JVM原理與實戰
- Learning Elixir
- Hadoop+Spark大數據分析實戰
- 零基礎學Java程序設計
- Instant RubyMotion App Development
- Visual Basic程序設計實踐教程
- Tableau 10 Bootcamp
- 面向對象程序設計及C++(第3版)
- 從零開始學算法:基于Python
- Splunk Essentials
- Python面向對象編程(第4版)