I recently worked on a quite complex project mixing many Java EE 6
technologies (such as JPA, JAXB, JMS, JTA, JAX-RS, etc…). For
productivity and planning reasons, the prototyped application was
designed as a standalone pure Spring application. When the development
of the real application started, we re-challenged our initial choice
(i.e. Spring v3) and analyzed the interest of switching to a Java EE 6
app server like GlassFish or JBoss.
This finally ended up in two major questions:
- can we do in Java EE 6 everything we can do in Spring ?
- can we do that as easy as in Spring ?
Well, I would say that, globally, the answer is: yes we can !
I
do not want to reopen the (endless) debate of knowing which one,
between Spring and Java EE 6, is the best. No, I just want to share with
you my experience regarding this migration. I was – and I am still – a
real Spring fanboy (which I, historically speaking, discovered after
having been literally disgusted by EJB’s 1.0), but I am also aware of
the recent progress, not to say simplifications, that have been
introduced in Java EE these last years, as well as the impressive speed
improvements on the side of Java EE 6 application servers.
Let us
now study in details some typical requirements of “enterprise”
applications, and compare the code to produce in both worlds for:
- Contexts & Dependency Injection
- Messaging
- Transaction management
- Web services
This
comparison should provide you with some concrete decision elements in
the event you hesitate to migrate from one technology to the other…
Part I : Contexts & Dependency Injection (CDI)
Spring
allows you to define beans using various stereotypes (eg @Repository,
@Service, @Controller, and @Component). The one to choose is not that
important (that’s not entirely true. For instance, tagging your DAO as
@Repository will add the automatic translation of SQL exceptions) as
this distinction is mostly intended to IDE’s (in order to categorize
beans). Optionally, you can give your bean an alias.
1 | public interface MyInterface {...} |
1 | import org.springframework.stereotype.Component; |
4 | public class MySpringBean implements MyInterface {...} |
6 | @Component ( "firstBeanMock" ) |
7 | public class MockImpl implements MyInterface {...} |
Java
EE provides a very similar annotation (@Named) but its use should be
limited to pure pojo’s. In case of service-oriented beans (especially
transactional coarse-grained services), consider using a (preferably
stateless) EJB – namely because they offer better scalability.
1 | import javax.inject.Named; |
4 | public class MyJeeBean implements MyInterface {...} |
1 | import javax.ejb.Stateless; |
3 | @Stateless (name= "firstService" ) |
4 | public class MyJeeService implements MyInterface {...} |
Also beware that, in contrary to Spring, singletons should be explicitly marked as such in Java EE:
1 | import javax.inject.Singleton; |
4 | public class MyJeeSingleton implements MyInterface {...} |
Remark:
you may get confused when choosing between a “javax.inject.Singleton”
and a “javax.ejb.Singleton”. The first one defines a standard POJO
managed by the container (aka a “
Managed Bean”
in the Java EE world), while the second one defines an “Enterprise
Bean”. Remember that the later is designed for concurrent access (a
client doesn’t need to worry about any other clients that may be
simultaneously invoking the same methods of the singleton) and also
offers transaction management facilities (see further).
Now that
we have registered (and optionally named) our beans, we can inject them
in other beans. Once again, the procedure is somewhat similar at both
sides:
SPRING
01 | import org.springframework.stereotype.Component; |
02 | import org.springframework.beans.factory.annotation.Autowired; |
03 | import org.springframework.beans.factory.annotation.Qualifier; |
06 | public class UseCaseHandler { |
09 | @Qualifier ( "firstBean" ) |
10 | private MyInterface serviceFacade; |
JAVA EE 6
01 | import javax.inject.Named; |
02 | import javax.inject.Inject; |
05 | public class UseCaseHandler { |
09 | private MyInterface serviceFacade; |
Remark:
The JSR-330 has unified the way to inject managed beans. This
concretely means that the @Inject annotation can be used to inject
simple POJOs as well as EJB’s (making thereby the @EJB annotation a bit
obsolete).
Fine ! However, in the real world, the name (eg
“firstBean”) of the beans we want to inject might be dynamic. This is
particularly true as soon as you play with behavioral patterns,
generics, etc…
In Spring, this is pretty easy. You can for
instance make your bean ApplicationContext-aware, so that you can then
use the injected Spring context in order to lookup for specific bean
instances:
01 | import org.springframework.beans.BeansException; |
02 | import org.springframework.context.ApplicationContext; |
03 | import org.springframework.context.ApplicationContextAware; |
04 | import org.springframework.stereotype.Service; |
06 | import com.javacodegeeks.Request; |
09 | public class Dispatcher implements ApplicationContextAware { |
11 | private ApplicationContext appContext; |
13 | public void setApplicationContext(ApplicationContext ctx) throws BeansException { |
17 | public void dispatch(Request request) throws Exception { |
18 | String beanName = "requestHandler_" + request.getRequestTypeId(); |
19 | RequestHandler myHandler = appContext.getBean(beanName, RequestHandler. class ); |
20 | myHandler.handleRequest(request); |
1 | public interface RequestHandler { |
2 | public void handleRequest(Request request); |
1 | @Component ( "requestHandler_typeA" ) |
2 | public class HandlerA implements RequestHandler {...} |
1 | @Component ( "requestHandler_typeB" ) |
2 | public class HandlerB implements RequestHandler {...} |
In Java EE 6, the same is possible but yet requires a bit more lines of code (that could be centralized in an helper class):
02 | import javax.inject.Inject; |
03 | import javax.inject.Named; |
04 | import javax.enterprise.context.spi.CreationalContext; |
05 | import javax.enterprise.inject.spi.Bean; |
06 | import javax.enterprise.inject.spi.BeanManager; |
08 | import com.javacodegeeks.Request; |
11 | public class Dispatcher |
14 | private BeanManager beanManager; |
16 | public void dispatch(Request request) throws Exception { |
17 | String beanName = "requestHandler_" + request.getRequestTypeId(); |
18 | RequestHandler myHandler = this .getBean(beanName, RequestHandler. class ); |
19 | myHandler.handleRequest(request); |
22 | @SuppressWarnings ( "unchecked" ) |
23 | private <T> T getBean(String name, Class<T> clazz) throws Exception { |
24 | Set<Bean<?>> founds = beanManager.getBeans(name); |
25 | if ( founds.size()== 0 ) { |
26 | throw new Exception( "No such bean found: " +name); |
28 | Bean<T> bean = (Bean<T>) founds.iterator().next(); |
29 | CreationalContext<T> cc = beanManager.createCreationalContext(bean); |
30 | T instance = (T) beanManager.getReference(bean, clazz, cc); |
1 | public interface RequestHandler { |
2 | public void handleRequest(Request request); |
1 | @Named ( "requestHandler_typeA" ) |
2 | public class HandlerA implements UseCaseHandler {…} |
1 | @Named ( "requestHandler_typeB" ) |
2 | public class HandlerB implements UseCaseHandler {...} |
PART II : JMS
Java Messaging Service eases the implementation of a loosely coupled distributed communication.
This is why it has become a classical technique in Enterprise Application Integration (EAI).
Spring has an outstanding JMS support. You can very quickly setup JMS producers or consumers,
with destination resolvers, and optionally with an automatic conversion
of JMS messages into pojos (and vice-versa). On the other hand, J2EE
comes with a rich set of annotations in order to access or define JMS
resources such as queue/topics, connection or messages-oriented beans.
Let’s start with a JMS client that receives messages, that is a message consumer (or subscriber):
SPRING
01 | < bean id = "jndiTemplate" class = "org.springframework.jndi.JndiTemplate" > |
02 | < property name = "environment" > |
07 | < bean id = "jmsConnectionFactory" class = "org.springframework.jndi.JndiObjectFactoryBean" > |
08 | < property name = "jndiTemplate" ref = "jndiTemplate" /> |
09 | < property name = "jndiName" value = "java:/JmsXA" /> |
12 | < bean id = "jndiDestResolver" |
13 | class = "org.springframework.jms.support.destination.JndiDestinationResolver" > |
14 | < property name = "jndiTemplate" ref = "jndiTemplate" /> |
17 | < bean id = "jmsContainer" |
18 | class = "org.springframework.jms.listener.DefaultMessageListenerContainer" > |
19 | < property name = "connectionFactory" ref = "jmsConnectionFactory" /> |
20 | < property name = "destinationResolver" ref = "jndiDestResolver" /> |
21 | < property name = "destinationName" value = "queue/myQueue" /> |
22 | < property name = "messageListener" ref = "myMsgConsumer" /> |
25 | < bean id = "myMsgConverter" class = "com.javacodegeeks.MsgToRequestConverter" /> |
27 | < bean id = "myMsgConsumer" class = "com.javacodegeeks.MsgConsumer" /> |
01 | import javax.jms.Message; |
02 | import javax.jms.MessageListener; |
03 | import org.springframework.beans.factory.annotation.Autowired; |
04 | import org.springframework.jms.support.converter.MessageConverter; |
06 | import com.javacodegeeks.Request; |
07 | import com.javacodegeeks.Dispatcher; |
10 | * Example of message consumer (Message-Driven-Pojo) in Spring |
12 | public class MsgConsumer implements MessageListener { |
15 | private MessageConverter msgConverter; |
18 | private Dispatcher dispatcher; |
20 | public void onMessage(Message message) { |
22 | Request request = (Request) msgConverter.fromMessage(message); |
23 | dispatcher.dispatch(request); |
24 | } catch (Exception e) { |
JAVA EE 6
01 | import javax.inject.Inject; |
02 | import javax.jms.Message; |
03 | import javax.jms.MessageListener; |
04 | import javax.ejb.MessageDriven; |
05 | import javax.ejb.ActivationConfigProperty; |
07 | import com.javacodegeeks.Request; |
08 | import com.javacodegeeks.Dispatcher ; |
09 | import com.javacodegeeks.MsgToRequestConverter; |
12 | * Example of message consumer (Message-Driven-Bean) in JEE |
14 | @MessageDriven (activationConfig = { |
15 | @ActivationConfigProperty (propertyName= "destinationType" , propertyValue= "javax.jms.Queue" ), |
16 | @ActivationConfigProperty (propertyName= "destination" , propertyValue= "queue/myQueue" ) |
18 | public class MsgConsumer implements MessageListener { |
21 | private MsgToRequestConverter msgConverter; |
24 | private Dispatcher dispatcher; |
26 | public void onMessage(Message message) { |
28 | Request request = msgConverter.fromMessage(message); |
29 | dispatcher.dispatch(request); |
30 | } catch (Exception e) { |
Let’s now code a JMS client that creates and sends messages, that is a message producer (or publisher):
SPRING
01 | < bean id = "jndiTemplate" class = "org.springframework.jndi.JndiTemplate" > |
02 | < property name = "environment" > |
07 | < bean id = "jmsConnectionFactory" class = "org.springframework.jndi.JndiObjectFactoryBean" > |
08 | < property name = "jndiTemplate" ref = "jndiTemplate" /> |
09 | < property name = "jndiName" value = "java:/JmsXA" /> |
12 | < bean id = "jndiDestResolver" |
13 | class = "org.springframework.jms.support.destination.JndiDestinationResolver" > |
14 | < property name = "jndiTemplate" ref = "jndiTemplate" /> |
17 | < bean id = "jmsTemplate" class = "org.springframework.jms.core.JmsTemplate" > |
18 | < property name = "connectionFactory" ref = "jmsConnectionFactory" /> |
19 | < property name = "destinationResolver" ref = "jndiDestResolver" /> |
20 | < property name = "messageConverter" ref = "myMsgConverter" /> |
23 | < bean id = "myMsgConverter" class = "com.javacodegeeks.MsgConverter" > |
01 | import org.springframework.stereotype.Component; |
02 | import org.springframework.beans.factory.annotation.Autowired; |
03 | import org.springframework.jms.core.JmsTemplate; |
04 | import com.javacodegeeks.Request; |
07 | * Example of message producer component in Spring |
10 | public class MsgProducer { |
13 | private JmsTemplate jmsTemplate; |
15 | public void postRequest(Request request) throws Exception { |
16 | jmsTemplate.convertAndSend( "queue/myQueue" , request); |
JAVA EE 6
01 | import javax.annotation.PostConstruct; |
02 | import javax.annotation.PreDestroy; |
03 | import javax.annotation.Resource; |
04 | import javax.inject.Inject; |
05 | import javax.jms.Connection; |
06 | import javax.jms.ConnectionFactory; |
07 | import javax.jms.JMSException; |
08 | import javax.jms.Message; |
09 | import javax.jms.MessageProducer; |
10 | import javax.jms.Queue; |
11 | import javax.jms.Session; |
12 | import javax.ejb.Stateless; |
13 | import javax.ejb.EJBException; |
15 | import com.javacodegeeks.Request; |
16 | import com.javacodegeeks.MsgToRequestConverter; |
19 | * Example of message producer (here a session bean) in JEE |
21 | @Stateless (name= "msgProducer" ) |
22 | public class MsgProducer { |
25 | private MsgToRequestConverter msgConverter; |
27 | @Resource (mappedName= "java:/JmsXA" ) |
28 | private ConnectionFactory connectionFactory; |
30 | @Resource (mappedName= "queue/myQueue" ) |
33 | private Connection jmsConnection; |
37 | private void initialize() { |
39 | jmsConnection = connectionFactory.createConnection(); |
40 | } catch (JMSException e) { |
41 | throw new EJBException(e); |
47 | private void cleanup() { |
49 | if (jmsConnection!= null ) jmsConnection.close(); |
50 | } catch (JMSException e) { |
51 | throw new EJBException(e); |
56 | public void postRequest(Request request) throws Exception { |
57 | Session session = null ; |
58 | MessageProducer producer = null ; |
60 | session = jmsConnection.createSession( false , Session.AUTO_ACKNOWLEDGE); |
61 | producer = session.createProducer(queue); |
62 | Message msg = msgConverter.toMessage(request, session); |
66 | if (producer!= null ) producer.close(); |
67 | if (session!= null ) session.close(); |
68 | } catch (Exception e) { |
69 | System.err.println( "JMS session not properly closed: " + e); |
Remarks:
- Do
not forget that, in contrary to JMS connections and JMS queues, JMS
sessions are not thread-safe. Sessions should therefore not be shared by
all bean instances, nor be created in the constructor or in a
PostConstruct method.
- PostConstruct and PreDestroy methods
should only throw runtime exceptions; this is the reason why JMS
exceptions have to be wrapped (for instance) into EJB exceptions.
Part III : Transaction management
The
need for transactions is crucial in system architecture, especially
with the advent of SOA. In such architectures, coarse-grained
transactional services can be built by assembling existing – possibly
also transactional – smaller services (“
microservices”).
Both Spring and Java EE fulfills this need by offering a powerful declarative (annotation-based) transaction management.
SPRING
2 | < tx:annotation-driven transaction-manager = "txManager" /> |
5 | < bean id = "txManager" class = "org.springframework.orm.jpa.JpaTransactionManager" > |
01 | import org.springframework.stereotype.Service; |
02 | import org.springframework.transaction.annotation.Transactional; |
03 | import org.springframework.transaction.annotation.Propagation; |
05 | import com.javacodegeeks.Request; |
06 | import com.javacodegeeks.RequestProcessor; |
09 | public class RequestProcessorImpl implements RequestProcessor { |
11 | @Transactional (propagation=Propagation.REQUIRED, rollbackFor=Exception. class ) |
12 | public void process(Request request) throws Exception { |
JAVA EE 6
01 | import javax.ejb.Stateless; |
02 | import javax.ejb.TransactionAttribute; |
03 | import javax.ejb.TransactionAttributeType; |
04 | import javax.ejb.TransactionManagement; |
05 | import javax.ejb.TransactionManagementType; |
07 | import com.javacodegeeks.Request; |
08 | import com.javacodegeeks.RequestProcessor; |
11 | @TransactionManagement (value=TransactionManagementType.CONTAINER) |
12 | public class RequestProcessorImpl implements RequestProcessor { |
14 | @TransactionAttribute (TransactionAttributeType.REQUIRED) |
15 | public void process(Request request) throws Exception { |
Be
very careful with runtime/unchecked exceptions in Java EE. By default,
they are automatically wrapped by the EJB container into an
EJBException, which may cause surprising results (especially in
try…catch statements!). If you need finer tuning of rollback cases,
consider tagging such runtime exceptions as applicative exceptions,
either using the @ApplicationException annotation, or by augmenting the
ejb descriptor like this:
3 | < application-exception > |
4 | < exception-class >java.lang.NullPointerException</ exception-class > |
5 | < rollback >true</ rollback > |
6 | </ application-exception > |
Part IV : Restful web services
Enterprise
applications often need to expose some of their services to the outside
world, typically through internet. This is where web services are
coming into play. Like JMS (for asynchronous communication), web
services are another classical integration technique for implementing a
synchronous, request-response oriented, communication using XML (or
JSON) as exchange format.
SPRING
2 | < servlet-name >ws</ servlet-name > |
3 | < servlet-class >org.springframework.web.servlet.DispatcherServlet</ servlet-class > |
6 | < servlet-name >ws</ servlet-name > |
7 | < url-pattern >/services/*</ url-pattern > |
2 | < mvc:annotation-driven /> |
01 | import org.springframework.beans.factory.annotation.Autowired; |
02 | import org.springframework.stereotype.Controller; |
03 | import org.springframework.web.bind.annotation.PathVariable; |
04 | import org.springframework.web.bind.annotation.RequestMapping; |
05 | import org.springframework.web.bind.annotation.RequestMethod; |
06 | import org.springframework.web.bind.annotation.ResponseBody; |
08 | import com.javacodegeeks.Geek; |
09 | import com.javacodegeeks.GeekService; |
12 | @RequestMapping ( "/geeks" ) |
13 | public class GeekWebService { |
16 | GeekService bizService; |
18 | @RequestMapping (value= "/{id}" , method=RequestMethod.GET) |
20 | public Geek getGeek( @PathVariable ( "id" ) long geekId) { |
21 | return bizService.findGeek(geekId); |
01 | import javax.xml.bind.annotation.XmlAttribute; |
02 | import javax.xml.bind.annotation.XmlElement; |
03 | import javax.xml.bind.annotation.XmlRootElement; |
05 | @XmlRootElement (name= "geek" ) |
12 | public String getName() { |
16 | public void setName(String name) { |
25 | public void setId(Long id) { |
JAVA EE 6
01 | import javax.inject.Inject; |
02 | import javax.ws.rs.GET; |
03 | import javax.ws.rs.Path; |
04 | import javax.ws.rs.PathParam; |
05 | import javax.ws.rs.Produces; |
06 | import javax.ws.rs.core.MediaType; |
08 | import com.javacodegeeks.Geek; |
09 | import com.javacodegeeks.GeekService; |
12 | @Produces (MediaType.APPLICATION_XML) |
13 | public class GeekWebService { |
16 | GeekService bizService; |
20 | public Geek getGeek( @PathParam ( "id" ) long geekId) { |
21 | return bizService.findGeek(geekId); |
01 | import javax.xml.bind.annotation.XmlAttribute; |
02 | import javax.xml.bind.annotation.XmlElement; |
03 | import javax.xml.bind.annotation.XmlRootElement; |
05 | @XmlRootElement (name= "geek" ) |
12 | public String getName() { |
16 | public void setName(String name) { |
25 | public void setId(Long id) { |
Remark:
some JAX-RS implementations, like JBoss RestEasy, do not require to
modify the web.xml in order to configure and install web services…
PART V : Conclusion
Arguing
that things are in Spring much simpler, much lighter than in Java EE is
not – more exactly, no more – true. It is merely a matter of taste.
Furthermore, recent Java EE 6 application servers (like GlassFish 3 or
JBoss 6 & 7) are
booting
really fast, actually nearly as fast as Spring applications.
Nevertheless, in a “best-of-breed” perspective, it may still be
interesting to combine both technologies; this will be the subject of my
next post on JCG
No comments:
Post a Comment