The aim
In
Part 1 I mentioned Spring's transaction management API. I believe this is quite a serious reason to think about integrating Spring framework into EJB application. But not the only one. Spring provides easy way to maintain multiple application configurations with IoC making testing configuration of your application independent of production one. Other features, like JNDI support, integration with JMS, JMX, mail and other services make the framework so popular.
Returning to our internet shop application we have to decide what we want to leave managed by EJB and what we want to put under Spring's control, and look at other options we have.
Data access. We considered JPA as an appropriate way to configure entity and DAO beans that provide basic functionality to access former. DAO stateless beans usually do not depend upon any other application tier, and really seldom use each other;
EntityManager gives all an ordinal DAO needs.
Business services. This is really a good place Spring may take care of. Injecting dependencies on other beans (e.g. DAOs), configuration parameters provided within application configuration files, managing transactions is where Spring really strong. Moreover, with Spring
testing module we can use these features directly in test classes.
Web services. JSR-181 defines API that is available within stateless beans to define them as providing Web services methods. EJB3 greatly supports this API, and though there is an option to use XFire framework together with Spring, to configure Web services, I see no reason to keep away from standard. Sometimes it is better avoid getting deep into framework hell. Though not always :)
Sample application changes
Domain tier
It is not a surprise that there is nothing to be changed. Entity beans still use JPA for O/R mapping that is controlled by EJB container.
Data access tier
There is one thing that needs to be done to each DAO implementation bean. Since we are going to publish DAO stateless beans in JNDI, so they will be available to Spring, we will use
@LocalBinding JBoss annotation to provide bean JNDI name for production environment, as follows:
@Stateless(name = "productDAO")
@LocalBinding(jndiBinding = "productDAOLocal")
public class ProductDAOImpl extends AbstractDAO<Product> implements ProductDAO {
// methods implementations
}
Every DAO bean is configured in application context configuration file like this one:
<jee:jndi-lookup id="productDAO"
proxy-interface="org.ishop.dao.ProductDAO"
jndi-name="productDAOLocal"/>
Unfortunatelly I do not know if there is any way to avoid this, like using automatic scanning.
Service tier
Minor changes should be done to service methods - to switch to Spring transaction API we change
@TransactionAttribute to
@Transactional and specify other options, if needed. Also dependency injection is now controlled by Spring, based on bean name or type wiring, or on
@Autowired annotation:
@Service("productService")
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductDAO productDAO;
@Autowired
private PurchaseDAO purchaseDAO;
@Transactional
public void save(Product product) {
productDAO.save(product);
}
@Transactional(readOnly = true)
public Product getProduct(Long id) {
return productDAO.get(id);
}
@Transactional
public Purchase purchase(Long productId, Date paymentDate) {
// method implementation
}
}
Please notice that we used
@Service that allows us to skip configuring bean in application context configuration file, relying on
auto-detection option:
We need to configure transaction management - Spring will use container JTA transaction manager to which it will redirect all transaction processing requests, based on own transaction annotations support and handling:
<bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect"
factory-method="aspectOf">
<property name="transactionManager"
ref="platformTransactionManager"/>
</bean>
<bean id="platformTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager"/>
Here we configured aspect to support
@Transactional annotations. One principal change is made to the build script - Java classes are processed by AspectJ compiler. Another option is use dynamic proxies that will do the same thing. One significant disadvantage of this approach is that to handle calls between business methods of the same service properly they should be made through the proxy.
One more good thing about Spring transaction management API is that service method may throw any exception (checked or unchecked). Instead of annotating exception class with
@ApplicationException as EJB spec requires, in
@Transactional annotation parameters we may specify which exception(s) should cause transaction rollback and which will lead to commit. Really nice.
Web services
As well as within business tier, Web service need just minor changes in API - replacement of
@EJB with
@Autowired and adding
SpringContextInterceptor interceptor to the list of method call interceptors:
@Interceptors({SpringContextInterceptor.class, OpenSessionInViewInterceptor.class})
public class ProductWebServiceImpl implements ProductWebService {
@Autowired
private ProductService productService;
// methods skipped
}
SpringContextInterceptor is responsible for application context startup when application Web service accessed the first time.
public class SpringContextInterceptor extends SpringBeanAutowiringInterceptor {
private BeanFactory beanFactory;
protected BeanFactory getBeanFactory(Object o) {
if (beanFactory == null) {
beanFactory = new ClassPathXmlApplicationContext("/applicationContext.xml").getAutowireCapableBeanFactory();
}
return beanFactory;
}
@PreDestroy
@PrePassivate
public void releaseBean(InvocationContext invocationContext) {
super.releaseBean(invocationContext);
}
@PostConstruct
@PostActivate
public void autowireBean(InvocationContext invocationContext) {
super.autowireBean(invocationContext);
}
}
Changes to unit tests
Spring provides a module to support bean wiring into test classes for both jUnit and TestNG. What we need to change in
AbstractPersistenceTest is set up application context and remove
lookup() method since we do not rely on bean wiring with EJB:
@ContextConfiguration(locations = {"classpath:/test-applicationContext.xml"})
public abstract class AbstractPersistenceTest extends AbstractTestNGSpringContextTests {
@BeforeClass(groups = "database")
public final void setUpDatabase() throws NamingException, IOException {
Properties props = new Properties();
props.load(AbstractPersistenceTest.class.getResourceAsStream("/persistence.properties"));
new InitialContext(props);
}
@BeforeClass(dependsOnMethods = "setUpDatabase")
protected void springTestContextPrepareTestInstance() throws Exception {
super.springTestContextPrepareTestInstance();
}
}
Here method
springTestContextPrepareTestInstance has been overriden to be called when EJB container has been set up by
setUpDatabase().
Testing code does not need serious changes as well as production code does not - use dependency injection with
@Autowired instead of looking up for required resource in JNDI.
I strongly believe Spring offers a lot of features which when used in enterprise level applications may reduce coupling of beans and tiers, making daily development and testing easier.
Source code
ishop.ejb-testng-spring.zipThanks you!