Practices Of Using Spring In ZK"

From Documentation
(35 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Template:UnderConstruction}}
 
 
 
{{Template:Smalltalk_Author|
 
{{Template:Smalltalk_Author|
 
|author=Ian YT Tsai, Engineer, Potix Corporation
 
|author=Ian YT Tsai, Engineer, Potix Corporation
|date=October 05, 2012
+
|date=October 23, 2012
 
|version=ZK 6
 
|version=ZK 6
 
}}
 
}}
  
 
=Introduction=
 
=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 introduce the prerequisite of the demo application. The,n I'll focus on guiding how to use [http://www.zkoss.org/javadoc/latest/zk/org/zkoss/zkplus/spring/DelegatingVariableResolver.html ZK Spring DelegatingVariableResolver] properly in your ZK code and some programming tips of the Spring part design.
 
  
If you already read [http://books.zkoss.org/wiki/Small_Talks/2012/September/Practices_Of_Using_CDI_In_ZK Practices Of Using CDI In ZK], this article is the counterpart in Spring.
+
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 [http://www.zkoss.org/javadoc/latest/zk/org/zkoss/zkplus/spring/DelegatingVariableResolver.html 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"'''<ref> [http://books.zkoss.org/wiki/Small_Talks/2012/September/Practices_Of_Using_CDI_In_ZK Practices Of Using CDI In ZK] </ref>, this article is the counterpart of it in Spring.
  
 
=Develop Environment Preparation=
 
=Develop Environment Preparation=
To get the source code, you can download everything from [https://github.com/zanyking/smalltalk/downloads], please get the zip file named springZkDemo-XXXX.zip, and it's better to get the latest one.
+
You can get source code from github <ref> Visit [https://github.com/hawkchen/smalltalk/downloads github smalltalk] and click "Download as zip" button or use a git client to clone the whole repository. </ref>. If your are familiar with Git, you can also clone my repository.
If your are familiar with Git, you can also clone my smalltalk repository.
+
 
  
 
===IDE Setup===
 
===IDE Setup===
 
In this article, we use Eclipse with M2Eclipse to manage our Maven Project.
 
In this article, we use Eclipse with M2Eclipse to manage our Maven Project.
# '''Eclipse 3.x''': I think everything should be fine in 4.X but I haven't try it.
 
 
# '''M2Eclipse''': a Maven management Eclipse plugin.
 
# '''M2Eclipse''': a Maven management Eclipse plugin.
 
# '''RunJettyRun''': a simple Jetty Server which is very easy to use with M2Eclipse.
 
# '''RunJettyRun''': a simple Jetty Server which is very easy to use with M2Eclipse.
  
 
===Demo Project Setup===
 
===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 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 tthe zip file, unpack it, and put them to the project type you preferred.
+
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/Laying out Your ZK Components/Using ZK Borderlayout| ZK Essentials]]; it is an online order management system.
  
=About The Demo Application=
+
[[File:zk_cdi_integration_demo.png | 500px | center | Demo: Order Management System]]
The demo case of this article is the same one in [http://books.zkoss.org/wiki/Small_Talks/2012/September/Practices_Of_Using_CDI_In_ZK Practices Of Using CDI In ZK], it is an online order management system.[[File:zk_cdi_integration_demo.png||400px|alt=text|Demo: Order Management System]]
 
  
  
This demo consists of 4 parts:
+
This application consists of 4 parts:
 
<ol>  
 
<ol>  
<li>'''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.</li>
+
<li>'''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.</li>
<li>'''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.</li>
+
<li>'''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.</li>
<li>'''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.</li>
+
<li>'''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.</li>
<li>'''order view''', The bottom of the main page displays user's order and order details of each order.</li>
+
<li>'''Order view''', The bottom of the main page displays user's order and order details of each order.</li>
 
</ol>
 
</ol>
  
===Entity Beans of This Application===
+
===Entity Beans ===
In this demo application we have several entity beans all under package '''demo.model.bean'''
+
In this demo application we have several entity beans under package '''org.zkoss.springdemo.bean'''
 
<ul>
 
<ul>
<li>Product, a product with several attributes like name, price, quantity</li>
+
<li><tt>Product</tt>, a product with several properties like name, price, quantity</li>
<li>CartItem, a shopping cart item which has attribute amount and pointed to a product.</li>
+
<li><tt>CartItem</tt>, a shopping cart item which references to a <tt>Product</tt>.</li>
<li>Order, an order which contains multiple order item.</li>
+
<li><tt>Order</tt>, When a users submit all items in a shopping cart, we create an order.
<li>OrderItem, an item that has attributes which comes from cartitem</li>
+
<li><tt>OrderItem</tt>, an item that comes from submitted <tt>CartItem</tt></li>
<li>User, a user with attributes user name and password</li>
+
<li><tt>User</tt>, a user who logins into the application.</li>
 
</ul>
 
</ul>
  
 
=Using Spring in ZK 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 can manage it's application context properly for different level of scope.
+
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 is looked 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 66: 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>
  
 
=Persistence Layer=
 
=Persistence Layer=
In this Demo we use JPA as our persistence layer solution. There are two ways to configure and use JPA:
 
# 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.
 
# 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:
+
<!--
 +
In this Demo we use JPA as our persistence layer solution. There are 3 options for JPA setup in a Spring environment:
 +
# LocalEntityManagerFactoryBean. This is suitable for simple deployment environments where the application uses only JPA for data access.
 +
# Obtaining an EntityManagerFactory from JNDI. Use this option when deploying to a Java EE 5 server.
 +
# LocalContainerEntityManagerFactoryBean. This option gives full control over EntityManagerFactory configuration and is appropriate for environments where fine-grained customization is required.
 +
 
 +
We choose the third one here to keep this demo project simple but still close to real case.
 +
 
 +
== JPA Configuration in Spring ==
 +
As the <context-param> declaration in ''WEB-INF/web.xml'', we put our Spring application context configuration file in ''WEB-INF/applicationContext.xml''
 +
 
 +
Opening our applicationContext.xml, the JPA related configuration part is briefed below:
 
<source language="xml">
 
<source language="xml">
<context:component-scan base-package="org.zkoss.springdemo.model, org.zkoss.springdemo.web.model" />
+
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"/>
 
 
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
 
<property name="persistenceUnitName" value="breakfast" />
 
</bean>
 
 
 
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
 
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
 
<property name="entityManagerFactory" ref="emf" />
 
<property name="entityManagerFactory" ref="emf" />
 
</bean>
 
</bean>
 
 
<tx:annotation-driven transaction-manager="txManager" />
 
<tx:annotation-driven transaction-manager="txManager" />
 
</source>
 
</source>
'''<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.
+
* LIne 1: To get and use a JPA Entity Manager, we need to register an Entity Manager Factory. In Spring, ''org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'' is the bean that can simply construct a factory in application.
 +
* Line 3: Declare a transaction manager for JPA.
 +
* Line 7: enable the configuration of transactional behavior based on annotations.
 +
-->
 +
In our spring configuration, we have declared class path scanning element which implicitly registers <tt> PersistenceAnnotationBeanPostProcessor</tt> for us and a transaction manager. Spring can understand <tt>@PersistenceContext</tt> both at field and method level and inject proper <tt>EntityManager</tt> in current transaction.
 +
 
 +
=== Implement DAO on JPA ===
 +
 
 +
In our project, we implement DAO (Data Access Object) pattern for the persistence layer with injected EntityManger and this DAO doesn't depends on any Spring's classes.
 +
 
 +
<source lang="java" high="4">
  
===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:
 
<source lang="java">
 
 
@Repository
 
@Repository
@Scope("request")
+
public class ProductDAO {
public class OrderDAO {
+
 
 
@PersistenceContext
 
@PersistenceContext
EntityManager em;
+
private EntityManager em;
+
 
@Transactional(value="txManager", propagation=Propagation.REQUIRES_NEW)
+
public List<Product> findAll() {
public Order createOrder(User user, List<CartItem> items, String description) {
+
        Query query = em.createQuery("from products");
...
+
        List<Product> products = query.getResultList();
 +
        return products;
 
}
 
}
 
...
 
...
 
}
 
}
 
</source>
 
</source>
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'':
 
<source lang="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>
+
=Service Layer=
<filter-name>oemInViewFilter</filter-name>
+
Service Layer is composed of business objects which are put in package <tt>org.zkoss.springdemo.service</tt>.
<url-pattern>*</url-pattern>
+
 
</filter-mapping>
 
</source>
 
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 ''demo.web.model''.
 
 
==Business Object Design practice==
 
==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.
 
In this demo application, we have several main business objects which provides a set of logical API to interact with view.
 
They are:
 
They are:
 
<ul>
 
<ul>
<li>demo.web.model.ProductManager</li>
+
<li>org.zkoss.springdemo.service.ProductManager</li>
<li>demo.web.model.UserOrderManager</li>
+
<li>org.zkoss.springdemo.service.UserOrderManager</li>
<li>demo.web.model.UserCredentialManager</li>
+
<li>org.zkoss.springdemo.service.UserCredentialManager</li>
<li>demo.web.model.ShoppingCart</li>
+
<li>org.zkoss.springdemo.service.ShoppingCartManager</li>
 
</ul>
 
</ul>
 
Let's use them to see how to design your business object in some common scenario.
 
Let's use them to see how to design your business object in some common scenario.
===Application Scope Practice===
+
 
In '''demo.web.model.ProductManager''', as the scenario here is to provide a list of available products for view to display, the code is very simple:
+
 
 +
===Singleton Scope Practice===
 +
If a business object is stateless, we can declare it as a singleton bean. For example, '''org.zkoss.springdemo.service.ProductManager''', it only provides a list of available products and do not save any user's or product's state. Therefore, we should declare it as a singleton bean.
 +
 
 
<source lang="java">
 
<source lang="java">
 +
@Service
 
public class ProductManager {
 
public class ProductManager {
@Inject
+
 +
@Autowired
 
private ProductDAO productDao;
 
private ProductDAO productDao;
  
Line 157: Line 163:
 
}
 
}
 
</source>
 
</source>
We give a '''@Named''' annotation which will tell CDI the bean name of ''ProductManager'', then we set the scope of our ''ProductManager'' to be '''@ApplicationScoped''' as product manager is generic to every user.
 
  
===Inconsistent Scope handling ===
 
Though our ''ProductManager'' is application scoped, the ProductDAO which CDI injected is RequestScoped, this causes a scope inconsistent problem and needs to be solved.
 
  
[[File:zk_cdi_integration_cdi_proxy.png]]
+
===Session Scope Practice===
 +
A business object which needs to keep state across multiple requests for individual user should be "session" scope. For example, <tt>ShoppingCartManager </tt> contains selected products of a user.
  
As figure shown above, to deal with this inconsistency, during injection, CDI created a proxy object which wrapped some code that will get correct instance of ''ProductDAO'' according to request scope .
+
<source lang="java">
 +
@Component("shoppingCart")
 +
@Scope("session")
 +
public class ShoppingCartManager implements ShoppingCart {
  
===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 CDI, you can annotate a managed bean with '''@SessionScoped''' to make sure requests belongs to one session will access the same instance.
+
</source>
  
To identify if a managed bean should be session scoped or not, you can check if it contains unmanaged bean member fields.
+
The <tt>UserCredentialManager </tt> keeps the user credential. So we should make them "session" scope to keep data.
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:
 
  
[[File:zk_cdi_integration_login_proc.png]]
+
<source lang="java">
  
In order to do this, I designed ''demo.web.model.UserCredentialManager'' which is a session object maintaining the state of user credential:
+
@Component
<source lang="java">
+
@Scope("session")
@Named("userCredentialManager")
 
@SessionScoped
 
 
public class UserCredentialManager implements Serializable{
 
public class UserCredentialManager implements Serializable{
public UserCredentialManager(){}
+
 
 
 
private User user;
 
private User user;
  
@Inject
+
...
private UserDAO userDao;
+
}
+
</source>
public synchronized void login(String name, String password) {
+
 
User tempUser = userDao.findUserByName(name);
+
=Presentation Layer=
if (tempUser != null && tempUser.getPassword().equals(password)) {
+
This layer is about how to use BO in ZK's view, the java classes that controlling zk views are all under '''org.zkoss.springdemo.controller'''.
user = tempUser;
+
In this application we demonstrate how to use Spring bean in ZK MVC controller:
} else {
+
* ''org.zkoss.springdemo.controller.LoginViewCtrl''
user = null;
+
* ''org.zkoss.springdemo.controller.ProductViewCtrl''
 +
and how to use Spring bean in ZK MVVM view model bean:
 +
* ''org.zkoss.springdemo.controller.OrderViewViewModel''
 +
* ''org.zkoss.springdemo.controller.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 several circumstances in which you can use ZK's Spring ''DelegatingVariableResolver''.
 +
 
 +
 
 +
===ZK's Listener===
 +
In the login process, we have a ''WorkbenchInit'' declared in index.zul. It's one of [http://books.zkoss.org/wiki/ZK_Configuration_Reference/zk.xml/The_listener_Element ZK's listeners] that allow you to intercept ZK's life cycle. This listener calls <tt>UserCredentialManager.isAuthenticated()</tt> to verify user's authentication.
 +
 
 +
[[File:zk_cdi_integration_login_proc.png]]
 +
 
 +
So we need to get Spring managed bean in ZK's listener first. To get Spring managed bean from them, the easiest way is to use <tt>SpringUtil.getBean("userCredentialManager")</tt>.
 +
 
 +
<source lang="java" high="6">
 +
public class WorkbenchInit implements Initiator {
 +
private UserCredentialManager userCredentialManager;
 +
 
 +
public void doInit(Page page, @SuppressWarnings("rawtypes") Map arg) throws Exception {
 +
if(userCredentialManager==null){
 +
userCredentialManager = (UserCredentialManager)SpringUtil.getBean("userCredentialManager");
 +
}
 +
if (!userCredentialManager.isAuthenticated()) {
 +
Executions.getCurrent().sendRedirect("login.zul");
 
}
 
}
 
}
 
}
//....
+
//...
 
</source>
 
</source>
As you can see above, the ''UserCredentialManager '' has a ''login(name, password)'' method and stored a ''User'' bean inside.
 
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.
 
To bean's member field, except managed bean(ex: UserDAO) every member field needs to be ''Serializable'' or with keyword ''transient'' .
 
  
You can also inject BO into another BO, for example, in ''demo.web.model.UserOrderManager'' we need ''userCredentialManager'' to get current user:
 
<source lang="java">
 
@Named("userOrderManager")
 
@SessionScoped
 
public class UserOrderManager implements Serializable{
 
  
@Inject
+
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.controller.LoginViewCtrl'':
private OrderDAO orderDao;
+
<source lang="java" high="1,9">
 +
@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!");
 +
}
 +
}
 +
//...
 +
</source>
 +
* Line 1: We use '''<tt>@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)</tt>''' to annotate ''<tt>LoginViewCtrl</tt>'' which tells super class ''SelectorComposer'' that this controller will based on Spring context to do variable wiring.
 +
* Line 9: We can use '''<tt>@WireVariable</tt>''' to wire UserCredentialManager like Spring's <tt>@Autowired</tt>. 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.controller.OrderViewViewModel'' for example:
 +
<source lang="java" high="1,4">
 +
@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
 +
public class OrderViewViewModel  {
 +
 +
@WireVariable
 +
private UserOrderManager userOrderManager;
 
 
@Inject
+
private Order selectedItem;
private UserCredentialManager userCredentialManager;
 
  
public List<Order> findAll() {
+
public Order getSelectedItem() {
return orderDao.findByUser(userCredentialManager.getUser());
+
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);
 +
}
 +
}
 
</source>
 
</source>
 +
* Line 1,4: 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: [http://books.zkoss.org/wiki/Small_Talks/2012/September/Practices_Of_Using_CDI_In_ZK Practices Of Using CDI In ZK], [http://books.zkoss.org/wiki/Small_Talks/2012/Aug/Starting_A_Web_Application_Based_On_ZK_CDI_JPA_and_Jetty 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.
  
=Presentation Layer=
 
==Context Injection in ZK==
 
===ZK's Listener===
 
===ZK MVC===
 
===ZK MVVM===
 
  
 +
= References =
 +
<references/>
  
 
{{Template:CommentedSmalltalk_Footer|
 
{{Template:CommentedSmalltalk_Footer|
 
|name=Potix Corporation
 
|name=Potix Corporation
 
}}
 
}}

Revision as of 09:46, 31 October 2012

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

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

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 our spring configuration, we have declared class path scanning element which implicitly registers PersistenceAnnotationBeanPostProcessor for us and a transaction manager. Spring can understand @PersistenceContext both at field and method level and inject proper EntityManager in current transaction.

Implement DAO on JPA

In our project, we implement DAO (Data Access Object) pattern for the persistence layer with injected EntityManger and this DAO doesn't depends on any Spring's classes.

@Repository
public class ProductDAO {

	@PersistenceContext
	private EntityManager em;

	public List<Product> findAll() {
        Query query = em.createQuery("from products");
        List<Product> products = query.getResultList();
        return products;
	}
...
}

Service Layer

Service Layer is composed of business objects which are put in package org.zkoss.springdemo.service.


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.service.ProductManager
  • org.zkoss.springdemo.service.UserOrderManager
  • org.zkoss.springdemo.service.UserCredentialManager
  • org.zkoss.springdemo.service.ShoppingCartManager

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


Singleton Scope Practice

If a business object is stateless, we can declare it as a singleton bean. For example, org.zkoss.springdemo.service.ProductManager, it only provides a list of available products and do not save any user's or product's state. Therefore, we should declare it as a singleton bean.

@Service
public class ProductManager {
	
	@Autowired
	private ProductDAO productDao;

	public List<Product> findAllAvailable() {
		return productDao.findAllAvailable();
	}
}


Session Scope Practice

A business object which needs to keep state across multiple requests for individual user should be "session" scope. For example, ShoppingCartManager contains selected products of a user.

@Component("shoppingCart")
@Scope("session")
public class ShoppingCartManager implements ShoppingCart {

...
}

The UserCredentialManager keeps the user credential. So we should make them "session" scope to keep data.

@Component
@Scope("session")
public class UserCredentialManager implements Serializable{

	private User user;

	...
}

Presentation Layer

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

  • org.zkoss.springdemo.controller.LoginViewCtrl
  • org.zkoss.springdemo.controller.ProductViewCtrl

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

  • org.zkoss.springdemo.controller.OrderViewViewModel
  • org.zkoss.springdemo.controller.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 several circumstances in which you can use ZK's Spring DelegatingVariableResolver.


ZK's Listener

In the login process, we have a WorkbenchInit declared in index.zul. It's one of ZK's listeners that allow you to intercept ZK's life cycle. This listener calls UserCredentialManager.isAuthenticated() to verify user's authentication.

Zk cdi integration login proc.png

So we need to get Spring managed bean in ZK's listener first. To get Spring managed bean from them, the easiest way is to use SpringUtil.getBean("userCredentialManager").

public class WorkbenchInit implements Initiator {	
	private UserCredentialManager userCredentialManager;

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


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.controller.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!");
		}
	}
//...
  • Line 1: We use @VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class) to annotate LoginViewCtrl which tells super class SelectorComposer that this controller will based on Spring context to do variable wiring.
  • Line 9: We can use @WireVariable to wire UserCredentialManager like Spring's @Autowired. 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.controller.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);
	}
}
  • Line 1,4: 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.


References

  1. Practices Of Using CDI In ZK
  2. Visit github smalltalk and click "Download as zip" button or use a git client to clone the whole repository.


Comments



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