您的位置:首页 > 移动开发

Spring 3 maven 2 web application step by step

2013-12-03 07:49 344 查看


Before writting this I admin that there are lots of tutorials like this but what I found is that they are not step by step guides, with missing vital parts of process of making it all happen
from scratch. This makes you revert to the documentation and therefore defies the point of going through the tutorial. With this one I hope to make it a bit clearer, though I do not attempt to make it IDE specific, therefore there will be no guidance on how
to do it in specific IDE. The layout will be to work with standard Maven project structure.

What is in this tutorial?

Maven Spring project configurations
Persistence using Hibernate and HSQLDB
Cache support using Ehcahe
Spring MVC configurations
Example integration tests for DB and Caching (Please not there are no Unit tests as I assume that the reader is familiar with those already)

Link to the full set of sources for this tutorial:

denis-pavlov-spring3mvc-mvn2-example-updated.zip

First things first - the POM:

view
plaincopy
to clipboardprint?

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelversion>4.0.0</modelversion>

<groupid>dp.examples</groupid>

<artifactid>shoppingcart</artifactid>

<version>0.0.1-SNAPSHOT</version>

<packaging>war</packaging>

<name>ShoppingCart</name>

<build>

<plugins>

<plugin>

<artifactid>maven-compiler-plugin</artifactid>

<configuration>

<source>1.5

<target>1.5</target>

</configuration>

</plugin>

</plugins>

</build>

<!-- Shared version number properties -->

<properties>

<org.springframework.version>3.1.2.RELEASE</org.springframework.version>

<commons-dbcp.version>1.4</commons-dbcp.version>

<sqlite-jdbc.version>3.7.2</sqlite-jdbc.version>

<hsqldb-jdbc.version>2.2.9</hsqldb-jdbc.version>

<hibernate-core.version>4.1.9.Final</hibernate-core.version>

<hibernate-search.version>4.2.0.Final</hibernate-search.version>

<xalan.version>2.7.0</xalan.version>

<backport-util-concurrent.version>3.1</backport-util-concurrent.version>

<ehcache.version>2.6.6</ehcache.version>

</properties>

<dependencies>

<dependency>

<groupid>log4j</groupid>

<artifactid>log4j</artifactid>

<version>1.2.15</version>

<exclusions>

<exclusion>

<groupid>javax.mail</groupid>

<artifactid>mail</artifactid>

</exclusion>

<exclusion>

<groupid>javax.jms</groupid>

<artifactid>jms</artifactid>

</exclusion>

<exclusion>

<groupid>com.sun.jdmk</groupid>

<artifactid>jmxtools</artifactid>

</exclusion>

<exclusion>

<groupid>com.sun.jmx</groupid>

<artifactid>jmxri</artifactid>

</exclusion>

</exclusions>

<scope>runtime</scope>

</dependency>

<!--

JSP taglib support

-->

<dependency>

<groupid>taglibs</groupid>

<artifactid>standard</artifactid>

<version>1.1.2</version>

</dependency>

<dependency>

<groupid>javax.servlet</groupid>

<artifactid>jstl</artifactid>

<version>1.2</version>

</dependency>

<!--

Core utilities used by other modules.

Define this if you use Spring Utility APIs (org.springframework.core.*/org.springframework.util.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-core</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Expression Language (depends on spring-core)

Define this if you use Spring Expression APIs (org.springframework.expression.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-expression</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Bean Factory and JavaBeans utilities (depends on spring-core)

Define this if you use Spring Bean APIs (org.springframework.beans.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-beans</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Aspect Oriented Programming (AOP) Framework (depends on spring-core, spring-beans)

Define this if you use Spring AOP APIs (org.springframework.aop.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-aop</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Application Context (depends on spring-core, spring-expression, spring-aop, spring-beans)

This is the central artifact for Spring's Dependency Injection Container and is generally always defined

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-context</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Various Application Context utilities, including EhCache, JavaMail, Quartz, and Freemarker integration

Define this if you need any of these integrations

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-context-support</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Transaction Management Abstraction (depends on spring-core, spring-beans, spring-aop, spring-context)

Define this if you use Spring Transactions or DAO Exception Hierarchy

(org.springframework.transaction.*/org.springframework.dao.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-tx</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

JDBC Data Access Library (depends on spring-core, spring-beans, spring-context, spring-tx)

Define this if you use Spring's JdbcTemplate API (org.springframework.jdbc.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-jdbc</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Object-to-Relation-Mapping (ORM) integration with Hibernate, JPA, and iBatis.

(depends on spring-core, spring-beans, spring-context, spring-tx)

Define this if you need ORM (org.springframework.orm.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-orm</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Object-to-XML Mapping (OXM) abstraction and integration with JAXB, JiBX, Castor, XStream, and XML Beans.

(depends on spring-core, spring-beans, spring-context)

Define this if you need OXM (org.springframework.oxm.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-oxm</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Web application development utilities applicable to both Servlet and Portlet Environments

(depends on spring-core, spring-beans, spring-context)

Define this if you use Spring MVC, or wish to use Struts, JSF, or another web framework with Spring (org.springframework.web.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-web</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Spring MVC for Servlet Environments (depends on spring-core, spring-beans, spring-context, spring-web)

Define this if you use Spring MVC with a Servlet Container such as Apache Tomcat (org.springframework.web.servlet.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-webmvc</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Spring MVC for Portlet Environments (depends on spring-core, spring-beans, spring-context, spring-web)

Define this if you use Spring MVC with a Portlet Container (org.springframework.web.portlet.*)

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-webmvc-portlet</artifactid>

<version>${org.springframework.version}</version>

</dependency>

<!--

Support for testing Spring applications with tools such as JUnit and TestNG

This artifact is generally always defined with a 'test' scope for the integration testing framework and unit testing stubs

-->

<dependency>

<groupid>org.springframework</groupid>

<artifactid>spring-test</artifactid>

<version>${org.springframework.version}</version>

<scope>test</scope>

</dependency>

<!-- hibernate -->

<dependency>

<groupid>org.hibernate</groupid>

<artifactid>hibernate-core</artifactid>

<version>${hibernate-core.version}</version>

<exclusions>

<exclusion>

<groupid>xml-apis</groupid>

<artifactid>xml-apis</artifactid>

</exclusion>

</exclusions>

</dependency>

<dependency>

<groupid>xalan</groupid>

<artifactid>xalan</artifactid>

<version>${xalan.version}</version>

</dependency>

<!-- dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-search</artifactId>

<version>${hibernate-search.version}</version>

</dependency -->

<!-- connection pooling -->

<dependency>

<groupid>commons-dbcp</groupid>

<artifactid>commons-dbcp</artifactid>

<version>${commons-dbcp.version}</version>

<scope>runtime</scope>

</dependency>

<!-- HSQLDB JDBC library -->

<dependency>

<groupid>org.hsqldb</groupid>

<artifactid>hsqldb</artifactid>

<version>${hsqldb-jdbc.version}</version>

<scope>runtime</scope>

</dependency>

<!-- EhCache -->

<dependency>

<groupid>net.sf.ehcache</groupid>

<artifactid>ehcache-core</artifactid>

<version>${ehcache.version}</version>

</dependency>

<!-- Unit and integration testing -->

<dependency>

<groupid>junit</groupid>

<artifactid>junit</artifactid>

<version>4.9</version>

<scope>test</scope>

</dependency>

</dependencies>

</project>

At this point we have done all we need maven wise and can safely proceed to next step.

BTW if you are using eclipse and started of with a maven2 project and now trying to convert it to web projects I think you need to read this

We are making a webapp, so the second thing is web.xml (That should be in the src/main/webapp/WEB-INF/):

view
plaincopy
to clipboardprint?

<!--?xml version="1.0" encoding="UTF-8"?-->

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>

<param-name>log4jConfigLocation</param-name>

<param-value>classpath:META-INF/properties/log4j.properties</param-value>

</context-param>

<listener>

<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>

</listener>

<!-- Spring context -->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath*:WEB-INF/spring/*.xml</param-value>

</context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<!-- Spring webapp -->

<servlet>

<servlet-name>spring</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/spring/*.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>spring</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>

index.jsp

</welcome-file>

</welcome-file-list>

</web-app>

Then the Spring context configuration (for webapp that is located in WEB-INF/spring/ directory as specified in web.xml parameters to the servlet):

view
plaincopy
to clipboardprint?

<!--?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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" 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/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<property name="locations">

<list>

<!-- You can have multiple files here -->

<value>classpath:spring-context.properties</value>

</list>

</property>

</bean>

<import resource="classpath*:spring/cache.xml">

<import resource="classpath*:spring/persistence.xml">

<!-- Service to manipulate the cart -->

<bean id="cartService" parent="txProxyTemplate">

<property name="target">

<bean class="dp.example.shoppingcart.service.impl.WebCartServiceCached">

<constructor-arg index="0" ref="cartDao">

</constructor-arg></bean>

</property>

</bean>

<!-- Scans the classpath of this application for @Components to deploy as beans -->

<context:component-scan base-package="dp.example.shoppingcart.web">

<!-- Configures the @Controller programming model -->

<mvc:annotation-driven>

<!-- Forwards requests to the "/" resource to the "welcome" view -->

<mvc:view-controller path="/" view-name="cart">

<!-- Configures Handler Interceptors -->

<mvc:interceptors>

<!-- Changes the locale when a 'locale' request parameter is sent; e.g. /?locale=de -->

<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">

</bean></mvc:interceptors>

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->

<mvc:resources mapping="/resources/**" location="/resources/">

<!-- Saves a locale change using a cookie -->

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

<!-- Application Message Bundle -->

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

<property name="basename" value="/WEB-INF/messages/messages">

<property name="cacheSeconds" value="0">

</property></property></bean>

<!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/views/">

<property name="suffix" value=".jsp">

</property></property></bean>

</bean></mvc:resources></mvc:view-controller></mvc:annotation-driven></context:component-scan></import></import></beans>

The main context file contains two imports: spring/cache.xml that contains ehcache configuration and spring/persistence.xml that contains hibernate configuration

view
plaincopy
to clipboardprint?

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:util="http://www.springframework.org/schema/util" xsi:schemalocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
<cache:annotation-driven>

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cachemanager-ref="ehcache">

<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configlocation="classpath:ehcache.xml" p:shared="true">

</bean></bean></cache:annotation-driven></beans>

Note the p:shared="true" attribute. This ensures that we are using singleton manager for the Ehcache, which is important if you would like to use the same cache manager for your webapp and your hibernate 2nd level cache. "p:configLocation="classpath:ehcache.xml"
tells Spring that we what to use the src/main/resources/ehcache.xml for our cache configurations

view
plaincopy
to clipboardprint?

<ehcache updatecheck="false">

<defaultcache maxelementsinmemory="10000" overflowtodisk="false" eternal="false" timetoidleseconds="1800" timetoliveseconds="3600">

<!-- ############################################# cluster cache #######################################-->

<cache name="cart" maxelementsinmemory="100" overflowtodisk="false" eternal="false" timetoliveseconds="86400" timetoidleseconds="7200">

</cache></defaultcache></ehcache>

Persistence condifuration is quite simple. We setup dataSource first. Note that we use Spring variable substitution, which is defined by propertyConfigurer bean in context.xml that takes values from spring-context.properties. Then we define the hibernate factory
and txProxyTemplate, which is used by transactional beans. It is good practice to manage transaction boundaries on the service layer. I.e. each method of service beans represents a unit of work and hence is a good candidate for a single transaction. For that
purpose our Data access object is specifically not marked as transactional to prevent using it directly and hence have transaction per single db operation, which is not optimal. Instead we will make our service (WebCartServiceCached) transactional which will
be using DAO and hence the transaction will be propagated to DAO.

view
plaincopy
to clipboardprint?

<!--?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:util="http://www.springframework.org/schema/util" xmlns:ctx="http://www.springframework.org/schema/context" xsi:schemalocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

<property name="driverClassName" value="${db.driverClassName}">

<property name="url" value="${db.url}">

<property name="username" value="${db.username}">

<property name="password" value="${db.password}">

</property></property></property></property></bean>

<util:properties id="hibernateProperties" location="classpath:hibernate/hibernate-hsqldb.properties">

<!-- Hibernate ******************************************************************* -->

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">

<property name="dataSource" ref="dataSource">

<property name="hibernateProperties" ref="hibernateProperties">

<property name="mappingResources">

<list>

<value>hibernate/mapping.hbm.xml</value>

</list>

</property>

</property></property></bean>

<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory">

</property></bean>

<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

<description>

NOTE: see org.springframework.transaction.TransactionDefinition

DAO: all beans must be wrapped into this proxy to allow seamless

transaction management integration via AOP.

</description>

<property name="transactionManager" ref="transactionManager">

<property name="transactionAttributes">

<props>

<!--<prop key="add*">PROPAGATION_REQUIRED,-Throwable</prop>-->

<!--<prop key="remove*">PROPAGATION_REQUIRED,-Throwable</prop>-->

<prop key="*">PROPAGATION_REQUIRED,-Throwable</prop>

</props>

</property>

</property></bean>

<bean id="cartDao" class="dp.example.shoppingcart.dao.impl.CartDaoImpl">

<constructor-arg index="0" ref="sessionFactory">

</constructor-arg></bean>

</util:properties></beans>

DAO object is very simple and uses Hibernates current session which is provided by txProxyTemplate from WebCartService to perform DB operations. Note that if we to use DAO bean directly we would have a Hibernate exception and no session is available standalone.

view
plaincopy
to clipboardprint?

package dp.example.shoppingcart.dao.impl;

import dp.example.shoppingcart.dao.CartDao;

import dp.example.shoppingcart.domain.impl.CartEntity;

import dp.example.shoppingcart.dto.Cart;

import dp.example.shoppingcart.dto.Item;

import dp.example.shoppingcart.service.CartService;

import org.hibernate.SessionFactory;

import java.util.List;

/**

* Web implementation of cart service

*

* @author DPavlov

*/

public class CartDaoImpl implements CartDao

{

private final SessionFactory sessionFactory;

public CartDaoImpl(final SessionFactory sessionFactory) {

this.sessionFactory = sessionFactory;

}

public Cart createCart() {

final Cart cart = new CartEntity();

save(cart);

return cart;

}

public Cart findCart(final long pk) {

return (Cart) sessionFactory.getCurrentSession().get(CartEntity.class, pk);

}

public void save(final Cart cart) {

sessionFactory.getCurrentSession().save(cart);

}

}

Our service uses DAO to provide business functions represented by WebCartServiceCached methods. For example #addToCart(final long pk, String item) will either find or create cart with specific primary key, add and item and persist the cart. All these db operations
will happen in a single transaction. And if we have an exception all these changes are rolled back. So we have a good "all or nothing" strategy that will keep our db in a consistent state.

Caching is very simple. Facilitated by two annotations: @Cacheable(value = "cart") and @CacheEvict(value = "cart", allEntries = false, key = "#pk"). There are lots of configurations for this but this is the basic setup. @Cacheable tells ehcache to use cache
named "cart" (this is why we have this entry in ehcache.xml) to keep results of this method. @CacheEvict allows to remove cache entries. In this case we remove a single entry by key 'pk', but it is possible to evict all items. As I already said it is very
configurable.

view
plaincopy
to clipboardprint?

package dp.example.shoppingcart.service.impl;

import dp.example.shoppingcart.dao.CartDao;

import dp.example.shoppingcart.dto.Cart;

import dp.example.shoppingcart.dto.Item;

import dp.example.shoppingcart.service.CartService;

import org.springframework.cache.annotation.CacheEvict;

import org.springframework.cache.annotation.Cacheable;

import java.util.List;

/**

* Web implementation of cart service

*

* @author DPavlov

*/

public class WebCartServiceCached implements CartService

{

private final CartDao cartDao;

public WebCartServiceCached(final CartDao cartDao) {

this.cartDao = cartDao;

}

@CacheEvict(value = "cart", allEntries = false, key = "#pk")

public void addToCart(final long pk, String item) {

final Cart cart = findOrCreateCart(pk);

cart.addItem(item);

cartDao.save(cart);

}

@Cacheable(value = "cart")

public List<item> getItemsInCart(final long pk) {

final Cart cart = findOrCreateCart(pk);

return cart.getItems();

}

@CacheEvict(value = "cart", allEntries = false, key = "#pk")

public void removeFromCart(final long pk, String item) {

final Cart cart = findOrCreateCart(pk);

cart.removeItem(item);

cartDao.save(cart);

}

private Cart findOrCreateCart(final long pk) {

Cart cart = cartDao.findCart(pk);

if (cart == null) {

cart = cartDao.createCart();

}

return cart;

}

}

</item>

Basic controller uses @RequestMapping and @PathVariable to give a nice looking and SEO friendly url pattern.

view
plaincopy
to clipboardprint?

package dp.example.shoppingcart.web;

import dp.example.shoppingcart.dto.Item;

import dp.example.shoppingcart.service.CartService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.servlet.ModelAndView;

import java.util.List;

/**

* Basic controller

*

* @author DPavlov

*/

@Controller

public class CartController {

private CartService cartService;

private int counter = 1;

@Autowired

public CartController(CartService cartService) {

this.cartService = cartService;

}

@RequestMapping(value = "/cart/{pk}", method = RequestMethod.GET)

public ModelAndView getCart(@PathVariable final long pk) {

final List<item> list = cartService.getItemsInCart(pk);

ModelAndView mav = new ModelAndView("cart");

mav.addObject("pk", pk);

mav.addObject("items", list);

mav.addObject("add", "Item" + counter++);

return mav;

}

@RequestMapping(value = "/cart/{pk}/addtocart/{item}", method = RequestMethod.GET)

public String addToCart(@PathVariable final long pk, @PathVariable final String item) {

cartService.addToCart(pk, item);

return "redirect:/cart/" + pk; // Do redirect to prevent double post

}

@RequestMapping(value = "/cart/{pk}/removefromcart/{item}", method = RequestMethod.GET)

public String removeFromCart(@PathVariable final long pk, @PathVariable final String item) {

cartService.removeFromCart(pk, item);

return "redirect:/cart/" + pk; // Do redirect to prevent double post

}

}

</item>

Basic view (if you look at the spring context.xml file you will see that we are using spring resolver that look for views in WEB-INF/views/ and uses view name as file name with extension .jsp):

view
plaincopy
to clipboardprint?

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ page isELIgnored="false" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<title>Items</title>

<h3>Items:</h3>

<c:foreach var="item" items="${items}">

<div>${item.articleNo} @ ${item.price}, qty: ${item.quantity} <a href="${pageContext.request.contextPath}/cart/${pk}/removefromcart/${item.articleNo}">[-] remove</a> <a href="${pageContext.request.contextPath}/cart/${pk}/addtocart/${item.articleNo}">[+] add</a></div>

</c:foreach>

<a href="${pageContext.request.contextPath}/cart/${pk}/addtocart/${add}">add "${add}"</a>

${pageContext.request.contextPath} in jsp allows to reference the root of your webapp. This is very usefull if your webapp context name changes from environment to environment as this kind of url building approach will work everywhere consistently. Now if you
navigate to localhost:8080/cart/1 you will see contents for cart with PK 1.

Here is screenshot of the outcome:



This sums up the basic Spring webapp project. Now we can have a look at how we can do integration tests for what we have written.

Firstly we need an alternative Spring context where we can remove all the MVC part so we can use it for core testing (i.e. services, dao). Note: that I am jumping to integration tests straight away since I assume that you know (and already done) unit tests.
Integration tests is the final frontier! Before doing such tests appropriate unit test needs to be done! Please do not substitute these kind of tests for unit tests as they are considerably slower to run and much harder to test thoroughly.

view
plaincopy
to clipboardprint?

package dp.example.shoppingcart.service.impl;

import dp.example.shoppingcart.dto.Item;

import dp.example.shoppingcart.service.CartService;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import static org.junit.Assert.*;

/**

* User: denispavlov

* Date: 29/11/2013

* Time: 13:17

*/

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = { "/spring/testContext.xml" })

@Transactional

public class WebCartServiceCachedTest {

@Autowired

private CartService cartService;

/**

* This is NOT unit test but rather an integration suite that proves that

* our business logic works as expected using actual db and all the wiring.

*/

@Test

public void testCartCRUDOperationsIntegrationTest() throws Exception {

List<item> items;

// Check empty initially

items = cartService.getItemsInCart(1L);

assertNotNull(items);

assertTrue(items.isEmpty());

// Add one item and check contents of cart

cartService.addToCart(1L, "ABC");

items = cartService.getItemsInCart(1L);

assertNotNull(items);

assertFalse(items.isEmpty());

assertEquals(1, items.size());

assertEquals("ABC", items.get(0).getArticleNo());

assertEquals(new BigDecimal(1).compareTo(items.get(0).getQuantity()), 0);

// Add same item and check contents of cart

cartService.addToCart(1L, "ABC");

items = cartService.getItemsInCart(1L);

assertNotNull(items);

assertFalse(items.isEmpty());

assertEquals(1, items.size());

assertEquals("ABC", items.get(0).getArticleNo());

assertEquals(new BigDecimal(2).compareTo(items.get(0).getQuantity()), 0);

// Add another item and check contents of cart

cartService.addToCart(1L, "DEF");

items = cartService.getItemsInCart(1L);

assertNotNull(items);

assertFalse(items.isEmpty());

assertEquals(2, items.size());

// we re-map the items in list to have deterministic assertions

final Map<string, item=""> byArticle = new HashMap<string, item="">();

for (final Item item : items) {

byArticle.put(item.getArticleNo(), item);

}

// deterministic assertions by article no

assertTrue(byArticle.containsKey("ABC"));

assertEquals(new BigDecimal(2).compareTo(byArticle.get("ABC").getQuantity()), 0);

assertTrue(byArticle.containsKey("DEF"));

assertEquals(new BigDecimal(1).compareTo(byArticle.get("DEF").getQuantity()), 0);

// Remove one item

cartService.removeFromCart(1L, "DEF");

items = cartService.getItemsInCart(1L);

assertNotNull(items);

assertFalse(items.isEmpty());

assertEquals(1, items.size());

assertEquals("ABC", items.get(0).getArticleNo());

assertEquals(new BigDecimal(2).compareTo(items.get(0).getQuantity()), 0);

// Remove last item

cartService.removeFromCart(1L, "ABC");

items = cartService.getItemsInCart(1L);

assertNotNull(items);

assertTrue(items.isEmpty());

}

/**

* Since we already established that the CRUD works assertions in this test are specific

* to making sure that we get the cached list back, so we do not need to check individual elements.

*

* @throws Exception

*/

@Test

public void testCachingIsWorking() throws Exception {

// Check empty initially

List<item> items1st = cartService.getItemsInCart(2L);

assertEquals(0, items1st.size());

// add cart item

cartService.addToCart(2L, "ABC");

List<item> items2nd = cartService.getItemsInCart(2L);

assertEquals(1, items2nd.size());

// now we can ask for the cart contents again and assert that is it same instance of list

List<item> items3rd = cartService.getItemsInCart(2L);

assertSame(items3rd, items2nd);

// we expect that adding items will evict cache

cartService.addToCart(2L, "ABC");

List<item> items4th = cartService.getItemsInCart(2L);

assertEquals(1, items4th.size());

// but these list should be different now

assertNotSame(items2nd, items4th);

// now check the cart contents again which should be cached object from 4th call

List<item> items5th = cartService.getItemsInCart(2L);

assertSame(items4th, items5th);

// make sure that after adding to a different cart we still have cached version of list for 2L

cartService.addToCart(1L, "ABC");

List<item> items6th = cartService.getItemsInCart(2L);

assertSame(items4th, items6th);

}

}

</item></item></item></item></item></item></string,></string,></item>

The important elements are: include JUnit dependency in pom.xml, use @RunWith and @ContextConfiguration to tell JUnit what context configuration file you want to use. If you are testing DAO objects in isolation you can add @Transactional annotation to your
Test suite at class level to provide the transaction support.

You also may have noticed that we use alternative context file /spring/testContext.xml. This context imports our original cache.xml and persistence.xml but provides override for spring-context.properties to supply alternative data source configurations and
also does not contain the MVC configuration which are not needed for this test.

view
plaincopy
to clipboardprint?

<!--?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-3.0.xsd">

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<property name="locations">

<list>

<!-- spring-testContext will override any properties in spring-context as it comes after -->

<value>classpath:spring-context.properties</value>

<value>classpath:spring-testContext.properties</value>

</list>

</property>

</bean>

<import resource="classpath*:spring/cache.xml">

<import resource="classpath*:spring/persistence.xml">

<!-- Service to manipulate the cart -->

<bean id="cartService" parent="txProxyTemplate">

<property name="target">

<bean class="dp.example.shoppingcart.service.impl.WebCartServiceCached">

<constructor-arg index="0" ref="cartDao">

</constructor-arg></bean>

</property>

</bean>

</import></import></beans>

Hope you enjoyed this tutorial and let me know if any information is inaccurate or incomplete. Here is link to the full source for this:

denis-pavlov-spring3mvc-mvn2-example-updated.zip

PLEASE BARE IN MIND THIS IS AN EXAMPLE TO SHOW HOW SPRING MAVEN PROJECT IS SETUP - THIS IS NOT PRODUCTION CODE AND NEVER WAS INTENDED TO IMITATE ONE.

This page was last updated on: 29/11/2013 15:23

zz:http://www.inspire-software.com/en/index/view/articles-design-and-implementation-spring-maven2-web-app-step-by-step.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring java maven jsp
相关文章推荐