From Documentation

(Difference between revisions)
Jump to: navigation, search
m (About The Demo Application)
m (Using Spring in ZK Application)
Line 53: Line 53:
The configuration looks like this:
The configuration looks like this:
 +
'''web.xml'''
<source lang="xml">
<source lang="xml">
<!--  Declares where the Spring application context setting is -->
<!--  Declares where the Spring application context setting is -->
<context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-name>contextConfigLocation</param-name>
-
<param-value>classpath:META-INF/spring/app-context.xml</param-value>
+
<param-value>WEB-INF/applicationContext.xml</param-value>
</context-param>
</context-param>
Line 67: Line 68:
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</listener>
 +
</source>
 +
 +
Our demo project uses the classpath scanning feature to register beans. It detects classes in specified packages with stereotype annotations (e.g. <tt>@Component</tt>) and registers them as Spring managed beans.
 +
 +
'''applicationContext.xml'''
 +
<source lang="xml">
 +
<!-- AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor are both
 +
included implicitly-->
 +
<context:component-scan base-package="org.zkoss.springdemo" />
 +
<!-- JPA and transaction manager configuration -->
 +
...
</source>
</source>

Revision as of 09:08, 31 October 2012





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

Contents

Introduction

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

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

Develop Environment Preparation

You can get source code from github [2]. If your are familiar with Git, you can also clone my repository.


IDE Setup

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

  1. M2Eclipse: a Maven management Eclipse plugin.
  2. RunJettyRun: a simple Jetty Server which is very easy to use with M2Eclipse.

Demo Project Setup

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

The Demo Application

The demo application of this article is the same one in ZK Essentials; it is an online order management system.

Demo: Order Management System


This application consists of 4 parts:

  1. Login, when the user requests the URL: http://localhost:8080/springZkDemo for the first time, the request will be redirected to the login page and ask user to perform the login process.
  2. Product view, after logging-in, 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

In this demo application we have several entity beans under package org.zkoss.springdemo.bean

  • Product, a product with several properties like name, price, quantity
  • CartItem, a shopping cart item which references to a Product.
  • Order, When a users submit all items in a shopping cart, we create an order.
  • OrderItem, an item that comes from submitted CartItem
  • User, a user who logins into the application.

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 manage its application context properly for different levels of scope.

The configuration looks like this: web.xml

	<!--  Declares where the Spring application context setting is -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/applicationContext.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>

Our demo project uses the classpath scanning feature to register beans. It detects classes in specified packages with stereotype annotations (e.g. @Component) and registers them as Spring managed beans.

applicationContext.xml

	<!-- AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor are both 
	included implicitly-->
	<context:component-scan base-package="org.zkoss.springdemo" />
	<!-- JPA and transaction manager configuration -->
	...

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 recommended 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 starts up.

Here we will choose the second approach 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 below:

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

You could also choose to use XML declaration to manage bean's construction in Spring, but annotation in some cases are much more 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 all the configuration above has been done, the Spring context of this application is ready to handle the usage of @PersistentContext, @Repository and @Transactional in DAO class:

@Repository
public class OrderDAO {
	@PersistenceContext
	EntityManager em;
	
	@Transactional(value="txManager", propagation=Propagation.REQUIRES_NEW)
	public Order cancelOrder(final Order order) {
		if(order==null)return null;
		order.setStatus(Order.CANCELED);
		return em.merge(order);
	}
...
}

According to Spring's manual: Spring JPA Note, if you use the correct Entity Manager Factory bean to manage EntityManager usage, you can get fresh and safe entity Manager in application context during Http request. But this is not enough for the old entity bean, they were fetched last time and needs to be reconciled to entity manager. For this purpose we need 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 requests to make sure every request could use entity manager freshly.

Logic Layer

Logic Layer is a layer for business objects of this demo. In this demo application, all objects 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 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 that needs to be solved. To deal with this inconsistency, Spring has a Scoped Proxy mechanism which is 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 ProductDAO{
	...

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 calls: productDao.findAllAvailable(), the manager would still work 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 tries to request http://localhost:8080/springZkDemo/index.zul we need to check if current user is authenticated, if not, we'll redirect him to login.zul for the login process:

Zk cdi integration login proc.png

In order to do this, in this demo, org.zkoss.springdemo.web.model.UserCredentialManager which is a session designed to 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 has a User bean stored 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

This layer is about how to use BO in ZK's view, java classes that controls zk views are all under org.zkoss.springdemo.web.ui.ctrl. In this application we demonstrate how to use Spring bean in ZK MVC controller:

  • org.zkoss.springdemo.web.ui.ctrl.LoginViewCtrl
  • org.zkoss.springdemo.web.ui.ctrl.ProductViewCtrl

and how to use Spring bean in ZK MVVM view model bean:

  • org.zkoss.springdemo.web.ui.ctrl.OrderViewViewModel
  • org.zkoss.springdemo.web.ui.ctrl.ShoppingCartViewModel

Context Injection in ZK

Adopting Spring's context to ZK view context is very simple, you simply use ZK's org.zkoss.zkplus.spring.DelegatingVariableResolver. Here we will discuss 3 circumstances in which you can use ZK's Spring DelegatingVariableResolver.

ZK's Listener

In the login process example above, we have a WorkbenchInit declared in index.zul, let's see how to get managed bean in ZK's listener (ex: org.zkoss.zk.ui.util.Initiator).

In ZK, we have a bunch of Listeners that you can do some interception or flow control with. To get Spring managed bean from them, the easiest way is to construct a new DelegatingVariableResolver instance:

public class WorkbenchInit implements Initiator {	
	private UserCredentialManager userCredentialManager;

	public void doInit(Page page, @SuppressWarnings("rawtypes") Map arg) throws Exception {
		if(userCredentialManager==null){
			userCredentialManager = (UserCredentialManager) 
			new DelegatingVariableResolver().resolveVariable("userCredentialManager");
		}
		if (!userCredentialManager.isAuthenticated()) {
			Executions.getCurrent().sendRedirect("login.zul");
		}
	}
//...

As illustrated above, we can get our session object userCredentialManager by calling resolveVariable(String) through DelegatingVariableResolver in an Initiator.

In Listener scenario we use a programmatic approach to get managed bean. In ZK MVC controller, we can use annotations for variable wiring to save coding effort. For example, in org.zkoss.spring demo.web.ui.ctrl.LoginViewCtrl:

@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
public class LoginViewCtrl extends SelectorComposer<Window> {

	@Wire
	private Textbox nameTxb, passwordTxb;
	@Wire
	private Label mesgLbl;

	@WireVariable
	private UserCredentialManager userCredentialManager;

	@Listen("onClick=#confirmBtn; onOK=#passwordTxb")
	public void doLogin() {
		userCredentialManager.login(nameTxb.getValue(), passwordTxb.getValue());
		if (userCredentialManager.isAuthenticated()) {
			Executions.getCurrent().sendRedirect("index.zul");
		} else {
			mesgLbl.setValue("Your User Name or Password is invalid!");
		}
	}
//...

We use @VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class) to annotate LoginViewCtrl which tells super class SelectorComposer that this controller will be based on Spring context to do variable wiring. Then in the member field declaration part, we can use @WireVariable to wire UserCredentialManager. As you can see, by default if the field's name is the name of that Spring bean, the instance will be wired automatically.

ZK MVVM

In ZK MVVM, the way to do variable wiring is very similar to ZK MVC, let's use org.zkoss.springdemo.web.ui.ctrl.OrderViewViewModel for example:

@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
public class OrderViewViewModel  {
	
	@WireVariable
	private UserOrderManager userOrderManager;
	
	private Order selectedItem;

	public Order getSelectedItem() {
		return selectedItem;
	}
	
	@NotifyChange("selectedItem")
	public void setSelectedItem(Order selectedItem) {
		this.selectedItem = selectedItem;
	}
	
	public List<Order> getOrders() {
		return userOrderManager.findAll();
	}
	
	@Command
	@NotifyChange({"orders", "selectedItem"})
	public void cancelOrder() {
		if (getSelectedItem() == null) {
			return;
		}
		userOrderManager.cancelOrder(getSelectedItem());
		setSelectedItem(null);
	}
	
	@GlobalCommand
	@NotifyChange("orders")
	public void submitNewOrder( 
			 @BindingParam("cartItems")List<CartItem> cartItems 
			,@BindingParam("orderNote") String orderNote){
		userOrderManager.createOrder( cartItems, orderNote);
	}
}

We reuse @VariableResolver and @WireVariable annotations here, which makes our View Model object becomes very clean to both ZK view and data.

Conclusion

In this series of articles(with the other two: Practices Of Using CDI In ZK, Starting A Web Application Based On ZK CDI JPA and Jetty ) I showed how to start a web application stack based on two of the most famous application frameworks(CDI, Spring) with ZK. As you can see, the usage of these framework's context are totally the same in ZK.


Comments



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

Cite error: <ref> tags exist, but no <references/> tag was found