High Level Overview
At a coarse level, AS 7 consists of two main elements:
- A core manageable service container based on modular classloading
- Extensions to that core that provide the kind of functionality most users associate with an application server, like handling HTTP requests and managing transactions
The AS distribution also includes two clients for the management interfaces it exposes (a CLI tool and a web-based admin console). The AS codebase also produces implementations of the SPIs published by the Arquillian project, allowing AS7 servers to run Arquillian-based tests.
The core of the AS consists of the following primary elements:
- A modular classloading system provided by the jboss-modules library
- A fast, highly scalable service container framework provided by the jboss-msc library
- An extensible management layer that is meant to mediate access to the service container by logic that wants to add, remove and modify services. The management layer also provides a consistent, persistent, configuration model for the AS. The management layer involves
- core elements, via the jboss-dmr library and the AS codebase's own controller, controller-client, deployment-repository, domain-management and network modules as well as parts of the server module
- remote management capability via the protocol and domain-http modules
- multi-server managed domains via the process-controller and host-controller modules
- miscellaneous elements provided via the managment-client-content and platform-mbean modules
- A deployment framework for coordinating installation of deployment content into the runtime. This is one of the things provided by the server module.
Most of the functionality that end users associate with an application server is provided AS extensions. Most of the modules in the AS7 codebase are extension implementations, with each extension providing a coherent set of functionality. Many extensions provide support for some aspect of the Java EE specifications.
Extensions implement an interface (org.jboss.as.controller.Extension) that allows integration with the core AS management layer. Via that mechanism, extensions are able to
- participate in the parsing and marshalling of the AS's configuration documents
- register resources and operations to be exposed via the AS's management API
- install services into the AS's service container
- register deployment unit processors with the AS's deployment framework
See the "Extending JBoss AS 7" document for more on extensions. See also https://community.jboss.org/docs/DOC-25627 for more on how extending AS 7 differs from extending previous versions of JBoss AS.
AS7 Boot Process
To better illustrate the AS 7 architecture, let's walk through the boot process for a standalone server.
When you run bin/standalone.sh from the root of the AS 7 distribution, the final effect is to launch a JVM using the following essential command
java -jar jboss-modules.jar -mp modules org.jboss.as.standalone
If you start the AS and use the ps command to see the actual JVM launch command, you'll see a lot of JVM settings (-Xmx and the like) and a lot of system property settings, but the above bits are the key information. Let's break that down a bit:
java -jar jboss-modules.jar
We can see from this that the actual main class the VM will invoke isn't in a JBoss AS library at all; it's in jboss-modules.jar (specifically, the org.jboss.modules.Main class.) So, when you start the AS the first thing you are doing is setting up a modular classloading environment.
These are arguments passed to org.jboss.modules.Main.main(). The -mp is short for "module path" and the value is a path somewhat analogous to the value of an OS $PATH environment variable. Each item in the path is a location under which jboss-modules will look for a module when needed. If there is more than one item in the path, the items are searched in order, with the search ending as soon as the module is found. In this case there is only one item in the path -- the modules/ dir under the AS distribution root.
This is the name of a module located on the module path. This module will be loaded by jboss-modules. If you look in the modules/org/jboss/as/standalone/main/module.xml file in the AS distribution, you will see it includes this element:
When jboss-modules sees that element in the module.xml for the module passed to its main() method, it knows to load the specified class and to pass any remaining command line arguments into its main() method. So, now we have a modular classloading enviroment set up, and the org.jboss.as.server.Main.main() method (found in the AS codebase's server module) has been invoked.
The org.jboss.as.server.Main.main() method does a number of things, but two are most relevant for people who wish to understand how to develop the AS:
- Any remaining command line arguments are processed and an instance of the ServerEnvironment is created. This object encapsulates environmental information available via the command line or system properties; things like where the root of the AS dist is; where the root of the server instance is; where the configuration file is, etc. Later this information is made available to all services in the system via the ServerEnvironmentService.
- An object implementing the Bootstrap interface (specifically, the org.jboss.as.server.BootstrapImpl class) is created and configured and its bootstrap() method is invoked.
The most important thing BootstrapImpl does is create an instance of the JBoss MSC library's ServiceContainer interface. A service container manages a set of running services. A service is simply a thing that can be started and stopped, with start and stop performed via methods specified in the JBoss MSC Service interface. A service also exposes a value of some type T via the Service interface's public T getValue() method. The value type T specified by the service is used by default by consumers of the service, and should represent the public interface of the service. It may or may not be the same as the implementing type of the service.
The ServiceContainer provides an API for managing services -- configuring and installing them, removing them, triggering start and stop, looking up already installed services, etc. Configuring a service can involve expressing the dependencies the service has on other services and asking that the service container inject the value of depended-upon services into the service before starting it. If a service depends upon another service, that other service must be successfully started before the service container will invoke the dependent service's start method. If for some reason the depended-upon service needs to be stopped, the service container will invoke the dependent service's stop method first.
The ServiceContainer maintains an internal thread pool. In order to achieve highly performant management of large numbers of inter-related services, activities related to starting and stopping services are performed as a series of concisely scoped tasks, with the threads in the thread pool used to execute those tasks. For example, executing a service's start method would be a task performed by a thread in the ServiceContainer's thread pool. Because of this fact, it is important to recognize that any activity related to starting and stopping services will be highly multi-threaded. For example, you can never assume that the thread that asks the service container to install a service will be the thread that calls its start method.
So, at this point in our boot, we have in place two of the four main architectural elements discussed in the "AS Core" section of the "High Level Overview" above: a modular classloading environment provided by the jboss-modules library, and a service container framework provided by the jboss-msc library.
Up to this point in the AS boot, all activity has been on a single thread, the JVM's main thread. In the BootstrapImpl.bootstrap() method, things get more interesting, since much of the remaining boot work involves installing services, with the ServiceContainer's thread pool threads doing the most of that work.
BootstrapImpl.bootstrap() installs two services:
- ControlledProcessStateService. This service provides access to a simple enum value showing the current running state of the server (STARTING, STOPPING, RUNNING, RESTART_REQUIRED, RELOAD_REQUIRED). This is the only service that once started should not be stopped until the VM is being shut down.
- ApplicationServerService. This is the root service for the application server; all other services in one way or another depend upon it. When you use the CLI to perform the :reload operation, the server handles that request by telling the service container to stop this service, which has the effect of stopping all other services that depend upon it. Then once it is stopped, the service container is told to start the service again.
The ApplicationServerService starts a number of other services in its start method. The most significant of these is the ServerService.
The ServerService and the Controller Boot Thread
ServerService brings in the 3rd and 4th of the four primary components of the core AS listed in the "High Level Overview" above -- the extensible management layer and the deployment framework. For those of you familiar with the "Extending JBoss AS 7" document, ServerService performs many of the same kinds of activities an Extension implementation performs in its initialize(ExtensionContext context) method, but for the core AS management model:
- registers resource definitions, attribute definitions and OperationStepHandlers for the core AS managed resources
- registers core DeploymentUnitProcessors
ServerService implements Service<ModelController>. A ModelController is the central execution point for management operations in a managed AS process (i.e. a server or a HostController in a managed domain.)
In its start method, ServerService spawns a separate thread, the "Controller Boot Thread", which is responsible for coordinating the remainder of the boot. It does the following primary tasks
- Triggers the parsing of the server configuration file (e.g. standalone.xml) into a list of management operations that should be executed by the ModelController to bring the server's running configuration in line with the xml configuration
- Passes those management operations into the ModelController's execute method. The ModelController uses the Controller Boot Thread to handle execution of many of the steps involved in handling those operations.
- Blocks until all services added into the runtime as part of handling those management ops have either started or failed to start.
- Logs the completion of boot.
The tasks of the Controller Boot Thread are spelled out in further detail below.
XML Parsing, Extension Loading, Extension Parsing Initialization
The task of an XML parser for an AS7 configuration document (e.g. standalone.xml, domain.xml, host.xml) is to populate a list of management operations that should be executed by the ModelController to bring the process' running configuration in line with the xml. Each of those management operations has the same format as would be read off the wire if the CLI had sent an operation request to the server to invoke an equivalent operation.
The parser for the core AS xml namespace has some special behavior when it comes to parsing an <extension> element in the xml:
- The name attribute of the element is the name of a JBoss Modules module that contains an implementation of the org.jboss.as.controller.Extension interface
- Once the name is parsed, the parser asks JBoss Modules to load the module
- Once the module is loaded, the java.lang.ServiceLoader mechanism is used to load the module's implementation of the Extension interface.
- The initializeParsers(ExtensionParsingContext context) method on that Extension implementation is invoked. That allows the extension to register XML parsers for the XML namespaces supported by the subsystem(s) the extension provides.
- When the core AS xml parser subsequently encounters a <subsystem> element in the configuration document, the xml namespace of the element is determined, and the appropriate parser registered by some Extension is used to parse that portion of the document.
Execution of Boot Management Operations, Extension Initialization
Once xml parsing is complete, a list of management operations is ready for execution by the ModelController. Each operation has the same format (address, operation name, parameters) as would be read off the wire if the CLI had sent an operation request to the server to invoke an equivalent operation after boot. The Controller Boot Thread asks the ModelController to execute the operations as a unit, with each individual operation acting as a step in the overall unit of work. Execution proceeds in 3 stages, with all steps in the overall unit completing a stage before execution on the next stage begins. (This execution pattern applies whenever any operation or atomic list of operations is invoked, not just during boot.)
- Stage MODEL -- the OperationStepHandler registered for each operation makes necessary updates to the server's internal configuration model, and, if necessary, registers a handler for the same operation for Stage RUNTIME.
- Stage RUNTIME -- any OperationStepHandler registered in Stage MODEL for an operation accesses the JBoss MSC ServiceContainer and installs/removes/updates any relevant services. The ServiceContainer has its own thread pool and uses it to perform the necessary tasks to start and stop services. The thread executing the OperationStepHandler does not do this directly, and the handler implementation needs to recognize that service start/stop will be asynchronous.
- Stage VERIFY -- A Stage.MODEL or Stage.RUNTIME handler can register a Stage.VERIFY handler that will only run once the ServiceContainer had completed all service modifications made in stage RUNTIME. A VERIFY handler can check that the runtime service changes completed successfully.
Before the list of boot operations is passed into the ModelController, a check is done for operations that add extension resources (for example, using CLI syntax, /extension=org.foo.extension:add.) When one is found, the relevant Extension implementation's initialize(ExtensionContext context) method is invoked. This gives the Extension a chance to register its resource and attribute definitions and its OperationStepHandlers with the core AS management layer before the boot operations are executed.
Wait for Service Container Stability and Boot Completion
The final steps in the AS 7 boot are to wait while the JBoss MSC ServiceContainer processes the installation and start of all the services added by the handlers for the boot operations. As discussed in the introduction to the ServiceContainer above, the start of services is performed by threads in the ServiceContainer's internal thread pool. Services are not started by the Controller Boot Thread. However, the ServerService attaches a listener to the controller object for each service; using this the ServerService is able to track when all services have reached a stable state. The Controller Boot Thread uses this facility to block until the ServiceContainer has stabilized. At that point, all services are either started or failed, and boot is nearly complete. The Controller Boot Thread switches the state of the ControlledProcessState service from STARTING to RUNNING, writes a log message reporting that the boot is complete, and boot is finished.
When you trigger deployment of some content, one of the management operations supported by the core AS management layer is invoked. The logic responsible for handling that operation will extract relevant information from the operation request (e.g. the name of the deployment) and will then install services into the AS's service container:
- A Service<VirtualFile> implementation that provides an org.jboss.vfs.VirtualFile that represents the deployment content.
- A Service<DeploymentUnit> (in this case RootDeploymentUnitService) that provides a DeploymentUnit for the deployment. A DeploymentUnit retains data which is persistent for the life of the deployment, and will later be passed into the various DeploymentUnitProcessor implementations that perform various actions to install the runtime services needed by the deployment.
The RootDeploymentUnitService has injected into it a reference to all the DeploymentUnitProcessor (DUP) implementations that were registered by the core ServerService at boot or that have been registered by subsystems. The DUP implementations are grouped by the Phase of the deployment process in which they execute, and are numerically ordered within that phase. DeploymentUnitProcessors are organized in a chain fashion, with each DUP performing a limited set of tasks to help take a deployment from being a bunch of bits to being a set of useful services.
Deployment proceeds in phases. See the Phase enum for a listing of the phases. For each phase, a DeploymentUnitPhaseService representing that phase that is installed into the service container. Each phase service (save the first) depends on the phase service for the previous phase, and each phase service (save the last) in its start method installs the phase service for the next phase. The RootDeploymentUnitService in its start method installs the first phase service, which in turn depends on it the RootDeploymentUnitService. The effect of all this is if the RootDeploymentUnitService is stopped (e.g. by the undeploy management operation), this will trigger a requirement for the service container to first stop the first phase service, which will in turn trigger a requirement to first stop the next phase service, and so on all the way to the final phase service. The effect is the phase services will be stopped in reverse order from how they were started.
The primary thing the phase services do in their start and stop methods is invoke the deploy and undeploy methods of each DeploymentUnitProcessor registered for their phase. The deploy method is invoked in phase service start and the undeploy method is invoked in stop. For each deploy/undeploy call the DUP is provided with a DeploymentPhaseContext that provides context to the call and gives the DUP access to the service container, allowing it to install or remove services.
Deployment Processing and Modules
One of the tasks performed by the DeploymentUnitProcessors is to set up the modular classloading enviroment for the deployment. Each top-level deployment has its own dynamically generated module. For deployment types that include known subdeployments (e.g. an ear can include wars, ejb jars, etc) then in addition those subdeployments also get their own dynamically generated module. What other modules these deployment modules have visibility to depends on the requirements determined by the deployment framework when it analyzes the deployment content (e.g. by parsing deployment descriptors, reading manifests, or scanning annotations.)
The key actor in this process is the org.jboss.as.server.deployment.module.ModuleSpecProcessor, which is a DeploymentUnitProcessor. The ModuleSpecProcessor configures and installs an MSC service that interacts with jboss-modules to dynamically generate the deployment's module. Other DeploymentUnitProcessors that execute prior to ModuleSpecProcessor analyze the content of the deployment and add contextual information to the DeploymentUnit that is used by ModuleSpecProcessor to establish what other modules the deployment's module can access. So, for example, a DUP registered by the JPA subsystem might recognize that the deployment requires JPA support (e.g. by detecting the presence of a persistence.xml file) and record that visibility to the modules that provide the JPA APIs should be added. The effect of all this is the deployment's classes have access to an appropriate set of classes located in the AS's own modules or in other deployments, but do not have access to classes located in other modules.