Practices Of Using Spring In ZK"

From Documentation
Line 183: Line 183:
 
===Session Scope Practice===
 
===Session Scope Practice===
 
In web logical layer design, a business object which needs to keep state across multiple requests should be stored in session.
 
In web logical layer design, a business object which needs to keep state across multiple requests should be stored in session.
In CDI, you can annotate a managed bean with '''@SessionScoped''' to make sure requests belongs to one session will access the same instance.
+
In this Spring demo app, business layered beans' meta info are all declared in ''app-context.xml'':
 +
<source lang="xml">
 +
<bean id="shoppingCart" scope="session"
 +
class="org.zkoss.springdemo.web.model.ShoppingCartSessionImpl">
 +
<property name="userCredentialManager" ref="userCredentialManager" />
 +
</bean>
 +
</source>
  
To identify if a managed bean should be session scoped or not, you can check if it contains unmanaged bean member fields.
+
To identify if a managed bean should be session scoped or not, you can check if it contains unmanaged state-related member fields in bean class.
Normally a bean contains member fields which have a strong flavor of current user state could be session scoped, for example in our demo application while user try to request ''http://localhost:8080/backend_demo/index.zul'' we need to check if current user is authenticated. if not, well redirect him to login.zul for login process:
+
For example in our demo application while user try to request ''http://localhost:8080/springZkDemo/index.zul'' we need to check if current user is authenticated. if not, well redirect him to login.zul for login process:
  
 
[[File:zk_cdi_integration_login_proc.png]]
 
[[File:zk_cdi_integration_login_proc.png]]
  
In order to do this, I designed ''demo.web.model.UserCredentialManager'' which is a session object maintaining the state of user credential:  
+
In order to do this, in this demo, i designed a ''org.zkoss.springdemo.web.model.UserCredentialManager'' which is a session scoped object maintaining the state  
 +
of user credential:  
 
<source lang="java">
 
<source lang="java">
@Named("userCredentialManager")
+
 
@SessionScoped
 
 
public class UserCredentialManager implements Serializable{
 
public class UserCredentialManager implements Serializable{
 
public UserCredentialManager(){}
 
public UserCredentialManager(){}
Line 199: Line 205:
 
private User user;
 
private User user;
  
@Inject
 
 
private UserDAO userDao;
 
private UserDAO userDao;
+
public UserDAO getUserDao() {
 +
return userDao;
 +
}
 +
public void setUserDao(UserDAO userDao) {
 +
this.userDao = userDao;
 +
}
 
public synchronized void login(String name, String password) {
 
public synchronized void login(String name, String password) {
User tempUser = userDao.findUserByName(name);
 
if (tempUser != null && tempUser.getPassword().equals(password)) {
 
user = tempUser;
 
} else {
 
user = null;
 
}
 
}
 
 
//....
 
//....
 
</source>
 
</source>
As you can see above, the ''UserCredentialManager '' has a ''login(name, password)'' method and stored a ''User'' bean inside.
+
As you can see above, the ''UserCredentialManager '' has a ''login(name, password)'' method and stored a ''User'' bean inside. And in the ''app-context.xml'':
Here's one thing to be careful, in CDI, a session scoped object needs to be ''Serializable'' otherwise CDI will throw exception while parsing bean.  
+
<source lang="xml">
To bean's member field, except managed bean(ex: UserDAO) every member field needs to be ''Serializable'' or with keyword ''transient'' .
+
<bean id="userCredentialManager" scope="session"
 
+
class="org.zkoss.springdemo.web.model.UserCredentialManager">
You can also inject BO into another BO, for example, in ''demo.web.model.UserOrderManager'' we need ''userCredentialManager'' to get current user:
+
<aop:scoped-proxy/>
<source lang="java">
+
<property name="UserDao" ref="userDAO" />
@Named("userOrderManager")
+
</bean>
@SessionScoped
 
public class UserOrderManager implements Serializable{
 
 
 
@Inject
 
private OrderDAO orderDao;
 
 
@Inject
 
private UserCredentialManager userCredentialManager;
 
 
 
public List<Order> findAll() {
 
return orderDao.findByUser(userCredentialManager.getUser());
 
}
 
 
</source>
 
</source>
  

Revision as of 07:59, 8 October 2012

WarningTriangle-32x32.png This page is under construction, so we cannot guarantee the accuracy of the content!


DocumentationSmall Talks2012OctoberPractices Of Using Spring In ZK
Practices Of Using Spring In ZK

Author
Ian YT Tsai, Engineer, Potix Corporation
Date
October 05, 2012
Version
ZK 6

Introduction

Spring Framework is one of the most common framework for Java web developer. In this article, I'll introduce how to integrate ZK, Spring and Hibernate together to build a web application. First, I'll list the prerequisite of the demo application. Then I'll focus on the detail configuration of this application stack and guide how to use ZK Spring DelegatingVariableResolver properly in your ZK code with some programming tips of the Spring part design.

If you already read Practices Of Using CDI In ZK, this article is the counterpart of it in Spring.

Develop Environment Preparation

To get the source code, you can download everything from [1], please get the zip file named springZkDemo-XXXX.zip, and it's better to get the latest one. If your are familiar with Git, you can also clone my smalltalk repository.

IDE Setup

In this article, we use Eclipse with M2Eclipse to manage our Maven Project.

  1. Eclipse 3.x: I think everything should be fine in 4.X but I haven't try it.
  2. M2Eclipse: a Maven management Eclipse plugin.
  3. RunJettyRun: a simple Jetty Server which is very easy to use with M2Eclipse.

Demo Project Setup

If you are a Maven user and already cloned my repository , the project is a Maven project, you can use Eclipse Import Existing Project function to import it. If you get the code by downloading tthe zip file, unpack it, and put them to the project type you preferred.

About The Demo Application

The demo case of this article is the same one in Practices Of Using CDI In ZK, it is an online order management system.text


This demo consists of 4 parts:

  1. Login, when it's the first time the user requests the URL: http://localhost:8080/springZkDemo, the request will be redirected to the login page and ask user to perform the login process.
  2. product view, after login, user will be redirected back to main page which has three fragments, the first one is product view which displays all available products.
  3. shopping cart view, At the east part of main view which displays user's shopping cart content, user can add, remove and change quantity of products in cart.
  4. order view, The bottom of the main page displays user's order and order details of each order.

Entity Beans of This Application

In this demo application we have several entity beans all under package demo.model.bean

  • Product, a product with several attributes like name, price, quantity
  • CartItem, a shopping cart item which has attribute amount and pointed to a product.
  • Order, an order which contains multiple order item.
  • OrderItem, an item that has attributes which comes from cartitem
  • User, a user with attributes user name and password

Using Spring in ZK Application

Just like a normal Spring web application, you have to configure some listener, context parameter and filter in WEB-INF/web.xml to make Spring can manage it's application context properly for different level of scope. The configuration is looked like this:

	<!--  Declares where the Spring application context setting is -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:META-INF/spring/app-context.xml</param-value>
	</context-param>

	<!-- Spring Context Initialization & Request scope Handling -->
	<listener>
		<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
	</listener>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

Persistence Layer

In this Demo we use JPA as our persistence layer solution. There are two ways to configure and use JPA:

  1. Use container specific data source configuration to setup the data source, and define and declare it in your web application. this way is strongly suggested if your are running your system in a official environment with a well managed application server.
  2. Define the JPA data source in classpath:META-INF/persistence.xml. your application must has JPA related jar library(Hibernate in our case) to scan and initialize the whole stuff while application start up.

We chose the second one here to make this demo project container independent.

Spring JPA Specific configuration

As the <context-param> declaration in WEB-INF/web.xml, we put our Spring application context configuration file in classpath:META-INF/spring/app-context.xml

Opening our app-context.xml, the JPA related configuration part is briefed bellow:

	<context:component-scan base-package="org.zkoss.springdemo.model, org.zkoss.springdemo.web.model" />
	
	<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="breakfast" />
	</bean>

	<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="emf" />
	</bean>
	
	<tx:annotation-driven transaction-manager="txManager" />

<context:component-scan>: this part is to tell Spring which java package contains beans which have additional meta-info(annotation) that requires specific treatment. Though personally I'd much prefer to use XML declaration to manage bean's construction in Spring, but annotation in some cases are much convenient than XML, for example, to tell which class is a DAO and what method of it should become transactional.

<bean id="emf" ...>: To get and use a JPA Entity Manager, first, we need to have an Entity Manager Factory. in Spring, org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean is the bean that can simply construct a factory in application.

bean id="txManager" & tx:annotation-driven: These two parts will declare a transaction manager for JPA and tell Spring component scanner that transaction manager is aware of some annotation.

Open Entity Manager In View

After the configurations above, the Spring context of this application is ready to handle the usage of @PersistentContext, @Repository and @Transactional in DAO class:

@Repository
@Scope("request")
public class OrderDAO {
	@PersistenceContext
	EntityManager em;
	
	@Transactional(value="txManager", propagation=Propagation.REQUIRES_NEW)
	public Order createOrder(User user, List<CartItem> items, String description) {
		...
	}
...
}

But what about the @Scope("request")? The annotation declaration here means: "for each request, please inject different entity manager", But how do we make our application prepare a fresh entity manager every time a request come? The answer is to declare a <filter> in WEB-INF/web.xml:

	<filter>
		<filter-name>oemInViewFilter</filter-name>
		<filter-class>
			org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
		<init-param>
			<param-name>entityManagerFactoryBeanName</param-name>
			<param-value>emf</param-value>
		</init-param>
	</filter>

	<filter-mapping>
		<filter-name>oemInViewFilter</filter-name>
		<url-pattern>*</url-pattern>
	</filter-mapping>

The entityManagerFactoryBeanName is mapped to the bean with id "emf" in app-context.xml, and the filter mapping is mapped to all possible request to make sure every request could get a fresh entity manager.

Logic Layer

Logic Layer is a layer for business objects of this demo, in this demo application, all object about this layer has been put in package org.zkoss.springdemo.web.model.

Business Object Design practice

In this demo application, we have several main business objects which provides a set of logical API to interact with view. They are:

  • org.zkoss.springdemo.web.model.ProductManager
  • org.zkoss.springdemo.web.model.UserOrderManager
  • org.zkoss.springdemo.web.model.UserCredentialManager
  • org.zkoss.springdemo.web.model.ShoppingCart

Let's use them to see how to design your business object in some common scenario.

Application Scope Practice

In org.zkoss.springdemo.web.model.ProductManager, as the scenario here is to provide a list of available products for view to display, the code is very simple:

public class ProductManager {
	
	private ProductDAO productDao;

	public ProductDAO getProductDao() {
		return productDao;
	}
	public void setProductDao(ProductDAO productDao) {
		this.productDao = productDao;
	}
	public List<Product> findAllAvailable() {
		return productDao.findAllAvailable();
	}
}

As what you can see this bean has nothing but a productDao attribute, the scope declaration and bean id are all defined in app-context.xml:

<bean id="productManager" scope="application"
	class="org.zkoss.springdemo.web.model.ProductManager">
	<property name="productDao" ref="productDAO" />
</bean>

Inconsistent Scope handling

Though our ProductManager is application scoped, the ProductDAO which Spring injected has a request scoped life-cycle, this causes a scope inconsistent problem and this needs to be solved. To deal with this inconsistency, Spring has a Scoped Proxy mechanism which based on Spring AOP and we need to define the proxy mode in ProductDAO class annotation declaration:

@Repository
@Scope(value="request", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class UserDAO{
	...

At the back-end with this declaration, Spring will use CGLIB to create a proxy class to pretend our ProductDao and inject it into our ProductManager to make sure every time when user call: productDao.findAllAvailable(), the manager would works fine.

Session Scope Practice

In web logical layer design, a business object which needs to keep state across multiple requests should be stored in session. In this Spring demo app, business layered beans' meta info are all declared in app-context.xml:

	<bean id="shoppingCart" scope="session"
		class="org.zkoss.springdemo.web.model.ShoppingCartSessionImpl">
		<property name="userCredentialManager" ref="userCredentialManager" />
	</bean>

To identify if a managed bean should be session scoped or not, you can check if it contains unmanaged state-related member fields in bean class. For example in our demo application while user try to request http://localhost:8080/springZkDemo/index.zul we need to check if current user is authenticated. if not, well redirect him to login.zul for login process:

Zk cdi integration login proc.png

In order to do this, in this demo, i designed a org.zkoss.springdemo.web.model.UserCredentialManager which is a session scoped object maintaining the state of user credential:

public class UserCredentialManager implements Serializable{
	public UserCredentialManager(){}
	
	private User user;

	private UserDAO userDao;
	public UserDAO getUserDao() {
		return userDao;
	}
	public void setUserDao(UserDAO userDao) {
		this.userDao = userDao;
	}
	public synchronized void login(String name, String password) {
//....

As you can see above, the UserCredentialManager has a login(name, password) method and stored a User bean inside. And in the app-context.xml:

	<bean id="userCredentialManager" scope="session"
		class="org.zkoss.springdemo.web.model.UserCredentialManager">
		<aop:scoped-proxy/>
		<property name="UserDao" ref="userDAO" />
	</bean>

Presentation Layer

Context Injection in ZK

ZK's Listener

ZK MVC

ZK MVVM

Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.