 | Level: Introductory Brian Goetz (brian@quiotix.com), Principal Consultant, Quiotix Corp
20 Apr 2004 Most projects fall squarely into either the category of J2EE
application or J2SE application. However, there are a number of
J2EE technologies that can exist outside of the J2EE container, and
some J2SE applications may benefit from them. This month, Brian Goetz looks
at how some J2EE services can be used in J2SE applications.
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?
Loose coupling
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.
Somnifugi JMS
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.
Added flexibility
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.
Using JNDI from J2SE
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.
Free JNDI providers
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.
Summary
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.
Resources
About the author  | |  | 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. |
Rate this page
|  |