Jacn - Seamless JSF and Spring (without XML)

Last changed on September 17, 2006 by Bing Ran (bran@icer.com.cn)

New in CVS HEAD

  • 2006-09-17: added field, method and bean name reflection syntax in the AbstractJacnConfigFile. See http://sourceforge.net/tracker/index.php?func=detail&aid=1559672&group_id=144308&atid=758395 for explaination. See TestMemberNameMethods.java in the CVS HEAD for the usage. Thanks, Sean.
  • 2006-07-2: added init-method and destroy-method support. Used to be a hack. Promoted to version 0.6.
  • 2005-09-21: Jacn now is a full featured bean management facility for JSF!
  • 2005-09-4: added support for setting a FactoryBean instance itself on a property.
  • 2005-08-21: added autoWireByName() and autoWireByType() marker funtions in the config file support.
  • 2005-08-20: finally an Ant script to build Jacn. The Jacn built this way has ASM2.0 embedded, which means you don't need to be concerned with ASM version issues anymore.
  • 2005-08-20: Added support for Maps and lazyInit() beans.
  • 2005-08-10: added support for quick enablement of transactions on objects. Use TransactionProxyFactoryBean behind the scene. Use the enableTx method declared in the base config class to do the trick.
  • Sample usage:

    UserManager userManager;
    // ....
    userManager.setUserDAO(userDAO);
    Properties pu = new Properties();
    pu.put("save*", "PROPAGATION_REQUIRED,-UserExistsException");
    pu.put("*", "PROPAGATION_REQUIRED,readOnly");
    enableTx(userManager, transactionManager, pu, 
        new Class[] {UserDaoInterface}, new Object[]{preInterceptor}, null);
    	

    As you can see, you don't need to expose the underware - TransactionProxyFactoryBean. A simple function call will make a bean transaction-aware. An overloaded version of the method allows you to specify the pre-interceptors and the post-interceptors. See also TestEnableTx.java in the test package.

  • 2005-08-10: added a convenient method to convert a jacn config file to XML, which gives developers a quick idea what DOM object will be created based on a Jacn descriptor. It can also be used to create XML files for deployment in prodcution.
  • String xml = ConvertUtil.jacn2Xml(Jacn_QuickTx.class);
    	
  • 2005-08-09: added support for calling arbitrary methods on beans, mapped to using MethodInvokingFactoryBean.

The wonderful Spring framework has been bothering me in two ways:

1. Its excessive use of XML as the configuration mechanism. The configuration files grow too big. Lacking full refactorability with Java makes it worse.

2. The integration with component-based WEB framework, such as JSF, is less than ideal. The reason is the generic Spring doesn't have the concept of web scopes, such as application, session and request. With the integration package available until now, developers are forced to put some beans in Spring and other beans in faces-config.xml, resulting a big gap between the presentation layer and the transactional layer.

The early versions of Jacn aimed to replace/complement the XML configurations. The solution Jacn gives is a Java-only way of configuring Spring managed beans. The innovative part of Jacn is that it uses Java as a macro language to record what the Spring core engine is supposed to do when requested to instantiate beans.

Since version 0.5, Jacn has been enhanced to unify the bean management facility of JSF and Spring, effectively replacing the minimalism DI in JSF with the full featured Spring bean container, leading to much better integration of web presenation layer and Spring managed transactional layer. The result is much improved productivity in web programming.

Spring without XML

XML is fine when everything has already been properly configured and working. XML is bad when you start writing your configuration from scratch. You need to look up the class definitions each step along the way: the class name, the property names, and the name of the other referenced beans. Personally this has been the bottle neck in a fastly growing project when new managed beans are introduced everyday.

All this mess is gone with Jacn. So instead of writing:

<bean id="dataSource" class="org....DriverManagerDataSource">
    <property name="driverClassName" value="com.mydriver"/>
</bean>

<bean id="sessionFactory" class="org....LocalSessionFactoryBean">
    <property name="dataSource"><ref bean="dataSource"/></property>
    <property name="mappingResources">
        <list>
            <value>com/lcare/model/Role.hbm.xml</value>
            <value>com/lcare/model/User.hbm.xml</value>
        </list>
    </property>
</bean>

I write in a Java file:

static DriverManagerDataSource dataSource;
static LocalSessionFactoryBean sessionFactory;
public void wire(){
    dataSource.setDriverClassName(com.MyDriver.class.getName());
    sessionFactory.setDataSource(dataSource);
    sessionFactory.setMappingResources(
            new String[] {
                "com/lcare/model/Role.hbm.xml",
                "com/lcare/model/User.hbm.xml",
            }
    );
}

Instead of:

<bean id="a" class="bran.jacn.test.AA">
    <property name="b2">
      <bean class="bran.jacn.test.BBFactory" 
		       factory-method="newInstanceStatic">
        <constructor-arg>
          <value>hello1</value>
        </constructor-arg>
        <constructor-arg>
          <value>b2</value>
        </constructor-arg>
      </bean>
    </property>
</bean>

I now do:

a.setB2(BBFactory.newInstanceStatic("hello1", "b2"));

The simple fact is the beans are mapped to the fields in the Jacn files, with the name of the field as the bean id, thus enabling all the automatic bean cross-referencing and refactoring support. Additionally, setting values on properties has now become calling the setters directly in Java. All of these are fully refactorable.

You get the idea: use the familiar Java syntax to do declarative programming. No, you cannot use if/else, try/catch and many others :)

Jacn files are plain Java files and they compile to class files. However those class files do NOT get loaded as class files in the JVM. They are read by Jacn runtime to build the bean definitions, like xml files being used.

The source files are not required at runtime. All happens with the class files. There is no need for any special treatment (build process) of Jacn files. You put them with other "regular" Java Files.

How to use Jacn?

First you write Jacn files, which basically are Java code:

public class ApplicationContext_AB extends AbstractJacnConfigFile {
    static AA a; 
  // static modifier indicates singleton pattern for this bean

    public void wire() throws Exception {
        a.setB2(BBFactory.newInstanceStatic("hello", "b2"));
        a.setB4(new BB("hello", "b4"));
    }
}

Of course not all Java syntax is valid in Jacn files, things like if/else, try/catch do not run.

After that you use the above file in a standalone Java application:

JacnApplicationContext ctx = 
    new JacnApplicationContext(ApplicationContext_AB.class);
AA a = (AA) ctx.getBean(ApplicationContext_AB._A);
// do whatever with a

Or if you deploy it in a web application, you configure your web.xml like this:

<context-param>
  <param-name>contextClass</param-name>
  <param-value>
    bran.spring.jacn.JacnWebApplicationContext
  </param-value>
</context-param>

<context-param>

  <param-name>contextConfigLocation</param-name>
  <param-value>
     classpath:ApplicationContext_AB.class
  </param-value>
</context-param>

The trick is setting the Spring reserved contextClass parameter to the JacnWebApplicationContext class and setting the contextConfigLocation parameter to a list of Jacn configuration classes. As simple as that.

Of course you can have multiple Jacn files. You can even mix XML and Jacn files (See that in the test cases) in the deployment. This is very good way of progressively migrating your classical Spring to Jacn Spring: leave your old stuff intact and manage your new beans in Jacn.

This pattern will allow you to reference a bean defined externally in an XML file:

package bran.jacn.test;
import bran.spring.jacn.AbstractJacnConfigFile;
public class Jacn_fooRefBean extends AbstractJacnConfigFile{
    static Foo foo;
    public void wire() throws Exception {
        foo.setBar((Bar) refBean("bar"));
    }
}
 

The trick is using the refBean method defined (shall I say declared?) in AbstractJacnConfigFile. Of course you don't get type-safety.

Spring comes with all fancy and convoluted way to describe Java functions calls and JavaBean initialization. Right now Jacn support the most commonly used constructs in everyday Spring configurations. I'm using Jacn in a full-blown web application (with about 200 managed beans) that uses JSF/Spring/Hibernate stack and I'm enjoying all the goodies my favorite IDE brings me. Now I call my experience of Spring programming "seamless".

Spring Faces, the two power horses working as one

Tapestry uses HiveMind as the bean management container. JBoss Seam builds its own so called "Bi-directional" container. Likewise JSF has its own bean management facility. The container is not as full featured as Spring, so many developer have tried integrating JSF with Spring. However in doing that people have found that using these two together usually means xml duplications and the integration solution available today is less than seamless.

Jacn's objective is to unify the bean management model of JSF and Spring, making the developer's experience of using the two systems as one, effectively filling the gap of JSF and transactional business logic layer.

A Quick Tour

First, a developer configures the web.xml as described above to use Jacn and JSF. Here is the intersting part of the web.xml using Jacn 0.5 and MyFaces 1.0.9: using JacnWebApplicationContext in place of Spring WebApplicationContext

<context-param>
  <param-name>contextClass</param-name> <!-- used by Spring -->
  <param-value>bran.spring.jacn.JacnWebApplicationContext</param-value>
 </context-param>
 <context-param>
  <param-name>contextConfigLocation</param-name> <!-- used by Spring -->
  <param-value>
  	classpath:jacn/Jacn_jsfApplogic.class 
  </param-value>
 </context-param>

After that a new entry is added to the faces-config.xml in the application section:

<application>
	 <variable-resolver>
	 	bran.spring.jacn.SpringFacesVariableResolver
	 </variable-resolver>
</application>

Now you're ready to write and configure your managed beans in Jacn. For an example, here is a
managed bean configured in the old JSF fasion in the faces-config.xml:

<managed-bean>
  <managed-bean-name>userForm</managed-bean-name>
  <managed-bean-class>com.lcare.webapp.action.UserForm</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
   <property-name>userManager</property-name>
   <value>#{userManager}</value>
  </managed-property>

  <managed-property>
    <property-name>username</property-name>
    <value>#{param.username}</value>
  </managed-property>

</managed-bean>

where the #{userManager} expression refers to a bean defined in Spring context (not shown here). Now you remove the above code and write the following in a Jacn style Spring configuration file, with
other Spring beans together:

public class Jacn_jsfApplogic extends AbstractJacnConfigFile {
	UserForm userForm;
	static UserManager userManager;
	
	// transaction manager, userDao, interceptors, etc, omitted
	public void wire() throws Exception {
		setWebScopeRequest(new Object[] {
			userForm,	
		});
		
		userForm.setUserManager(userManager);
		userForm.setUsername("#{param.username}");

		userManager.setUserDAO(userDAO);
		Properties pu = new Properties();
		pu.put("*", "PROPAGATION_REQUIRED");
		enableTx(userManager, transactionManager, pu, null, 
		    new Object[] {userSecurityInterceptor}, null);
	}
}

As shown above, the userForm, a JSF page "backing bean", seamlessly blends with userManager, a transactiona-aware business component, and the rest of other components in the business logic tier.

Note setWebScopeRequest() method is called to scope the userForm bean on web "request". The UserManager has a static modifier, indicating it is in the web "application" scope.

Another intersting statement is

userForm.setUsername("#{param.username}");

An EL expression is set on the username property of the userForm. The EL expression will be evaluated in the JSF context each time the bean is created upon a new request and the result is passed to the userForm. You surely understand that #{param.username} binds to a request parameter named "username".

With all that the userForm can now be used in JSF pages like this:

<h:inputText value="#{userForm.firstName}" />

or in Facelets:

<input type="text" jsfc="h:inputText" id="firstname" value="#{userForm.firstName}" />

So next time you see #{userForm.someProperty} in your JSF files, don't go to faces-config.xml; go to your java config file for Spring.

I hope this has given you a quick tour of how Jacn ties the JSF and Spring together. More detail is in a separate document.

Compared with other Java-based configurations

Spring can natively use Java to configure all the beans in a bean factory. The configuration is a procedure that really creates new instances of beans and register them with the container. The code will really run during the Spring startup time. The API is available but not really suitable for developers in most situation because it is very verbose and requires one to know a lot about Spring API. That's why the Spring reference almost exlusively uses XML as the configuration mechanism.

Jacn looks like Java and is not really used as Java, because it's not loaded into the JVM as java classes. Rather a Jacn file is loaded as a stream of resource just like any resources, XML, properties, etc. The Jacn bean factory, which extends a Spring application context super class, just parses it as though it's an XML file and generates a Spring compatible DOM object, which is the interface between Jacn and Spring internals. Here developers don't need to know any Spring API. They use Jacn as a less verbose replacement for XML, which happens to be Java refactoring friendly.

The Pico container uses Java to configure managed components. Again Java is used in the classical way like the Spring bean factory API.

One can view Jacn as a sort of generic configuration mechnism that provides a mapper for Spring bean definition language in XML. I can/will develop another mapper that replaces the faces-config.xml in JSF, which suffers from the same impedance mismatch like Spring XML configurations.

Traversing between Jacn and XML

Jacn's philosophy has been to make writing configurations as pleasant as possible. Sometimes some configurations will be in the hands of none-developers, such as sysadmins. Jacn has considered that and has simple solutions for that requirement.

Remember you can always mix XML and Jacn in a Spring application. So you can choose to configure some part of your system in XML and the rest in Jacn. Actually you can generate XML files from Jacn configurations(see examples in the test cases of using JacnApplicationContext). This allows you to write everything in Jacn when you actively develop your application and export selected Jacn configs to XMLs when the development comes to the final phases and you do not expect much change. I consider myself considerate :)