Jetty Component Architecture
Applications that use the Jetty libraries (both client and server) create objects from Jetty classes and compose them together to obtain the desired functionalities.
A client application creates a ClientConnector
instance, a HttpClientTransport
instance and an HttpClient
instance and compose them to have a working HTTP client that uses to call third party services.
A server application creates a ThreadPool
instance, a Server
instance, a ServerConnector
instance, a Handler
instance and compose them together to expose an HTTP service.
Internally, the Jetty libraries create even more instances of other components that also are composed together with the main ones created by applications.
The end result is that an application based on the Jetty libraries is a tree of components.
In server application the root of the component tree is a Server
instance, while in client applications the root of the component tree is an HttpClient
instance.
Having all the Jetty components in a tree is beneficial in a number of use cases. It makes possible to register the components in the tree as JMX MBeans so that a JMX console can look at the internal state of the components. It also makes possible to dump the component tree (and therefore each component’s internal state) to a log file or to the console for troubleshooting purposes.
Jetty Component Lifecycle
Jetty components typically have a life cycle: they can be started and stopped.
The Jetty components that have a life cycle implement the org.eclipse.jetty.util.component.LifeCycle
interface.
Jetty components that contain other components implement the org.eclipse.jetty.util.component.Container
interface and typically extend the org.eclipse.jetty.util.component.ContainerLifeCycle
class.
ContainerLifeCycle
can contain these type of components, also called beans:
-
managed beans,
LifeCycle
instances whose life cycle is tied to the life cycle of their container -
unmanaged beans,
LifeCycle
instances whose life cycle is not tied to the life cycle of their container -
POJO (Plain Old Java Object) beans, instances that do not implement
LifeCycle
ContainerLifeCycle
uses the following logic to determine if a bean should be managed, unmanaged or POJO:
-
the bean implements
LifeCycle
-
the bean is not started, add it as managed
-
the bean is started, add it as unmanaged
-
-
the bean does not implement
LifeCycle
, add it as POJO
When a ContainerLifeCycle
is started, it also starts recursively all its managed beans (if they implement LifeCycle
); unmanaged beans are not started during the ContainerLifeCycle
start cycle.
Likewise, stopping a ContainerLifeCycle
stops recursively and in reverse order all its managed beans; unmanaged beans are not stopped during the ContainerLifeCycle
stop cycle.
Components can also be started and stopped individually, therefore activating or deactivating the functionalities that they offer.
Applications should first compose components in the desired structure, and then start the root component:
class Monitor extends AbstractLifeCycle
{
}
class Root extends ContainerLifeCycle
{
// Monitor is an internal component.
private final Monitor monitor = new Monitor();
public Root()
{
// The Monitor life cycle is managed by Root.
addManaged(monitor);
}
}
class Service extends ContainerLifeCycle
{
// An instance of the Java scheduler service.
private ScheduledExecutorService scheduler;
@Override
protected void doStart() throws Exception
{
// Java's schedulers cannot be restarted, so they must
// be created anew every time their container is started.
scheduler = Executors.newSingleThreadScheduledExecutor();
// Even if Java scheduler does not implement
// LifeCycle, make it part of the component tree.
addBean(scheduler);
// Start all the children beans.
super.doStart();
}
@Override
protected void doStop() throws Exception
{
// Perform the opposite operations that were
// performed in doStart(), in reverse order.
super.doStop();
removeBean(scheduler);
scheduler.shutdown();
}
}
// Create a Root instance.
Root root = new Root();
// Create a Service instance.
Service service = new Service();
// Link the components.
root.addBean(service);
// Start the root component to
// start the whole component tree.
root.start();
The component tree is the following:
Root
├── Monitor (MANAGED)
└── Service (MANAGED)
└── ScheduledExecutorService (POJO)
When the Root
instance is created, also the Monitor
instance is created and added as bean, so Monitor
is the first bean of Root
.
Monitor
is a managed bean, because it has been explicitly added to Root
via ContainerLifeCycle.addManaged(…)
.
Then, the application creates a Service
instance and adds it to Root
via ContainerLifeCycle.addBean(…)
, so Service
is the second bean of Root
.
Service
is a managed bean too, because it is a LifeCycle
and at the time it was added to Root
is was not started.
The ScheduledExecutorService
within Service
does not implement LifeCycle
so it is added as a POJO to Service
.
It is possible to stop and re-start any component in a tree, for example:
class Root extends ContainerLifeCycle
{
}
class Service extends ContainerLifeCycle
{
// An instance of the Java scheduler service.
private ScheduledExecutorService scheduler;
@Override
protected void doStart() throws Exception
{
// Java's schedulers cannot be restarted, so they must
// be created anew every time their container is started.
scheduler = Executors.newSingleThreadScheduledExecutor();
// Even if Java scheduler does not implement
// LifeCycle, make it part of the component tree.
addBean(scheduler);
// Start all the children beans.
super.doStart();
}
@Override
protected void doStop() throws Exception
{
// Perform the opposite operations that were
// performed in doStart(), in reverse order.
super.doStop();
removeBean(scheduler);
scheduler.shutdown();
}
}
Root root = new Root();
Service service = new Service();
root.addBean(service);
// Start the Root component.
root.start();
// Stop temporarily Service without stopping the Root.
service.stop();
// Restart Service.
service.start();
Service
can be stopped independently of Root
, and re-started.
Starting and stopping a non-root component does not alter the structure of the component tree, just the state of the subtree starting from the component that has been stopped and re-started.
Container
provides an API to find beans in the component tree:
class Root extends ContainerLifeCycle
{
}
class Service extends ContainerLifeCycle
{
private ScheduledExecutorService scheduler;
@Override
protected void doStart() throws Exception
{
scheduler = Executors.newSingleThreadScheduledExecutor();
addBean(scheduler);
super.doStart();
}
@Override
protected void doStop() throws Exception
{
super.doStop();
removeBean(scheduler);
scheduler.shutdown();
}
}
Root root = new Root();
Service service = new Service();
root.addBean(service);
// Start the Root component.
root.start();
// Find all the direct children of root.
Collection<Object> children = root.getBeans();
// children contains only service
// Find all descendants of root that are instance of a particular class.
Collection<ScheduledExecutorService> schedulers = root.getContainedBeans(ScheduledExecutorService.class);
// schedulers contains the service scheduler.
You can add your own beans to the component tree at application startup time, and later find them from your application code to access their services.
The component tree should be used for long-lived or medium-lived components such as thread pools, web application contexts, etc. It is not recommended adding to, and removing from, the component tree short-lived objects such as HTTP requests or TCP connections, for performance reasons. If you need component tree features such as automatic export to JMX or dump capabilities for short-lived objects, consider having a long-lived container in the component tree instead. You can make the long-lived container efficient at adding/removing the short-lived components using a data structure that is not part of the component tree, and make the long-lived container handle the JMX and dump features for the short-lived components. |
Jetty Component Listeners
A component that extends AbstractLifeCycle
inherits the possibility to add/remove event listeners for various events emitted by components.
A component that implements java.util.EventListener
that is added to a ContainerLifeCycle
is also registered as an event listener.
The following sections describe in details the various listeners available in the Jetty component architecture.
LifeCycle.Listener
A LifeCycle.Listener
emits events for life cycle events such as starting, stopping and failures:
Server server = new Server();
// Add an event listener of type LifeCycle.Listener.
server.addEventListener(new LifeCycle.Listener()
{
@Override
public void lifeCycleStarted(LifeCycle lifeCycle)
{
System.getLogger("server").log(INFO, "Server {0} has been started", lifeCycle);
}
@Override
public void lifeCycleFailure(LifeCycle lifeCycle, Throwable failure)
{
System.getLogger("server").log(INFO, "Server {0} failed to start", lifeCycle, failure);
}
@Override
public void lifeCycleStopped(LifeCycle lifeCycle)
{
System.getLogger("server").log(INFO, "Server {0} has been stopped", lifeCycle);
}
});
For example, a life cycle listener attached to a Server
instance could be used to create (for the started event) and delete (for the stopped event) a file containing the process ID of the JVM that runs the Server
.
Container.Listener
A component that implements Container
is a container for other components and ContainerLifeCycle
is the typical implementation.
A Container
emits events when a component (also called bean) is added to or removed from the container:
Server server = new Server();
// Add an event listener of type LifeCycle.Listener.
server.addEventListener(new Container.Listener()
{
@Override
public void beanAdded(Container parent, Object child)
{
System.getLogger("server").log(INFO, "Added bean {1} to {0}", parent, child);
}
@Override
public void beanRemoved(Container parent, Object child)
{
System.getLogger("server").log(INFO, "Removed bean {1} from {0}", parent, child);
}
});
A Container.Listener
added as a bean will also be registered as a listener:
class Parent extends ContainerLifeCycle
{
}
class Child
{
}
// The older child takes care of its siblings.
class OlderChild extends Child implements Container.Listener
{
private Set<Object> siblings = new HashSet<>();
@Override
public void beanAdded(Container parent, Object child)
{
siblings.add(child);
}
@Override
public void beanRemoved(Container parent, Object child)
{
siblings.remove(child);
}
}
Parent parent = new Parent();
Child older = new OlderChild();
// The older child is a child bean _and_ a listener.
parent.addBean(older);
Child younger = new Child();
// Adding a younger child will notify the older child.
parent.addBean(younger);
Container.InheritedListener
A Container.InheritedListener
is a listener that will be added to all descendants that are also Container
s.
Listeners of this type may be added to the component tree root only, but will be notified of every descendant component that is added to or removed from the component tree (not only first level children).
The primary use of Container.InheritedListener
within the Jetty Libraries is MBeanContainer
from the Jetty JMX support.
MBeanContainer
listens for every component added to the tree, converts it to an MBean and registers it to the MBeanServer; for every component removed from the tree, it unregisters the corresponding MBean from the MBeanServer.