Tuesday, December 14, 2010

JSF 2 with Spring 3 - basics (part 1 of 2)

Note: below description uses Eclipse Indigo, Tomcat 7.0.28, MyFaces 2.1.7, Spring Framework 3.1.1, Spring Security 3.1.0.

Requirements:
  • a working example from the last article of a serie introducing new JSF 2.0 features.
You will learn:
  • how to integrate Spring Framework into JSF 2.0 application
In the previous three posts I described some interesting JSF 2.0 features and I put them together in the sample application. Those parts were mostly focused on web content, a GUI and its behaviour.
What about the server side? What about the business logic executed underneath? Do we had a business logic in the sample application mentioned above? Of course we had. Presenting bikes list or a certain bike under some condition -  this is business logic resposniblity. Filtering bikes and presenting only those with the discount - this is also business logic. Don't think of it as a naive filtering of presented data - the business logic decides what does it mean that the certain bike has discount - it can be lower price but it can be also more complicated. In the previous example we had a class named BikeDataProvider.java which represented business logic. It was a singleton invoked with the help of static method getInstance() anywhere where needed.
When the application starts to grow up, we have more business logic accomplishing some business cases. We need something that will help us to manage the whole business logic in an elegant way - this is where Spring comes to play.
We will change the BikeDataProvider class into object created and managed by Spring. This object will be called service. A service which serves business logic. Then the service will be used by JSF managed beans. Let's add Spring to our sample web apllication. 

Step 1: adding required Spring libraries into the project. 


Step 2: creating Spring's configuration file named applicationContext.xml. The file has to be located inside /WEB-INF directory. This is full content of that file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:faces="http://www.springframework.org/schema/faces"
 xmlns:int-security="http://www.springframework.org/schema/integration/security"
 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:sec="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/integration/security http://www.springframework.org/schema/integration/security/spring-integration-security-3.1.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
  http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
  http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-3.1.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

 <context:component-scan base-package="com.jsfsample" />

</beans>

Step 3: modifying web.xml by registering listener resposnible for loading Spring in web application.
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext.xml</param-value>
 </context-param>
Step 4: modifying faces-config.xml file for allow JSF components use Spring components.
<application>
... <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
...
 </application>
Step 5: modifying BikeDataProvider.java toward Spring managed service.

First of all we will add a new functionality to the application - possibility to add a new bike to the selected category. We need to create an .xhtml page with the form, then managed bean for that page and at the end a business logic method in class BikeDataProvider.java responsible for adding new bike to bikes' list. That's easy part - it will be visible in the attached complete example. 
Assuming that we have this new funcitonality, we can modify .java files for Spring integration. First we create an interface: 
package com.jsfsample.services;
//imports
public interface BikeDataProvider {

 public List<Bike> getBikesByCategory(Integer categoryId,
   boolean onlyWithDiscount);

 public Bike getBikeById(Integer id);

 public void add(Bike newBike); // new function

} 
Then we have to create an implementation named BikeDataProviderImpl.java (yes, I know that this naming convention is bad) which will have the source code from previous BikeDataProvider.java class. This implementation will have Spring specific annotation defining the service:
package com.jsfsample.services.impl;
//imports
@Service("bikeDataProvider")
public class BikeDataProviderImpl implements BikeDataProvider {

 private List<Bike> bikes;
 private Integer currentBikeId;
 
 @PostConstruct
 private void prepareData(){
  bikes = new ArrayList<Bike>();
  
  // MTB
  Bike mtb1 = new Bike();
  mtb1.setId(1);
  mtb1.setName("Kellys Mobster");
  mtb1.setDescription("Kellys Mobster, lorem ipsut...");
  mtb1.setPrice(6500);
  mtb1.setCategory(1); 
  bikes.add(mtb1);
  // other bikes are mocked up the same way
  
 }
 
 public List<Bike> getBikesByCategory(Integer categoryId, boolean onlyWithDiscount) {
  // implementation 
 }
 public Bike getBikeById(Integer id){
  // implementation 
 }

 public void add(Bike newBike) {
  bikes.add(newBike);
 }
} 
The annotation @Service("bikeDataProvider") means that this is Spring managed object (created by Spring) and is visible in the Spring context under the name "bikeDataProvider". @PostConstruct is a little trick here - when object of this class is instantiated by Spring, the method is invoked right after the object is created. I used this for preparing demo data of bikes. The whole BikeDataProviderImpl.java class acts as a simple data source for the application - in the future we will use a real database instead.

Step 6: modifying JSF managed beans to use Spring service inside.

We will use registered Spring service inside JSF managed-beans. For example consider BikeDetails.java managed bean. Previously we loaded certain bike in this way:
package com.jsfsample.managedbeans;
// imports
@ManagedBean(name="bikeDetails")
@RequestScoped
public class BikeDetails {

 private Integer bikeId;
 private Bike bike; 
 
 public void loadBike(){
  bike = BikeDataProvider.getInstance().getBikeById(bikeId);
 }
        ...
}
Now BikeDataProvider.java is not a singleton - it is a Spring service. BikeDetails.java managed bean is changed:
package com.jsfsample.managedbeans;
// imports
@ManagedBean(name="bikeDetails")
@RequestScoped
public class BikeDetails {

 private Integer bikeId;
 private Bike bike; 
 
 @ManagedProperty("#{bikeDataProvider}")
 private BikeDataProvider bikeDataProvider; // injected Spring service

 public void loadBike(){
  bike = bikeDataProvider.getBikeById(bikeId);
 }
        ...
}
We used the name of registered service ("bikeDataProvider") to inject it into JSF managed bean (please note that we used here an interface as an instance variable, Spring injects its concrete implementation). This injection is possible thanks to modifications from step 4.


That's all. Other JSF managed beans responsible for displaying bikes' lists or adding a bike use mentioned Spring service in the same way.

-------------------------------------------
Download source files:
The complete working example of mentioned application which will contain all described issues, will be available in the last (second) article of this serie.


5 comments:

Marcelo Daniel said...

Greate Article man! congratulations, help me a lot.

William Zhi said...

@ManagedProperty("#{bikeDataProvider}")
private BikeDataProvider bikeDataProvider;

You forgot put setter and getter on it. Which spent me 2 hours to figure out why it was failed. ;)

Paweł Nieścioruk said...

@William Zhi:

I checked the project source (https://sites.google.com/site/technologyforhuman/JSF2FeaturesSpring.zip) and setters are everywhere where they are needed (in the AddBike.java, BikeDetails.java and BikesList.java). You don't need getters.

abhiram mv said...

Setter missing for
@ManagedProperty("#{bikeDataProvider}")
private BikeDataProvider bikeDataProvider;

abhiram mv said...

Setter missing for
@ManagedProperty("#{bikeDataProvider}")
private BikeDataProvider bikeDataProvider;