Externalizing the Configuration: XML-driven Config File
While its API is straightforward and clean, the sample's direct calls to the Jetty API leave configuration -- port numbers, context paths, servlet class names -- buried in code. Jetty provides an XML-based configuration as an alternative, such that you can externalize all of this information and keep your code clean.
The XML configuration is based on Java's Reflection API. Classes in the
java.lang.reflect represent Java methods and classes, such that you can instantiate objects and invoke their methods based on their names and argument types. Behind the scenes, Jetty's XML config parser translates the XML elements and attributes into Reflection calls.
This excerpt from the
Step2Driver sample class is a revamped version of
Step1Driver. As far as configuration is concerned, there's just enough Jetty-related code to load the file.
URL serviceConfig = /* load XML file */ ; // can use an InputStream or URL XmlConfiguration serverFactory = new XmlConfiguration( serviceConfig ) ; Server service = (Server) serverFactory.newInstance() ;
This admittedly doesn't save much code compared to the trivial example in
Step1Driver. Then again, this is as much code as
Step2Driver will ever need, even as you add more servlets or web apps. Directly calling methods on
Service and context objects is a case of diminishing returns as the configuration grows more complex.
Listing 1 is the XML file loaded by
Step2Driver. The toplevel
class attribute (marker 1) specifies which class to instantiate. Here it's a Jetty
<!-- 1 --> <Configure class="org.mortbay.jetty.Server"> <!-- 2 --> <Call name="addListener"> <Arg> <!-- 3 --> <New class="org.mortbay.http.SocketListener"> <!-- 4 --> <Set name="Host"> <!-- 5 --> <SystemProperty name="service.listen.host" default="localhost" /> </Set> <Set name="Port"> <SystemProperty name="service.listen.port" default="7501" /> </Set> </New> </Arg> </Call> <Call name="getContext"> <Arg>/embed</Arg> <!-- call methods on the return value of Server.getContext() --> <!-- 6 --> <Call name="addServlet"> <!-- servlet name --> <Arg>"Simple"</Arg> <!-- URL pattern --> <Arg>/TryThis/*</Arg> <!-- servlet class --> <Arg>sample.SimpleServlet</Arg> </Call> </Call> </Configure>
<Call> elements represent method invocations on the
Server object defined in the parent element. The call to
addListener(), at marker (2), itself has child
<Arg> elements that specify method arguments. Here I could have passed the string value for the listen address, but
addListener() is overloaded to take a
SocketListener object. For the sake of demonstration I call the
<New> element to instantiate a new
SocketListener at market (3). Markers 2 and 3 are the XML equivalent of calling
server.addListener( new SocketListener( ... ) ) ;
To configure the
SocketListener itself, you could use a
<Call> element to invoke its
setHost() method. Since this method follows JavaBean naming and signature conventions, the sample code instead uses the
<Set> element (4) as a shortcut. Behind the scenes, Jetty prepends "
set" to the value of the
name attribute to determine the method name to invoke (here,
setHost()'s arguments aren't explicitly listed here. Instead, the
<SystemProperty> element (5) accepts the name of the system property from which to fetch the values, here
service.listen.port. In case these system properties are not defined, the
<SystemProperty> element also lets you specify a default value using the
default attribute. Together, markers 4 and 5 are the same as calling:
socketListener.setHost( System.getProperty( "service.listen.host" , "localhost" ) ) ;
Finally, note the
<Call> elements inside the
getContext() (6). The inner
<Call> is invoked on the value returned by the outer
<Call>. Here, then,
addServlet() is called on the context object returned by
server.getContext().addServlet( ... ) ;
Kudos to the Jetty team for taking the XML configuration one step farther: notice that all Jetty-specific calls in Listing 1 are element or attribute values, not names. This means the XML configuration can be used with any classes, even non-Jetty classes. Depending on how your app is written, you could configure it all through Jetty's XML config.