Spring into Seam, Part 1: Build a Spring-Seam hybrid component

Which is better, Seam or Spring? Learn why you don't have to choose

1 2 3 4 5 6 Page 5
Page 5 of 6

Dealing with AOP proxies and covert beans

The definition of tournamentManager above is oversimplified because it does not have transactional capabilities. Spring prides itself on providing declarative services, such as transactions, that are completely transparent to the bean on which they are registered. However, it's important to understand the mechanism by which these services are applied because Spring's proxy mechanism has the potential to throw a wrench into the Spring-Seam integration. We are going to see an example of this problem and learn how to work around it. In Listing 9, let's start by adding some transaction advice to this bean using Spring's AOP configuration. The transaction manager definition is also shown in this listing.

Listing 9. Adding transaction advice with AOP

<aop:config proxy-target-class="true" >
  <aop:advisor id="courseManagerTx" advice-ref="defaultTxAdvice"
    pointcut="execution(* org.open18.partner.business.TournamentManager.*(..))"/>
</aop:config>   

<tx:advice id="defaultTxAdvice">
  <tx:attributes>
    <tx:method name="get*" read-only="true"/>
    <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

<bean id="transactionManger"
  class="org.springframework.orm.jpa.JpaTransactionManager">
  <!-- entityManagerFactory defined elsewhere -->
  <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean id="tournamentManager" 
  class="org.open18.partner.business.impl.TournamentManagerImpl">
  <seam:component/>
</bean>
Why doesn't Seam support JDK dynamic proxies?
A JDK dynamic proxy is a dynamically generated class that implements a set of interfaces specified at runtime. A dynamic proxy class cannot be cast to the target bean's class, nor does it provide access to the target bean (i.e., the proxied object). The proxy just happens to implement the same interface(s) as the target bean. Cglib proxies, on the other hand, can be treated more or less like the target bean itself. Cglib creates a new concrete class on the fly that is a subclass (in other words, a class that extends) the target bean's class. Because Seam needs direct access to the target bean to expose it as a Seam component, it is mandatory that the target bean be enhanced using Cglib rather than act through a JDK dynamic proxy.

You may not be a big fan of AOP pointcuts, but bear with me for the time being. This configuration essentially tells the PlatformTransactionManager implementation, which in this case is the JpaTransactionManager, to apply transactions around all of the methods of classes that implement the TournamentManager interface. Any method beginning with get uses a read-only transaction (no flushing), whereas the remaining transactions force a flush of the persistence context before committing. So far, this is just standard Spring behavior and does not affect how the Seam component is derived from the target bean.

The setting that does affect how the Seam component is created is the proxy-target-class attribute on the <aop:config> element, highlighted in Listing 9 in bold. This point I am about to make is very important. Seam cannot adopt JDK proxied Spring beans. Seam can only deal with Cglib and Javassist instrumented classes.

Huh? If you are new to dynamic proxying and bytecode manipulation, this may sound like Greek to you. Basically, as part of the AOP mechanism, Spring wraps your Spring bean in a layer that can intercept method calls and add functionality before and after each invocation. This technique is exactly what Seam does to add capabilities such as bijection to its components. However, Seam weaves in this behavior using runtime bytecode enhancement provided by the Javassist library. Spring, on the other hand, uses JDK dynamic proxies by default. The JDK proxying infrastructure is only capable of creating proxies for interfaces, whereas the bytecode enhancement libraries, including Javassist and Cglib, can work directly on classes.

Suffice to say, Seam cannot work with the Spring bean if it uses JDK dynamic proxying. This issue doesn't come up when accessing a Spring bean through the EL resolver because Seam only calls on the methods. Here, the Spring bean is being converted into a Seam component, so Seam is involved in the internals of the bean creation process and needs the extra level of visibility.

The situation is this: Seam cannot handle JDK proxies. Spring cannot enhance beans using Javassist. To meet in the middle, Spring can use runtime bytecode enhancement provided by Cglib, which is enough to make Seam happy. The switch to this alternate proxy provider happens on a per-bean basis. In the case of the <aop:confg> element, that switch is made using the proxy-target-class attribute. Despite the horrible name for this attribute, a value of true enables Cglib proxies rather than dynamic JDK proxies. If you do not make this switch, you will get the following exception when you try to make a proxied Spring bean into a Spring-Seam hybrid:

Caused by: java.lang.RuntimeException: Seam cannot wrap JDK proxied IoC beans.
Please use CGLib or Javassist proxying instead at org.jboss.seam.ioc.ProxyUtils.enhance(ProxyUtils.java:52)

What if pointcuts just aren't your cup of tea? You can opt, instead, to use Spring's TransactionProxyFactoryBean to wrap your service layer object in transactions. This bean has a similar switch for its proxy setting, but that isn't the only issue you will encounter. You now have a new problem. When you use a proxy factory bean, the class name of the target object is no longer on the <bean> definition. Listing 10 offers a look.

Listing 10. Using a proxy factory bean to avoid pointcuts

<bean id="tournamentManager"
  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <seam:component class="org.open18.partner.service.impl.TournamentManagerImpl"/>
  <property name="proxyTargetClass" value="true"/>
  <property name="transactionAttributes">
    <props>
      <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
      <prop key="*">PROPAGATION_REQUIRED</prop>
    </props>
  </property>
  <property name="proxyInterfaces" value="org.open18.partner.service.TournamentManager"/>
  <property name="target">
    <bean class="org.open18.partner.service.impl.TournamentManagerImpl"/>
  </property>
</bean>

This bean definition has the same transaction properties as the one in Listing 9, which used AOP pointcut expressions. It also sets the proxyTargetClass property to true to enable Cglib proxying. However, notice that the <seam:component> element is now using the class attribute to specify the name of the class that implements the service layer interface. This class is also supplied in the target property of the bean definition. Why specify it in both places?

The implementation class must be stated explicitly so that Seam knows what type of object the Spring factory bean produces. This information is buried deep inside the <bean> definition, where Seam is unable to find it. Without this information, Seam cannot perform operations that require knowledge of the hybrid component's type, such as injecting it into the property of another component. What's worse is that without this hint, Seam actually gets the type wrong, given that the class attribute on the <bean> definition is consulted to determine the component's type. In this example, Seam assumes that the class of the tournamentManager component is TransactionFactoryProxyBean.

This problem also comes up when using Spring's bean definition inheritance feature (the parent attribute on the <bean> definition). When the <seam:component> is used in a child bean definition, you once again have to educate Seam about which type to expect. This incompability with bean inheritence will likely be resolved soon, since all of the information Seam needs is there in the bean definition hierarchy -- it's just a matter of Seam looking for it. However, the point remains that if there is ever a case where Seam cannot determine the type, or gets the type wrong, you can supply an override using the class attribute.

Although the class attribute is required to get the hybrid component registered properly, Seam has no problem working with a Spring factory bean once the hybrid component is setup. When you inject the tournamentManager component into a TournamentManager property on another Seam component, Seam correctly unwraps the factory bean to get to the underlying proxied target object, as opposed to trying to inject the factory bean itself. What you also might find interesting is that Seam can correctly apply component configuration properties to the target bean.

That gets us started with creating Seam components with the <seam:component> tag. Let's give more thought to the significance of a Spring bean becoming a Seam component and learn how to fine-tune the behavior of the Seam component that it generates.

1 2 3 4 5 6 Page 5
Page 5 of 6