For the most part, Java applications are either J2EE applications or J2SE applications, and there's no ambiguity about which. J2EE applications require the services of a J2EE container, which implements a long list of J2EE APIs, including Enterprise JavaBeans (EJB), JTA, JNDI, JMS, JCA, and JMX. The J2EE APIs are designed to work together; after all, the J2EE design is the product of distilling out common requirements from hundreds of man-years of experience developing enterprise applications. Like all frameworks, a primary motivation for the J2EE APIs is "don't reinvent the wheel."
There are a few APIs that are part of the J2EE specification, but which have subsets that can easily be used by J2SE applications, such as JDBC, JSP, and servlets, but for the most part, J2EE is an all-or-nothing proposition -- most of the J2EE APIs require the services of a full-fledged J2EE container. However, there are a number of server applications developed as J2SE applications rather than J2EE applications, often for good reason. Why should developers of these applications have to reinvent the wheel? Are there parts of J2EE that can be easily borrowed by J2SE applications to provide the same benefits? What about components that are intended for use in both J2EE and J2SE applications?
One of the key design principles of J2EE is that J2EE applications can be loosely coupled -- assembled out of components whose interconnections can be defined or changed at application assembly or deployment time, rather than at component-development time. J2EE components use JNDI to find each other and the resources they need, such as JDBC and JMS connections. Technologies like JMS encourage loose coupling, allowing for flexible workflow modeling, easy distribution of processing tasks, scalability, and fault-tolerance. There are a lot of J2SE server applications that could benefit from these techniques and principles as well.
APIs such as JDBC, JMS, and JNDI are basically "middleware" -- they act as a uniform interface between an application and a disparate set of service providers. JDBC drivers exist for nearly every database server, and there are a large number of free database servers, so nearly every Java application that could benefit from using a database can easily do so. However, this is not the case with JMS. Message queuing servers are far less common than databases, especially in smaller shops. But there are a lot of applications that could benefit tremendously from the use of message queuing.
Message queuing is a powerful paradigm for building robust,
flexible, loosely coupled, scalable applications. There are a number of
commercial message queuing products, such as WebSphere MQ, Sonic,
Fiorano, JBossMQ, and SpiritWave. Just like JDBC for databases, JMS
is middleware for messaging -- it allows an application to access a
variety of message queuing products through a uniform interface,
providing abstractions such as Connection,
Topic, and Message.
Many J2SE applications use some form of message queuing without using JMS -- using thread pools, work queues, task managers, and the like. The AWT and Swing frameworks use messages (events) to communicate between the model and view layers, and JavaBeans components support a form of topic-based messaging using listeners. Message queuing offers a plethora of architectural advantages -- its inherent loose-coupling encourages a flexible, component-based approach and provides natural abstraction boundaries that help to simplify designs and reduce interconnections. As a bonus, the MQ paradigm makes it easier to design for distribution, scalability, and fault-tolerance, as it is not necessarily the case that message producers and consumers need to run in the same JVM, and the nature of most producer/consumer tasks allow for parallel processing.
It is fairly common for developers of J2SE server applications to roll
their own messaging layer, either from scratch or by building on a
library like util.concurrent. Typically, the argument
goes something like this:
MQ servers are expensive and heavyweight, and because we don't need the heavier features like persistence, distribution, transactions, and authentication, it's easier and cheaper to just roll our own in-memory messaging layer, providing only the features we need.
While this is usually true, application needs do often tend to grow over time, and some of the messaging features that were not requirements at the outset might become more important later.
Developers of message-oriented applications have a choice they
must make early on in the development process -- pick a commercial
messaging product, or roll their own cheaper, lighter-weight solution.
The Somnifugi JMS package marries these two approaches -- a
nonpersistent in-memory message-queuing service based on the
high-performance util.concurrent library, and an interface that
conforms to the JMS APIs. Compared to traditional JMS providers,
Somnifugi is quite lightweight, both in feature set and performance.
It is limited to use within a single JVM (although this restriction
could be removed) and lacks features for persistence, transactions,
and authentication. On the other hand, it is extremely fast -- so
much faster than traditional JMS implementations that it becomes
possible to use JMS in situations where performance might have
otherwise prohibited it. To demonstrate how lightweight Somnifugi is,
the distribution includes some examples of how the Swing/JavaBeans
event framework can be replaced with JMS topics.
Somnifugi also offers another significant benefit: It is now possible to develop components that use JMS interfaces, and then decide at application deployment time whether to use the fast, in-memory Somnifugi provider or a heavier but more reliable provider such as WebSphere MQ. The advantages of being able to defer this choice to deployment time are considerable -- especially because requirements can change over a project's lifecycle -- and provide an opportunity for code reuse that would not be practical with a homegrown messaging layer.
Like JDBC and JMS, JNDI is a form of middleware. And like JMS, using JNDI from J2SE applications is not quite as easy as it is with JDBC. JDBC providers are everywhere -- there are dozens of JDBC-compliant commercial and open-source database servers. While all J2EE containers must include a JNDI provider, there are relatively few JNDI providers that are not part of a J2EE container. Not only does this make it more difficult to use JNDI from J2SE applications, but it also means that J2SE developers are less likely to come into contact with JNDI and see its advantages.
Depending on the JNDI provider you are using and your application
configuration, JNDI can store arbitrary Java objects in a JNDI
namespace (with some restrictions -- some JNDI providers impose the
restriction that stored objects be serializable). It is common to use
JNDI to store static configuration data (integers and strings), JDBC
DataSource objects, JMS Connection and
Topic objects, and stateless objects (including factory
objects). Storing fully configured objects, such as JDBC
DataSource objects rather than simply configuration data
such as JDBC URLs, can also enhance the security of the application, as
the sensitive information such as authorization credentials are not
directly available to the application.
J2EE applications use JNDI as the "switchboard" for making connections between loosely coupled components -- J2EE components use JNDI to find other components that they want to use, such as EJB components, and to find resources, such as JDBC and JMS connections. Interconnections between J2EE components are defined declaratively in the component's deployment descriptor, and the container automatically binds objects at the specified place in the namespace and ensures that all resource dependencies between components are satisfied before deploying the components.
J2SE applications can use JNDI in a similar manner as J2EE applications, they just have to do a little more work to get the namespace populated. But the benefit is the same -- applications can be more loosely coupled, with components discovering each other at runtime.
While the JNDI reference implementation does not include a general purpose JNDI provider, you can download the File System (FSContext) provider from the Sun Web site. This is an example JNDI provider, which comes in source form, which accesses and stores serializable objects in files, and which also enables namespace contents to persist across program invocations. While the FSContext JNDI provider is primarily intended to be an example of how to write a JNDI provider, simple applications can also use it as a persistent data store for serialized objects or as a "stub" JNDI provider for running unit tests of components that derive their configuration from JNDI.
The JBoss open-source J2EE container also includes a more
general-purpose JNDI provider, JNPServer, which can easily be run as a
standalone JNDI provider without the JBoss container. JNP can be
accessed from remote JVMs through RMI and within the JVM without incurring
the overhead of RMI. It stores objects internally in memory in a
HashMap.
The JNP JNDI server is found in the JBoss distribution in the
jnpserver.jar JAR file; it also relies on the log4j
logging engine. To use it, you have to configure log4j, create an
appropriate jndi.properties file (see Listing 1), and arrange
to start the server by calling the main entry point in
org.jnp.server.Main either within the same JVM or in
another JVM. The class files for accessing the JNDI namespace are in
the JBoss distribution in the jnpclient.jar JAR file.
Listing 1. A jndi.properties file for JNPServer
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces # Uncomment this line only if the JNDI server is to run in another JVM; # otherwise, local JNDI requests will go over RMI #java.naming.provider.url=localhost |
Java Management Extensions (JMX)
The Java Management Extensions (JMX), is a mechanism for managing the lifecycle of
components and services. JBoss uses JMX extensively -- nearly all the
components in JBoss are delivered as JMX services. The result is that
it becomes easy to configure an application that includes only the
services you need. For each component service, you create an object,
called an MBean (managed bean), which contains lifecycle
methods (start() and stop()) and getters and
setters for exposed properties. Listing 2 shows the interface for an MBean that
describes a simple Web container service:
Listing 2. MBean interface for a simple Web server service
public interface WebServerMBean {
// Lifecycle methods
void create() throws Exception;
void start() throws Exception;
void stop();
void destroy();
// Getter and setter for listener-port property
int getPort();
void setPort(int port);
// Get the names of loaded Web applications
String[] getWebApplications();
}
|
JBoss also includes a Web application (jmx-console) that lets you view the
MBeans that are currently loaded into the JBoss server, inspect their
current state, and use a browser to read and write their properties.
(The JMX reference implementation also includes such a Web
application, called HtmlAdapter.)
While JMX was meant for J2EE, it can easily be used from a J2SE
application as well. There are at least two free JMX implementations,
the reference implementation from Sun and the open-source MX4J.
Writing an MBean to describe a component is quite simple -- typically
all you have to do is implement the start()
and stop() methods. And writing a simple
JMX "container" that loads a list of MBeans and starts them is about
40 lines of code. By following the JMX standard, not only do you get
the benefits of using JMX, such as remote property inspection and
manipulation (which is useful for debugging as well as management),
but it also becomes easier to write components that can be easily run
in both J2SE and J2EE environments.
While J2EE and J2SE are different tools for different jobs, many developers find themselves having to decide between the "lightweight" and "heavyweight" implementation for various framework services, such as messaging, configuration, or management. By using lighter-weight implementations of J2EE interfaces, such as Somnifugi JMS, developers may be able to preserve the flexibility to easily migrate to a heavier-weight solution in the long term if needed without giving up performance or ease of use in the short term.
- The specification for JMS can be found on the Sun Web site.
JMS 1.1 is part of the J2EE 1.4
specification.
- The Somnifugi
project is an in-memory message queuing system that implements the
JMS provider API.
- Somnifugi is based on the
util.concurrentlibrary, an open-source library of concurrency utilities that simplifies the building of multithreaded applications. - Learn more about the basic concepts behind JMS messaging with this Sun tutorial.
- In his August 2001 tutorial for developerWorks, e-business architect Willy Farrell provides an excellent introduction to the basic concepts behind in JMS.
- The JNP JNDI server and client provider classes are part of the JBoss distribution.
- The JMX
Reference Implementation and MX4J are freely available
implementations of the Java Management Extensions.
- "Implementing vendor-independent
JMS solutions" (developerWorks, February 2002) by Nicholas Whitehead explores how JNDI and JMS work together to abstract away the details of the messaging provider.
- "Enterprise messaging with JMS" (developerWorks, June 2003) by Kyle Gabhart explores the basics of JMS messaging.
- Browse for books on these and other technical topics.
- Find hundreds more Java technology resources on the
developerWorks Java technology zone.
Brian Goetz has been a professional software developer for the past 15 years. He is a Principal Consultant at Quiotix, a software development and consulting firm located in Los Altos, California, and he serves on several JCP Expert Groups. See Brian's published and upcoming articles in popular industry publications.





