Microcontainer User Guide
for use with JBoss Enterprise Application Platform 5
Edition 5.2.0
Mark Newton
Ales Justin
Edited by
Eva Kopalova
Edited by
Misty Stanley-Jones
Edited by
Petr Penicka
Edited by
Russell Dickenson
Edited by
Scott Mumford
Abstract
Part I. Introduction to The Microcontainer - Guided Tutorial
Chapter 1. Prerequisites to Using This Guide
1.1. Install Maven
Procedure 1.1. Install Maven
Verify Java Developer Kit 1.6 or above is installed. This is also a requirement for the Enterprise Platform.
Ensure you have Java installed on your system, and have set theJAVA_HOME
environment variable in your~/.bash_profile
for Linux, or in the System Properties for Windows. For more information regarding setting environment variables, refer to the Step 4 step in this procedure.Download Maven
Note
This step and future steps assume that you have saved Maven to the suggested location for your operating system. Maven, as any other Java application, is able to be installed in any reasonable location on your system.Click the compiled zip archive link, for exampleapache-maven-2.2.1-bin.zip
Select a download mirror from the list.For Linux UsersSave the zip archive to your
home
directory.For Windows UsersSave the zip archive to your
C:\Documents and Settings\user_name
directory.Install Maven
For Linux UsersExtract the zip file to your
home
directory. If you selected the zip archive in Step 2, and do not rename the directory, the extracted directory is namedapache-maven-version
.For Windows UsersExtract the zip archive to C:\Program Files\Apache Software Foundation. If you selected the zip archive in Step 2, and do not rename the directory, the extracted directory is named
apache-maven-version
.Configure Environment Variables
For Linux UsersAdd the following lines to your
~/.bash_profile
. Ensure you change the [username] to your actual username, and that the Maven directory is the actual directory name. The version number may be different than the one listed below.export M2_HOME=/home/[username]/apache-maven-2.2.1 export M2=$M2_HOME/bin export PATH=$M2:$PATH
By includingM2
at the beginning of your path, the Maven version you just installed will be the default version used. You may also want to set the path of yourJAVA_HOME
environment variable to the location of the JDK on your system.For Windows UsersAdd the
M2_HOME
,M2
, andJAVA_HOME
environment variables.- Press Start+Pause|Break. The System Properties dialog box is displayed.
- Click the Advanced tab, then click the Environment Variables button.
- Under System Variables, select Path.
- Click Edit, and append the two Maven paths using a semi-colon to separate each entry. Quotation marks are not required around paths.
- Add the variable
M2_HOME
and set the path toC:\Program Files\Apache Software Foundation\apache-maven-2.2.1
. - Add the variable
M2
and set the value to%M2_HOME%\bin
.
- In the same dialog, create the
JAVA_HOME
environment variable:- Add the variable
%JAVA_HOME%
and set the value to the location of your JDK. For exampleC:\Program Files\Java\jdk1.6.0_02
.
- In the same dialog, update or create the Path environment variable:
- Add the variable
%M2%
to allow Maven to be executed from the command-line. - Add the variable
%JAVA_HOME%\bin
to set the path to the correct Java installation.
- Click OK until the System Properties dialog box closes.
Implement changes to .bash_profile
For Linux Users OnlyTo update the changes made to the .bash_profile in the current terminal session, source your .bash_profile.
[localhost]$ source ~/.bash_profile
Update gnome-terminal profile
For Linux Users OnlyUpdate the terminal profile to ensure that subsequent iterations of gnome-terminal (or Konsole terminal) read the new environment variables.
- Click Edit → Profiles
- Select Default, and click the Edit button.
- In the Editing Profile dialog, click the Title and Command tab.
- Select the Run command as login shell check box.
- Close all open Terminal dialog boxes.
Verify the environment variable changes and Maven install
For Linux UsersTo verify that the changes have been implemented correctly, open a terminal and execute the following commands:
- Execute
echo $M2_HOME
, which should return the following result.[localhost]$ echo $M2_HOME /home/username/apache-maven-2.2.1
- Execute
echo $M2
, which should return the following result.[localhost]$ echo $M2 /home/username/apache-maven-2.2.1/bin
- Execute
echo $PATH
, and verify the Maven/bin
directory is included.[localhost]$ echo $PATH /home/username/apache-maven-2.2.1/bin
- Execute
which mvn
, which should display the path to the Maven executable.[localhost]$ which mvn ~/apache-maven-2.2.1/bin/mvn
- Execute
mvn -version
, which should display the Maven version, related Java version, and operating system information.[localhost]$ $ mvn -version Apache Maven 2.2.1 (r801777; 2009-08-07 05:16:01+1000) Java version: 1.6.0_0 Java home: /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0/jre Default locale: en_US, platform encoding: UTF-8 OS name: "Linux" version: "2.6.30.9-96.fc11.i586" arch: "i386" Family: "unix"
For Windows UsersTo verify that the changes have been implemented correctly, open a terminal and execute the following command:
- In a command prompt, execute
mvn -version
C:\> mvn -version Apache Maven 2.2.1 (r801777; 2009-08-06 12:16:01-0700) Java version: 1.6.0_17 Java home: C:\Sun\SDK\jdk\jre Default locale: en_US, platform encoding: Cp1252 OS name: "windows xp" version: "5.1" arch: "x86" Family: "windows"
1.2. Special Maven Settings for the Microcontainer Examples
settings.xml
File” in your ~/.m2/settings.xml
(Linux) or C:\Documents and Settings\username\.m2\settings.xml
(Windows). If the file does not exist, you can create it first.
Example 1.1. Example settings.xml
File
<settings> <profiles> <profile> <id>jboss-public-repository</id> <repositories> <repository> <id>jboss-public-repository-group</id> <name>JBoss Public Maven Repository Group</name> <url>https://repository.jboss.org/nexus/content/groups/public/</url> <layout>default</layout> <releases> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>jboss-public-repository-group</id> <name>JBoss Public Maven Repository Group</name> <url>https://repository.jboss.org/nexus/content/groups/public/</url> <layout>default</layout> <releases> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </snapshots> </pluginRepository> </pluginRepositories> </profile> <profile> <id>jboss-deprecated-repository</id> <repositories> <repository> <id>jboss-deprecated-repository</id> <name>JBoss Deprecated Maven Repository</name> <url>https://repository.jboss.org/nexus/content/repositories/deprecated/</url> <layout>default</layout> <releases> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </releases> <snapshots> <enabled>false</enabled> <updatePolicy>never</updatePolicy> </snapshots> </repository> </repositories> </profile> </profiles> <activeProfiles> <activeProfile>jboss-public-repository</activeProfile> <activeProfile>jboss-deprecated-repository</activeProfile> </activeProfiles> </settings>
1.3. Downloading the Examples
Chapter 2. Introduction to the Microcontainer
2.1. Features
- All the features of the JMX Microkernel
- Direct POJO deployment (no need for Standard/XMBean or MBeanProxy)
- Direct IOC style dependency injection
- Improved life cycle management
- Additional control over dependencies
- Transparent AOP integration
- Virtual File System
- Virtual Deployment Framework
- OSGi classloading
2.2. Definitions
Microcontainer Definition List
- JMX Microkernel
- The JBoss JMX Microkernel is a modular Java environment. It differs from standard environments like J2EE in that the developer is able to choose exactly which components are part of the environment, and leave out the rest.
- POJO
- A Plain Old Java Object (POJO) is a modular, reusable Java object. The name is used to emphasize that a given object is an ordinary Java Object, not a special object, and in particular not an Enterprise JavaBean. The term was coined by Martin Fowler, Rebecca Parsons and Josh MacKenzie in September 2000 in a talk where they were pointing out the many benefits of encoding business logic into regular java objects rather than using Entity Beans.
- Java Bean
- A Java Bean is a reusable software component that can be manipulated visually in a builder tool.A Java Bean is an independent piece of code. It is not required to inherit from any particular base class or interface. Although Java Beans are primarily created in graphical IDEs, they can also be developed in simple text editors.
- AOP
- Aspect-Oriented Programming (AOP) is a programming paradigm in which secondary or supporting functions are isolated from the main program's business logic. It is a subset of object-oriented programming.
2.3. Installation
Chapter 3. Building Services
Note
examples/User_Guide/gettingStarted/humanResourcesService
.
3.1. Introduction to the Human Resources Example
examples/User_Guide/gettingStarted/humanResourcesService/src/main/java/org/jboss/example/service
directory, after you have extracted the ZIP file. Each of these classes represents a simple POJO that does not implement any special interfaces. The most important class is HRManager
, which represents the service entry point providing all of the public methods that clients will call.
Methods Provided by the HRManager
Class
addEmployee
(Employeeemployee
)removeEmployee
(Employeeemployee
)getEmployee
(StringfirstName
, StringlastName
)getEmployees
()getSalary
(Employeeemployee
)setSalary
(Employeeemployee
, IntegernewSalary
)isHiringFreeze
()setHiringFreeze
(booleanhiringFreeze
)getSalaryStrategy
()setSalaryStrategy
(SalaryStrategystrategy
)
SalaryStrategy
interface it is possible to configure the HRManager so that different salary strategy implementations are available to place minimum and maximum limits on the salaries for different employee roles.
3.2. Compiling the HRManager Example Project
mvn compile
from the humanResourcesService/
directory. This creates a new directory called target/classes
which contains the compiled classes. To clean up the project and remove the target directory, issue the mvn clean
command.
3.3. Creating POJOs
3.3.1. XML Deployment Descriptors
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0"> <bean name="HRService" class="org.jboss.example.service.HRManager"/> </deployment>
HRManager
class and registers it with the name HRService. This file is passed to an XML deployer associated with the Microcontainer at run-time, which performs the actual deployment, and instantiates the beans.
3.4. Connecting POJOs Together
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0"> <bean name="HRService" class="org.jboss.example.service.HRManager"> <property name="salaryStrategy"><inject bean="AgeBasedSalary"/></property> </bean> <bean name="AgeBasedSalary" class="org.jboss.example.service.util.AgeBasedSalaryStrategy"/> </deployment>
HRManager
created using the HRService bean. Injection is possible because the HRManager
class contains a setSalaryStrategy(SalaryStrategy strategy)
method. Behind the scenes, JBoss Microcontainer calls this method on the newly created HRManager instance and passes a reference to the AgeBasedSalaryStrategy
instance.
HRManager hrService = new HRManager(); AgeBasedSalaryStrategy ageBasedSalary = new AgeBasedSalaryStrategy(); hrService.setSalaryStrategy(ageBasedSalary);
3.4.1. Special Considerations
Employee
and Address
classes is unnecessary, because the client creates these in response to input from the user. They remain part of the service but are not referenced in the deployment descriptor.
You can define multiple beans within a deployment descriptor as long as each has a unique name, which is used to perform injection as shown above. However all of the beans do not necessarily represent services. While a service can be implemented using a single bean, multiple beans are usually used together. One bean typically represents the service entry point, and contains the public methods called by the clients. In this example the entry point is the HRService bean. The XML deployment descriptor does not indicate whether a bean represents a service or whether a bean is the service entry point. It is a good idea to use comments and an obvious naming scheme to delineate service beans from non-service beans.
3.5. Working with Services
3.5.1. Configuring A Service
- Injecting references between POJO instances
- Injecting values into POJO properties
- A hiring freeze is implemented.
- The
AgeBasedSalaryStrategy
implements new minimum and maximum salary values.
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0"> <bean name="HRService" class="org.jboss.example.service.HRManager"> <property name="hiringFreeze">false</property> <property name="salaryStrategy"><inject bean="AgeBasedSalary"/></property> </bean> <bean name="AgeBasedSalary" class="org.jboss.example.service.util.AgeBasedSalaryStrategy"> <property name="minSalary">1000</property> <property name="maxSalary">80000</property> </bean> </deployment>
HRManager
class has a setHiringFreeze(boolean hiringFreeze)
method and the AgeBasedSalaryStrategy
class has setMinSalary(int minSalary)
and setMaxSalary(int maxSalary)
methods.
3.5.2. Testing A Service
MicrocontainerTest
class.
org.jboss.test.kernel.junit.MicrocontainerTest
class inherits from junit.framework.TestCase
, setting up each test by bootstrapping JBoss Microcontainer and adding a BasicXMLDeployer. It then searches the classpath for an XML deployment descriptor with the same name as the test class, ending in .xml
and residing in a directory structure representing the class's package name. Any beans found in this file are deployed and can then be accessed using a convenience method called getBean(String name)
.
src/test/resources
Directory”.
Example 3.1. Listing of the src/test/resources
Directory
├── log4j.properties └── org └── jboss └── example └── service ├── HRManagerAgeBasedTestCase.xml ├── HRManagerLocationBasedTestCase.xml ├── HRManagerTestCase.xml └── util ├── AgeBasedSalaryTestCase.xml └── LocationBasedSalaryTestCase.xml
Example 3.2. Listing of the src/test/java
Directory
└── org └── jboss └── example └── service ├── HRManagerAgeBasedTestCase.java ├── HRManagerLocationBasedTestCase.java ├── HRManagerTestCase.java ├── HRManagerTest.java ├── HRManagerTestSuite.java └── util ├── AgeBasedSalaryTestCase.java ├── LocationBasedSalaryTestCase.java └── SalaryStrategyTestSuite.java
HRManagerTest
class extends MicrocontainerTest
in order to set up a number of employees to use as the basis for the tests. Individual test cases then subclass HRManagerTest
to perform the actual work. Also included are a couple of TestSuite
classes that are used to group individual test cases together for convenience.
mvn test
from the humanResourcesService/
directory. You should see some DEBUG
log output which shows JBoss Microcontainer starting up and deploying beans from the relevant XML file before running each test. At the end of the test the beans are undeployed and the Microcontainer is shut down.
Note
HRManagerTestCase
, AgeBasedSalaryTestCase
, and LocationBasedSalaryTestCase
, unit test individual POJOs. Other tests, such as HRManagerAgeBasedTestCase
and HRManagerLocationBasedTestCase
unit test entire services. Either way, the tests are run in the same manner. Using the MicrocontainerTest class makes it easy to set up and conduct comprehensive tests for any part of your code.
Address
and Employee
classes are not tested here. Writing tests for them is up to you.
3.5.3. Packaging A Service
Procedure 3.1. Packaging a Service
Place the deployment descriptor in the
META-INF
directory (optional)If you do choose to include the deployment descriptor, by convention it should be namedjboss-beans.xml
and should be placed in aMETA-INF
directory. This is the default layout for the Enterprise Platform, so the JAR deployer recognizes this layout and automatically performs the deployment.The deployment descriptor is not included in the Human Resources example, because the service is configured by editing the descriptor directly, as a separate file.Generate the JAR
To generate a JAR containing all of the compiled classes, entermvn package
from thehumanResourcesService/
directory.Make the JAR available to other Maven projects
To make the JAR available to other Maven projects, entermvn install
in order to copy it to your local Maven repository. The final layout of the JAR is shown in Example 3.3, “Listing of theorg/jboss/example/service
andMETA-INF
Directories”.
Example 3.3. Listing of the org/jboss/example/service
and META-INF
Directories
`-- org `-- jboss `-- example `-- service |-- Address.java |-- Employee.java |-- HRManager.java `-- util |-- AgeBasedSalaryStrategy.java |-- LocationBasedSalaryStrategy.java `-- SalaryStrategy.java `--META-INF `-- MANIFEST.MF `-- maven `-- org.jboss.micrcontainer.examples `-- humanResourceService
Note
META-INF/maven
directory is automatically created by Maven, and will not be present if you are using a different build system.
Chapter 4. Using Services
examples/User_Guide/gettingstarted/commandLineClient
directory, which follows the Maven Standard Directory Layout, as seen in Example 4.1, “Listing for examples/User_Guide/gettingstarted/commandLineClient
Directory”.
Example 4.1. Listing for examples/User_Guide/gettingstarted/commandLineClient
Directory
├── pom.xml ├── src │ ├── main │ │ ├── assembly │ │ │ ├── aop.xml │ │ │ ├── classloader.xml │ │ │ ├── common.xml │ │ │ └── pojo.xml │ │ ├── config │ │ │ ├── aop-beans.xml │ │ │ ├── classloader-beans.xml │ │ │ ├── pojo-beans.xml │ │ │ └── run.sh │ │ ├── java │ │ │ └── org │ │ │ └── jboss │ │ │ └── example │ │ │ └── client │ │ │ ├── Client.java │ │ │ ├── ConsoleInput.java │ │ │ ├── EmbeddedBootstrap.java │ │ │ └── UserInterface.java │ │ └── resources │ │ └── log4j.properties │ └── test │ ├── java │ │ └── org │ │ └── jboss │ │ └── example │ │ └── client │ │ ├── ClientTestCase.java │ │ ├── ClientTestSuite.java │ │ └── MockUserInterface.java │ └── resources │ └── jboss-beans.xml └── target └── classes └── log4j.properties
org/jboss/example/client
directory.
UserInterface
describes methods that the client calls at run-time to request user input. ConsoleInput
is an implementation of UserInterface
that creates a TUI which the user uses to interact with the client. The advantage of this design is that you can easily create a Swing implementation of UserInterface
at a later date and replace the TUI with a GUI. You can also simulate the data-entry process with a script. Then you can check the behavior of the client automatically using conventional JUnit test cases found in Example 3.2, “Listing of the src/test/java
Directory”.
auditAspect.jar
from the examples/User_Guide/gettingStarted/auditAspect
directory using the mvn install command
. A number of different client distributions are created, including one based on AOP which relies on auditAspect.jar
being available in the local Maven repository.
mvn install
from the examples/User_Guide/gettingStarted
directory then humanResourcesService.jar
and auditAspect.jar
have already been built and packaged, along with the client, so this step will not be necessary.
mvn package
command from the commandLineClient
directory.
Procedure 4.1. Compiling the Source Code
- Run the unit tests.
- Build a client JAR.
- Assemble a distribution containing all of the necessary files.
commandLineClient/target
directory includes the subdirectories described in Example 4.2, “Subdirectories of the commandLineClient/target
Directory”.
Example 4.2. Subdirectories of the commandLineClient/target
Directory
client-pojo
- used to call the service without AOP.
client-cl
- used to demonstrate classloading features.
- client-aop
- Adding AOP support. See Chapter 5, Adding Behavior with AOP for more details.
client-pojo
distribution found in the client-pojo
sub-directory, which is listed in Example 4.3, “Listing of the client-pojo
Directory”.
Example 4.3. Listing of the client-pojo
Directory
|-- client-1.0.0.jar |-- jboss-beans.xml |-- lib | |-- concurrent-1.3.4.jar | |-- humanResourcesService-1.0.0.jar | |-- jboss-common-core-2.0.4.GA.jar | |-- jboss-common-core-2.2.1.GA.jar | |-- jboss-common-logging-log4j-2.0.4.GA.jar | |-- jboss-common-logging-spi-2.0.4.GA.jar | |-- jboss-container-2.0.0.Beta6.jar | |-- jboss-dependency-2.0.0.Beta6.jar | |-- jboss-kernel-2.0.0.Beta6.jar | |-- jbossxb-2.0.0.CR4.jar | |-- log4j-1.2.14.jar | `-- xercesImpl-2.7.1.jar `-- run.sh
client-pojo
directory and type ./run.sh
. The Example 4.4, “HRManager Menu Screen” appears.
Example 4.4. HRManager Menu Screen
Menu: d) Deploy Human Resources service u) Undeploy Human Resources service a) Add employee l) List employees r) Remove employee g) Get a salary s) Set a salary t) Toggle hiring freeze m) Display menu p) Print service status q) Quit >
Important
run.sh
script sets up the run-time environment by adding all of the JARs found in the lib/
directory to the classpath using the java.ext.dirs system property. It also adds the current directory and the client-1.0.0.jar
using the -cp flag so that the jboss-beans.xml
deployment descriptor can be found at run-time along with the org.jboss.example.client.Client
class which is called to start the application.
4.1. Bootstrapping the Microcontainer
public Client(final boolean useBus) throws Exception { this.useBus = useBus; ClassLoader cl = Thread.currentThread().getContextClassLoader(); url = cl.getResource("jboss-beans.xml"); // Start JBoss Microcontainer bootstrap = new EmbeddedBootstrap(); bootstrap.run(); kernel = bootstrap.getKernel(); controller = kernel.getController(); bus = kernel.getBus(); }
jboss-beans.xml
deployment descriptor is created. This is later required so that the XML deployer can deploy and undeploy beans declared in the file. The getResource()
method of the application classloader is used because the jboss-beans.xml
file is included on the classpath. This is optional; the name and location of the deployment descriptor are unimportant as long as the URL is valid and reachable.
BasicBootstrap
is provided as part of the Microcontainer to allow for programmatic configuration. To add an XML deployer, extend BasicBootstrap
to create an EmbeddedBootstrap
class and override the protected bootstrap()
method as follows:
public class EmbeddedBootstrap extends BasicBootstrap { protected BasicXMLDeployer deployer; public EmbeddedBootstrap() throws Exception { super(); } public void bootstrap() throws Throwable { super.bootstrap(); deployer = new BasicXMLDeployer(getKernel()); Runtime.getRuntime().addShutdownHook(new Shutdown()); } public void deploy(URL url) { ... deployer.deploy(url); ... } public void undeploy(URL url) { ... deployer.undeploy(url); ... } protected class Shutdown extends Thread { public void run() { log.info("Shutting down"); deployer.shutdown(); } } }
shutdown
hook ensures that when the JVM exits, all of the beans are undeployed in the correct order. The public deploy
/undeploy
methods delegate to the BasicXMLDeployer
so that beans declared in jboss-beans.xml
can be deployed and undeployed.
4.2. Deploying the Service
BasicXMLDeployer
has parsed the jboss-beans.xml
file using the URL, and instantiated the beans found within.
Note
lib/humanResourcesService.jar
file. You can also place these classes in an exploded directory structure and add it to the application classpath, but packaging them in a JAR is typically more convenient.
humanResourcesService.jar
file. This enables easy editing of it for testing purposes. The jboss-beans.xml
file in the example contains some commented-out fragments of XML which show some of the possible configurations.
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0"> <bean name="HRService" class="org.jboss.example.service.HRManager"> <!-- <property name="hiringFreeze">true</property> <property name="salaryStrategy"><inject bean="AgeBasedSalary"/></property> --> </bean> <!-- <bean name="AgeBasedSalary" class="org.jboss.example.service.util.AgeBasedSalaryStrategy"> <property name="minSalary">1000</property> <property name="maxSalary">80000</property> </bean> <bean name="LocationBasedSalary" class="org.jboss.example.service.util.LocationBasedSalaryStrategy"> <property name="minSalary">2000</property> <property name="maxSalary">90000</property> </bean> --> </deployment>
Important
4.3. Direct Access
run.sh
script when the client is started, a reference to the HRService
bean is looked up using the Microcontainer controller after the service is deployed:
private HRManager manager; ... private final static String HRSERVICE = "HRService"; ... void deploy() { bootstrap.deploy(url); if (!useBus && manager == null) { ControllerContext context = controller.getInstalledContext(HRSERVICE); if (context != null) { manager = (HRManager) context.getTarget(); } } }
ControllerContext
, then obtains a reference to the bean instance from the context using the getTarget()
method. The bean can exist within the Microcontainer in any of the states listed in States of a Bean Within the Microcontainer.
States of a Bean Within the Microcontainer
- NOT_INSTALLED
- DESCRIBED
- INSTANTIATED
- CONFIGURED
- INSTALLED
@SuppressWarnings("unchecked") Set<Employee> listEmployees() { if (useBus) ... else return manager.getEmployees(); }
jboss-beans.xml
file, save the file, and deploy it again using the d option. Printing out the status of the service using the p option shows that the client is still accessing the initial instance of the service that was deployed.
Warning
run.sh
script. For enterprise services such as Transactions, Messaging and Persistence this is perfectly acceptable behavior, since they are generally always in use. They cannot be redeployed at run-time and also benefit from the high performance given by using direct access. If your service falls into this category, consider using direct access via the Microcontainer controller.
4.4. Indirect Access
run.sh
script can be called with an optional parameter bus, which causes calls to the Human Resources service to use the Microcontainer bus.
invoke()
method on the bus, passing in the bean name, method name, method arguments and method types. The bus uses this information to call the bean on the client's behalf.
private final static String HRSERVICE = "HRService"; ... @SuppressWarnings("unchecked") Set<Employee> listEmployees() { if (useBus) return (Set<Employee>) invoke(HRSERVICE, "getEmployees", new Object[] {}, new String[] {}); else return manager.getEmployees(); } private Object invoke(String serviceName, String methodName, Object[] args, String[] types) { Object result = null; try { result = bus.invoke(serviceName, methodName, args, types); } catch (Throwable t) { t.printStackTrace(); } return result; }
Note
jboss-beans.xml
file, save the changes, and deploy it again using the d option. Print out the status again using the p option. The client is accessing the new service configuration.
4.5. Dynamic Classloading
run.sh
script using the -cp flag to include the current directory and the client-1.0.0.jar
, as shown here:
java -Djava.ext.dirs=`pwd`/lib -cp .:client-1.0.0.jar org.jboss.example.client.Client $1
lib
directory were added to the extension classloader's classpath using the java.ext.dirs system property, rather than listing the full path to each of the JARs after the -cp flag. Because the classloader
extension is the parent of the classloader
application, the client classes is able to find all of the Microcontainer classes and the Human Resources service classes at run-time.
Note
java -cp `pwd`/lib/*:.:client-1.0.0.jar org.jboss.example.client.Client $1
client-cl
distribution contains the file listed in the Example 4.5, “Listing of the commandLineClient/target/client-cl
Directory” .
Example 4.5. Listing of the commandLineClient/target/client-cl
Directory
|-- client-1.0.0.jar |-- jboss-beans.xml |-- lib | |-- concurrent-1.3.4.jar | |-- jboss-common-core-2.0.4.GA.jar | |-- jboss-common-core-2.2.1.GA.jar | |-- jboss-common-logging-log4j-2.0.4.GA.jar | |-- jboss-common-logging-spi-2.0.4.GA.jar | |-- jboss-container-2.0.0.Beta6.jar | |-- jboss-dependency-2.0.0.Beta6.jar | |-- jboss-kernel-2.0.0.Beta6.jar | |-- jbossxb-2.0.0.CR4.jar | |-- log4j-1.2.14.jar | `-- xercesImpl-2.7.1.jar |-- otherLib | `-- humanResourcesService-1.0.0.jar |`-- run.sh
humanResourcesService.jar
file has been moved to a new sub-directory called otherLib
. It is no longer available to either the extension or application classloaders whose classpaths are setup in the run.sh script:
java -Djava.ext.dirs=`pwd`/lib -cp .:client-1.0.0.jar org.jboss.example.client.Client $1
jboss-beans.xml
file:
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0"> <bean name="URL" class="java.net.URL"> <constructor> <parameter>file:/Users/newtonm/jbossmc/microcontainer/trunk/docs/examples/User_Guide/gettingStarted/commandLineClient/target/client-cl.dir/otherLib/humanResourcesService-1.0.0.jar</parameter> </constructor> </bean> <bean name="customCL" class="java.net.URLClassLoader"> <constructor> <parameter> <array> <inject bean="URL"/> </array> </parameter> </constructor> </bean> <bean name="HRService" class="org.jboss.example.service.HRManager"> <classloader><inject bean="customCL"/></classloader> <!-- <property name="hiringFreeze">true</property> <property name="salaryStrategy"><inject bean="AgeBasedSalary"/></property> --> </bean> <!-- <bean name="AgeBasedSalary" class="org.jboss.example.service.util.AgeBasedSalaryStrategy"> <property name="minSalary">1000</property> <property name="maxSalary">80000</property> </bean> <bean name="LocationBasedSalary" class="org.jboss.example.service.util.LocationBasedSalaryStrategy"> <property name="minSalary">2000</property> <property name="maxSalary">90000</property> </bean> --> </deployment>
- First, create an instance of
java.net.URL
calledURL
, using parameter injection in the constructor to specify the location of thehumanResourcesService.jar
file on the local file-system. - Next, create an instance of a
URLClassLoader
by injecting the URL bean into the constructor as the only element in an array. - Include a <classloader> element in your
HRService
bean definition and inject thecustomCL
bean. This specifies that theHRManager
class needs to be loaded by the customCL classloader.
Example 4.6. Specifying a Different Classloader
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0"> <classloader><inject bean="customCL"/></classloader> <bean name="URL" class="java.net.URL"> <constructor> <parameter>file:/Users/newtonm/jbossmc/microcontainer/trunk/docs/examples/User_Guide/gettingStarted/commandLineClient/target/client-cl.dir/otherLib/humanResourcesService-1.0.0.jar</parameter> </constructor> </bean> <bean name="customCL" class="java.net.URLClassLoader"> <constructor> <parameter> <array> <inject bean="URL"/> </array> </parameter> </constructor> </bean> ... </deployment>
AgeBasedSalary
or LocationBasedSalary
beans. Classloaders specified at the bean level override the deployment level classloader. To override the deployment level classloader altogether, and use the default classloader for a bean, use the <null/> value as follows:
<bean name="HRService" class="org.jboss.example.service.HRManager"> <classloader><null/></classloader> </bean>
4.5.1. Problems With Classloaders Created with Deployment Descriptors
run.sh
command, then try to deploy the service. A java.lang.NoClassDefFoundError
exception is thrown and the application exits.
Address
, Employee
, and SalaryStrategy
.
Chapter 5. Adding Behavior with AOP
HRManager
class, but it would clutter the class with code which is not relevant to its central purpose, bloating it and making it harder to maintain. The design of the aspect also provide modularity, making it easy to audit other classes in the future, if the scope of the project changes.
5.1. Creating An Aspect
examples/User_Guide/gettingStarted/auditAspect
directory contains all the files needed to create the aspect.
pom.xml
src/main/java/org/jboss/example/aspect/AuditAspect.java
Example 5.1. Example POJO
public class AuditAspect { private String logDir; private BufferedWriter out; public AuditAspect() { logDir = System.getProperty("user.dir") + "/log"; File directory = new File(logDir); if (!directory.exists()) { directory.mkdir(); } } public Object audit(ConstructorInvocation inv) throws Throwable { SimpleDateFormat formatter = new SimpleDateFormat("ddMMyyyy-kkmmss"); Calendar now = Calendar.getInstance(); String filename = "auditLog-" + formatter.format(now.getTime()); File auditLog = new File(logDir + "/" + filename); auditLog.createNewFile(); out = new BufferedWriter(new FileWriter(auditLog)); return inv.invokeNext(); } public Object audit(MethodInvocation inv) throws Throwable { String name = inv.getMethod().getName(); Object[] args = inv.getArguments(); Object retVal = inv.invokeNext(); StringBuffer buffer = new StringBuffer(); for (int i=0; i < args.length; i++) { if (i > 0) { buffer.append(", "); } buffer.append(args[i].toString()); } if (out != null) { out.write("Method: " + name); if (buffer.length() > 0) { out.write(" Args: " + buffer.toString()); } if (retVal != null) { out.write(" Return: " + retVal.toString()); } out.write("\n"); out.flush(); } return retVal; } }
Procedure 5.1. Creating the POJO
- The constructor checks for the presence of a
log
directory in the current working directory, and creates one if not found. - Next, an advice is defined. This advice is called whenever the constructor of the target class is called. This creates a new log file within the
log
directory to record method calls made on different instances of the target class in separate files. - Finally, another advice is defined. This advice applies to each method call made on the target class.The method name and arguments are stored, along with the return value. This information is used to construct an audit record and write it to the current log file. Each advice calls
inv.invokeNext()
, which chains the advices together if more than one cross-cutting concern has been applied, or to call the target constructor/method.
Note
auditAspect.jar
file that can be used by other examples, type mvn install
from the auditAspect
directory.
5.2. Configuring the Microcontainer for AOP
lib
sub-directory of the client-aop
distribution located in the examples/User_Guide/gettingStarted/commandLineClient/target/client-aop.dir
directory:
Example 5.2. Listing of the examples/User_Guide/gettingStarted/commandLineClient/target/client-aop.dir Directory
|-- client-1.0.0.jar |-- jboss-beans.xml |-- lib |-- auditAspect-1.0.0.jar |-- concurrent-1.3.4.jar |-- humanResourcesService-1.0.0.jar |-- javassist-3.6.0.GA.jar |-- jboss-aop-2.0.0.beta1.jar |-- jboss-aop-mc-int-2.0.0.Beta6.jar |-- jboss-common-core-2.0.4.GA.jar |-- jboss-common-core-2.2.1.GA.jar |-- jboss-common-logging-log4j-2.0.4.GA.jar |-- jboss-common-logging-spi-2.0.4.GA.jar |-- jboss-container-2.0.0.Beta6.jar |-- jboss-dependency-2.0.0.Beta6.jar |-- jboss-kernel-2.0.0.Beta6.jar |-- jbossxb-2.0.0.CR4.jar |-- log4j-1.2.14.jar |-- trove-2.1.1.jar `-- xercesImpl-2.7.1.jar |-- log `-- auditLog-18062010-122537 `-- run.sh
lib/auditAspect-1.0.0.jar
is required to create an instance of the aspect at run-time, in order to execute the logic. Next the jar file for JBoss AOP (jboss-aop.jar), along with its dependencies javassist and trove, adds the AOP functionality. Finally, the jboss-aop-mc-int jar is required because it contains an XML schema definition that allows you to define aspects inside an XML deployment descriptor. It also contains integration code to create dependencies between normal beans and aspect beans within the Microcontainer, allowing you to add behavior during the deployment and undeployment phases.
pom.xml
file and creating a valid assembly descriptor. A sample pom.xml
snippet is shown in Example 5.3, “Example pom.xml
Excerpt for AOP”. To perform your build using Ant, the procedure will be different.
Example 5.3. Example pom.xml
Excerpt for AOP
<dependency> <groupId>org.jboss.microcontainer.examples</groupId> <artifactId>jboss-oap</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.jboss.microcontainer.examples</groupId> <artifactId>javassist</artifactId> <version>3.6.0.GA</version> </dependency> <dependency> <groupId>org.jboss.microcontainer.examples</groupId> <artifactId>trove</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.jboss.microcontainer.examples</groupId> <artifactId>jboss-aop-mc-int</artifactId> <version>2.0.0.Beta6</version> </dependency>
5.3. Applying An Aspect
jboss-beans.xml
to apply the audit aspect. It is in examples/User_Guide/gettingStarted/commandLineClient/target/client-aop.dir
.
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0"> <bean name="AspectManager" class="org.jboss.aop.AspectManager"> <constructor factoryClass="org.jboss.aop.AspectManager" factoryMethod="instance"/> </bean> <aop:aspect xmlns:aop="urn:jboss:aop-beans:1.0" name="AuditAspect" class="org.jboss.example.aspect.AuditAspect" method="audit" pointcut="execution(public org.jboss.example.service.HRManager->new(..)) OR execution(public * org.jboss.example.service.HRManager->*(..))"> </aop:aspect> ... </deployment>
Procedure 5.2. Explanation of the Code to Apply an Aspect
- Before you can apply your aspect to any classes, you need to create an instance of
org.jboss.aop.AspectManager
using a <bean> element. A factory method is used here instead of calling a conventional constructor, since only one instance of the AspectManager in the JVM is necessary at run-time. - Next an instance of our aspect called AuditAspect is created, using the <aop:aspect> element. This looks similar to the <bean> element because it has name and class attributes that are used in the same way. However it also has method and pointcut attributes that you can use to apply or bind an advice within the aspect to constructors and methods within other classes. These attributes bind the audit advice to all public constructors and methods within the
HRManager
class. Only theaudit
method needs to be specified, since it has been overloaded within theAuditAspect
class with different parameters. JBoss AOP knows at run-time which to select, depending on whether a constructor or method invocation is being made.
run.sh
script. A log
directory is created on start-up alongside the lib
directory when the AuditAspect
bean is created by the Microcontainer. Each deployment of the Human Resources service causes a new log file to appear within the log
directory. The log file contains a record of any calls made from the client to the service. It is named something similar to auditLog-28112007-163902
, and contains output similar to Example 5.4, “Example AOP Log Output”.
Example 5.4. Example AOP Log Output
Method: getEmployees Return: [] Method: addEmployee Args: (Santa Claus, 1 Reindeer Avenue, Lapland City - 25/12/1860) Return: true Method: getSalary Args: (Santa Claus, null - Birth date unknown) Return: 10000 Method: getEmployees Return: [(Santa Claus, 1 Reindeer Avenue, Lapland City - 25/12/1860)] Method: isHiringFreeze Return: false Method: getEmployees Return: [(Santa Claus, 1 Reindeer Avenue, Lapland City - 25/12/1860)] Method: getSalaryStrategy
Warning
5.4. Life cycle callbacks
- NOT_INSTALLED
- the deployment descriptor containing the bean has been parsed, along with any annotations on the bean itself.
- DESCRIBED
- any dependencies created by AOP have been added to the bean, and custom annotations have been processed.
- INSTANTIATED
- an instance of the bean has been created.
- CONFIGURED
- properties have been injected into the bean, along with any references to other beans.
- CREATE
- the
create
method, if defined in the bean, has been called. - START
- the
start
method, if defined in the bean, has been called. - INSTALLED
- any custom install actions that were defined in the deployment descriptor have been executed and the bean is ready to access.
Important
CREATE
and START
states are included for legacy purposes. This allows services that were implemented as MBeans in previous versions of the Enterprise Platform to function correctly when implemented as beans in the Enterprise Platform 5.1. If you do not define any corresponding create/start methods in your bean, it will pass straight through these states.
<aop:lifecycle-describe>
- applied when entering/leaving the DESCRIBED state
<aop:lifecycle-instantiate>
- applied when entering/leaving the INSTANTIATED state
<aop:lifecycle-configure>
- applied when entering/leaving the CONFIGURED state
<aop:lifecycle-create>
- applied when entering/leaving the CREATE state
<aop:lifecycle-start>
- applied when entering/leaving the START state
<aop:lifecycle-install>
- applied when entering/leaving the INSTALLED state
callback
class, naming it so that it can be used as beans enter or leave the relevant state during deployment and undeployment. You can specify which beans are affected by the callback using the classes attribute, as shown in Example 5.5, “Using the classes Attribute”.
Example 5.5. Using the classes Attribute
<aop:lifecycle-install xmlns:aop="urn:jboss:aop-beans:1.0" name="InstallAdvice" class="org.jboss.test.microcontainer.support.LifecycleCallback" classes="@org.jboss.test.microcontainer.support.Install"> </aop:lifecycle-install>
lifecycleCallback
class is applied to any bean classes that are annotated with @org.jboss.test.microcontainer.support.Install
before they enter and after they leave the INSTALLED
state.
install
and uninstall
methods that take ControllerContext
as a parameter, as shown in Example 5.6, “Install and Uninstall Methods”.
Example 5.6. Install and Uninstall Methods
import org.jboss.dependency.spi.ControllerContext; public class LifecycleCallback { public void install(ControllerContext ctx) { System.out.println("Bean " + ctx.getName() + " is being installed"; } public void uninstall(ControllerContext ctx) { System.out.println("Bean " + ctx.getName() + " is being uninstalled"; } }
install
method is called during the bean's deployment, and the uninstall
method during its undeployment.
Note
5.5. Adding Service Look-ups Through JNDI
Example 5.7. Looking Up References To Beans
private HRManager manager; private EmbeddedBootstrap bootstrap; private Kernel kernel; private KernelController controller; private final static String HRSERVICE = "HRService"; ... // Start JBoss Microcontainer bootstrap = new EmbeddedBootstrap(); bootstrap.run(); kernel = bootstrap.getKernel(); controller = kernel.getController(); ... ControllerContext context = controller.getInstalledContext(HRSERVICE); if (context != null) { manager = (HRManager) context.getTarget(); }
Procedure 5.3. Writing Your Own ServiceLocator Implementation
- First, create an instance of JBoss NS using the Microcontainer.
- Next, add a life-cycle callback to perform the binding and unbinding of the bean references during deployment and undeployment.
- Mark the bean classes you wish to bind references for, using annotations.
- Now, you can locate the beans at run-time using the shorthand pointcut expression as shown earlier.
Part II. Advanced Concepts with the Microcontainer
Chapter 6. Component Models
6.1. Allowable Interactions with Component Models
6.2. A Bean With No Dependencies
Example 6.1. Deployment Descriptor for a Plain POJO
<deployment xmlns="urn:jboss:bean-deployer:2.0"> <bean name="PlainPojo" class="org.jboss.demos.models.plain.Pojo"/> <beanfactory name="PojoFactory" class="org.jboss.demos.models.plain.Pojo"> <property name="factoryClass">org.jboss.demos.models.plain.PojoFactory</property> </beanfactory> </deployment>
6.3. Using the Microcontainer with Spring
Example 6.2. Descriptor with Spring Support
<beans xmlns="urn:jboss:spring-beans:2.0"> <!-- Adding @Spring annotation handler --> <bean id="SpringAnnotationPlugin" class="org.jboss.spring.annotations.SpringBeanAnnotationPlugin" /> <bean id="SpringPojo" class="org.jboss.demos.models.spring.Pojo"/> </beans>
urn:jboss:spring-beans:2.0
namespace points to your version of the Spring schema port, which describes your bean's Spring style. The Microcontainer, rather than Spring's bean factory notion, deploys the beans.
Example 6.3. Using Spring with the Microcontainer
public class Pojo extends AbstractPojo implements BeanNameAware { private String beanName; public void setBeanName(String name) { beanName = name; } public String getBeanName() { return beanName; } public void start() { if ("SpringPojo".equals(getBeanName()) == false) throw new IllegalArgumentException("Name does not match: " + getBeanName()); } }
6.4. Using Guice With the Microcontainer
Example 6.4. Deployment Descriptor for Guice Integration In the Microcontainer
<deployment xmlns="urn:jboss:bean-deployer:2.0"> <bean name="GuicePlugin" class="org.jboss.guice.spi.GuiceKernelRegistryEntryPlugin"> <constructor> <parameter> <array elementClass="com.google.inject.Module"> <bean class="org.jboss.demos.models.guice.PojoModule"/> </array> </parameter> </constructor> </bean> </deployment>
PojoModule
and GuiceKernelRegistryEntryPlugin
. PojoModule
configures your beans, as in Example 6.5, “Configuring Beans for Guice”. GuiceKernelRegistryEntryPlugin
provides integration with the Microcontainer, as shown in Example 6.6, “Guice Integration with the Microcontainer”.
Example 6.5. Configuring Beans for Guice
public class PojoModule extends AbstractModule { private Controller controller; @Constructor public PojoModule(@Inject( bean = KernelConstants.KERNEL_CONTROLLER_NAME) Controller controller) { this.controller = controller; } protected void configure() { bind(Controller.class).toInstance(controller); bind(IPojo.class).to(Pojo.class).in(Scopes.SINGLETON); bind(IPojo.class).annotatedWith(FromMC.class). toProvider(GuiceIntegration.fromMicrocontainer(IPojo.class, "PlainPojo")); } }
Example 6.6. Guice Integration with the Microcontainer
public class GuiceKernelRegistryEntryPlugin implements KernelRegistryPlugin { private Injector injector; public GuiceKernelRegistryEntryPlugin(Module... modules) { injector = Guice.createInjector(modules); } public void destroy() { injector = null; } public KernelRegistryEntry getEntry(Object name) { KernelRegistryEntry entry = null; try { if (name instanceof Class<?>) { Class<?> clazz = (Class<?>)name; entry = new AbstractKernelRegistryEntry(name, injector.getInstance(clazz)); } else if (name instanceof Key) { Key<?> key = (Key<?>)name; entry = new AbstractKernelRegistryEntry(name, injector.getInstance(key)); } } catch (Exception ignored) { } return entry; } }
Note
Injector
is created from the Modules
class, then does a look-up on it for matching beans. See Section 6.5, “Legacy MBeans, and Mixing Different Component Models” for information about declaring and using legacy MBeans.
6.5. Legacy MBeans, and Mixing Different Component Models
Example 6.7. Injecting a POJO Into an MBean
<server> <mbean code="org.jboss.demos.models.mbeans.Pojo" name="jboss.demos:service=pojo"> <attribute name="OtherPojo"><inject bean="PlainPojo"/></attribute> </mbean> </server>
system-jmx-beans.xml
for more details. The code from this file lives in the JBoss Application Server source code: system-jmx sub-project.
6.6. Exposing POJOs as MBeans
Example 6.8. Exposing an Existing POJO as an MBean
<deployment xmlns="urn:jboss:bean-deployer:2.0"> <bean name="AnnotatedJMXPojo" class="org.jboss.demos.models.jmx.AtJmxPojo"/> <bean name="XmlJMXPojo" class="org.jboss.demos.models.mbeans.Pojo"> <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(exposedInterface=org.jboss.demos.models.mbeans.PojoMBean.class, registerDirectly=true)</annotation> </bean> <bean name="ExposedPojo" class="org.jboss.demos.models.jmx.Pojo"/> <bean name="AnnotatedExposePojo" class="org.jboss.demos.models.jmx.ExposePojo"> <constructor> <parameter><inject bean="ExposedPojo"/></parameter> </constructor> </bean> </deployment>
org.jboss.aop.microcontainer.aspects.jmx.JMX
. The bean can either be exposed directly, or in its property.
Example 6.9. Exposing a POJO as an MBean Using Its Property
<deployment xmlns="urn:jboss:bean-deployer:2.0"> <bean name="XMLLoginConfig" class="org.jboss.demos.models.old.XMLLoginConfig"/> <bean name="SecurityConfig" class="org.jboss.demos.models.old.SecurityConfig"> <property name="defaultLoginConfig"><inject bean="XMLLoginConfig"/></property> </bean> <bean name="SecurityChecker" class="org.jboss.demos.models.old.Checker"> <property name="loginConfig"><inject bean="jboss.security:service=XMLLoginConfig"/></property> <property name="securityConfig"><inject bean="jboss.security:service=SecurityConfig"/></property> </bean> </deployment>
Example 6.10. Autowiring
<deployment xmlns="urn:jboss:bean-deployer:2.0"> <bean name="FromGuice" class="org.jboss.demos.models.plain.FromGuice"> <constructor><parameter><inject bean="PlainPojo"/></parameter></constructor> <property name="guicePojo"><inject/></property> </bean> <bean name="AllPojos" class="org.jboss.demos.models.plain.AllPojos"> <property name="directMBean"><inject bean="jboss.demos:service=pojo"/></property> <property name="exposedMBean"><inject bean="jboss.demos:service=ExposedPojo"/></property> <property name="exposedMBean"><inject bean="jboss.demos:service=ExposedPojo"/></property> </bean> </deployment>
PlainPojo
is injected with a common name injection. Now, you can test if Guice binding works as expected, as shown in Example 6.11, “Testing Guice Functionality”.
Example 6.11. Testing Guice Functionality
public class FromGuice { private IPojo plainPojo; private org.jboss.demos.models.guice.Pojo guicePojo; public FromGuice(IPojo plainPojo) { this.plainPojo = plainPojo; } public void setGuicePojo(org.jboss.demos.models.guice.Pojo guicePojo) { this.guicePojo = guicePojo; } public void start() { f (plainPojo != guicePojo.getMcPojo()) throw new IllegalArgumentException("Pojos are not the same: " + plainPojo + "!=" + guicePojo.getMcPojo()); } }
Example 6.12. AbstractController Source Code
<deployment xmlns="urn:jboss:bean-deployer:2.0"> <alias name="SpringPojo">springPojo</alias> </deployment>
Chapter 7. Advanced Dependency Injection and IoC
7.1. Value Factory
Example 7.1. Value Factory
<bean name="Binding" class="org.jboss.demos.ioc.vf.PortBindingManager"> <constructor> <parameter> <map keyClass="java.lang.String" valueClass="java.lang.Integer"> <entry> <key>http</key> <value>80</value> </entry> <entry> <key>ssh</key> <value>22</value> </entry> </map> </parameter> </constructor> </bean> <bean name="PortsConfig" class="org.jboss.demos.ioc.vf.PortsConfig"> <property name="http"><value-factory bean="Binding" method="getPort" parameter="http"/></property> <property name="ssh"><value-factory bean="Binding" method="getPort" parameter="ssh"/></property> <property name="ftp"> <value-factory bean="Binding" method="getPort"> <parameter>ftp</parameter> <parameter>21</parameter> </value-factory> </property> <property name="mail"> <value-factory bean="Binding" method="getPort"> <parameter>mail</parameter> <parameter>25</parameter> </value-factory> </property> </bean>
getPort
method invocation.
Example 7.2. PortsConfig
public class PortBindingManager { private Map<String, Integer> bindings; public PortBindingManager(Map<String, Integer> bindings) { this.bindings = bindings; } public Integer getPort(String key) { return getPort(key, null); } public Integer getPort(String key, Integer defaultValue) { if (bindings == null) return defaultValue; Integer value = bindings.get(key); if (value != null) return value; if (defaultValue != null) bindings.put(key, defaultValue); return defaultValue; } }
7.2. Callbacks
Parser
which collects all Editor
s.
Example 7.3. Callbacks to Collect and Filter Beans
<bean name="checker" class="org.jboss.demos.ioc.callback.Checker"> <constructor> <parameter> <value-factory bean="parser" method="parse"> <parameter> <array elementClass="java.lang.Object"> <value>http://www.jboss.org</value> <value>SI</value> <value>3.14</value> <value>42</value> </array> </parameter> </value-factory> </parameter> </constructor> </bean> <bean name="editorA" class="org.jboss.demos.ioc.callback.DoubleEditor"/> <bean name="editorB" class="org.jboss.demos.ioc.callback.LocaleEditor"/> <bean name="parser" class="org.jboss.demos.ioc.callback.Parser"> <incallback method="addEditor" cardinality="4..n"/> <uncallback method="removeEditor"/> </bean> <bean name="editorC" class="org.jboss.demos.ioc.callback.LongEditor"/> <bean name="editorD" class="org.jboss.demos.ioc.callback.URLEditor"/>
Example 7.4. A Parser to Collect all Editors
public class Parser { private Set<Editor> editors = new HashSet<Editor>(); ... public void addEditor(Editor editor) { editors.add(editor); } public void removeEditor(Editor editor) { editors.remove(editor); } }
incallback
and uncallback
use the method name for matching.
<incallback method="addEditor" cardinality="4..n"/> <uncallback method="removeEditor"/>
cardinality=4..n/>
Checker
gets created and checks the parser. This is illustrated in Example 7.5, “The Checker for the Parser”.
Example 7.5. The Checker for the Parser
public void create() throws Throwable { Set<String> strings = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); for (Object element : elements) strings.add(element.toString()); if (expected.equals(strings) == false) throw new IllegalArgumentException("Illegal expected set: " + expected + "!=" + strings); }
7.3. Bean Access Mode
FieldsBean
Class” for an implementation.
Example 7.6. Possible BeanAccessMode Definitions
public enum BeanAccessMode { STANDARD(BeanInfoCreator.STANDARD), // Getters and Setters FIELDS(BeanInfoCreator.FIELDS), // Getters/Setters and fields without getters and setters ALL(BeanInfoCreator.ALL); // As above but with non public fields included }
Example 7.7. Setting the BeanAccessMode
<bean name="FieldsBean" class="org.jboss.demos.ioc.access.FieldsBean" access-mode="ALL"> <property name="string">InternalString</property> </bean>
Example 7.8. The FieldsBean
Class
public class FieldsBean { private String string; public void start() { if (string == null) throw new IllegalArgumentException("Strings should be set!"); } }
7.4. Bean Alias
Example 7.9. A Simple Bean Alias
<bean name="SimpleName" class="java.lang.Object"> <alias>SimpleAlias</alias> <alias replace="true">${some.system.property}</alias> <alias class="java.lang.Integer">12345</alias> <alias><javabean xmlns="urn:jboss:javabean:2.0" class="org.jboss.demos.bootstrap.Main"/></alias> </bean>
7.5. XML (or MetaData) Annotations Support
Example 7.10. Intercepting a Method based on Annotation
<interceptor xmlns="urn:jboss:aop-beans:1.0" name="StopWatchInterceptor" class="org.jboss.demos.ioc.annotations.StopWatchInterceptor"/> <bind xmlns="urn:jboss:aop-beans:1.0" pointcut="execution(* @org.jboss.demos.ioc.annotations.StopWatchLog->*(..)) OR execution(* *->@org.jboss.demos.ioc.annotations.StopWatchLog(..))"> <interceptor-ref name="StopWatchInterceptor"/> </bind> </interceptor>
public class StopWatchInterceptor implements Interceptor { ... public Object invoke(Invocation invocation) throws Throwable { Object target = invocation.getTargetObject(); long time = System.currentTimeMillis(); log.info("Invocation [" + target + "] start: " + time); try { return invocation.invokeNext(); } finally { log.info("Invocation [" + target + "] time: " + (System.currentTimeMillis() - time)); } } }
Example 7.11. A true class annotated executor
<bean name="AnnotatedExecutor" class="org.jboss.demos.ioc.annotations.AnnotatedExecutor">
public class AnnotatedExecutor implements Executor { ... @StopWatchLog // <-- Pointcut match! public void execute() throws Exception { delegate.execute(); } }
Example 7.12. Simple executor with XML annotation
<bean name="SimpleExecutor" class="org.jboss.demos.ioc.annotations.SimpleExecutor"> <annotation>@org.jboss.demos.ioc.annotations.StopWatchLog</annotation> // <-- Pointcut match! </bean>
public class SimpleExecutor implements Executor { private static Random random = new Random(); public void execute() throws Exception { Thread.sleep(Math.abs(random.nextLong() % 101)); } }
Example 7.13. Executor Logging Output
JBoss-MC-Demo INFO [15-12-2008 13:57:39] StopWatch - Invocation [org.jboss.demos.ioc.annotations.AnnotatedExecutor@4d28c7] start: 1229345859234 JBoss-MC-Demo INFO [15-12-2008 13:57:39] StopWatch - Invocation [org.jboss.demos.ioc.annotations.AnnotatedExecutor@4d28c7] time: 31 JBoss-MC-Demo INFO [15-12-2008 13:57:39] StopWatch - Invocation [org.jboss.demos.ioc.annotations.SimpleExecutor@1b044df] start: 1229345859265 JBoss-MC-Demo INFO [15-12-2008 13:57:39] StopWatch - Invocation [org.jboss.demos.ioc.annotations.SimpleExecutor@1b044df] time: 47
7.6. Autowire
Example 7.14. Include and Exclude with Autowiring
<bean name="Square" class="org.jboss.demos.ioc.autowire.Square" autowire-candidate="false"/> <bean name="Circle" class="org.jboss.demos.ioc.autowire.Circle"/> <bean name="ShapeUser" class="org.jboss.demos.ioc.autowire.ShapeUser"> <constructor> <parameter><inject/></parameter> </constructor> </bean> <bean name="ShapeHolder" class="org.jboss.demos.ioc.autowire.ShapeHolder"> <incallback method="addShape"/> <uncallback method="removeShape"/> </bean> <bean name="ShapeChecker" class="org.jboss.demos.ioc.autowire.ShapesChecker"/>
7.7. Bean Factory
createBean
method.
GenericBeanFactory
instance, but you can configure your own factory. The only limitation is that its signature and configuration hooks are similar to the one of AbstractBeanFactory
.
Example 7.15. Generic Bean Factory
<bean name="Object" class="java.lang.Object"/> <beanfactory name="DefaultPrototype" class="org.jboss.demos.ioc.factory.Prototype"> <property name="value"><inject bean="Object"/></property> </beanfactory> <beanfactory name="EnhancedPrototype" class="org.jboss.demos.ioc.factory.Prototype" factoryClass="org.jboss.demos.ioc.factory.EnhancedBeanFactory"> <property name="value"><inject bean="Object"/></property> </beanfactory> <beanfactory name="ProxiedPrototype" class="org.jboss.demos.ioc.factory.UnmodifiablePrototype" factoryClass="org.jboss.demos.ioc.factory.EnhancedBeanFactory"> <property name="value"><inject bean="Object"/></property> </beanfactory> <bean name="PrototypeCreator" class="org.jboss.demos.ioc.factory.PrototypeCreator"> <property name="default"><inject bean="DefaultPrototype"/></property> <property name="enhanced"><inject bean="EnhancedPrototype"/></property> <property name="proxied"><inject bean="ProxiedPrototype"/></property> </bean>
Example 7.16. Extended BeanFactory
public class EnhancedBeanFactory extends GenericBeanFactory { public EnhancedBeanFactory(KernelConfigurator configurator) { super(configurator); } public Object createBean() throws Throwable { Object bean = super.createBean(); Class clazz = bean.getClass(); if (clazz.isAnnotationPresent(SetterProxy.class)) { Set<Class> interfaces = new HashSet<Class>(); addInterfaces(clazz, interfaces); return Proxy.newProxyInstance( clazz.getClassLoader(), interfaces.toArray(new Class[interfaces.size()]), new SetterInterceptor(bean) ); } else { return bean; } } protected static void addInterfaces(Class clazz, Set<Class> interfaces) { if (clazz == null) return; interfaces.addAll(Arrays.asList(clazz.getInterfaces())); addInterfaces(clazz.getSuperclass(), interfaces); } private class SetterInterceptor implements InvocationHandler { private Object target; private SetterInterceptor(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.startsWith("set")) throw new IllegalArgumentException("Cannot invoke setters."); return method.invoke(target, args); } } }
public class PrototypeCreator { ... public void create() throws Throwable { ValueInvoker vi1 = (ValueInvoker)bfDefault.createBean(); vi1.setValue("default"); ValueInvoker vi2 = (ValueInvoker)enhanced.createBean(); vi2.setValue("enhanced"); ValueInvoker vi3 = (ValueInvoker)proxied.createBean(); try { vi3.setValue("default"); throw new Error("Should not be here."); } catch (Exception ignored) { } }
7.8. Bean Metadata Builder
BeanMetaDataBuilder
to create and configure your bean metadata.
Example 7.17. BeanMetaDataBuilder
<bean name="BuilderUtil" class="org.jboss.demos.ioc.builder.BuilderUtil"/> <bean name="BuilderExampleHolder" class="org.jboss.demos.ioc.builder.BuilderExampleHolder"> <constructor> <parameter><inject bean="BUExample"/></parameter> </constructor> </bean>
public class BuilderUtil { private KernelController controller; @Constructor public BuilderUtil(@Inject(bean = KernelConstants.KERNEL_CONTROLLER_NAME) KernelController controller) { this.controller = controller; } public void create() throws Throwable { BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder("BUExample", BuilderExample.class.getName()); builder.addStartParameter(Kernel.class.getName(), builder.createInject(KernelConstants.KERNEL_NAME)); controller.install(builder.getBeanMetaData()); } public void destroy() { controller.uninstall("BUExample"); } }
7.9. Custom ClassLoader
Example 7.18. Defining a ClassLoader Per Bean
<classloader><inject bean="custom-classloader:0.0.0"/></classloader> <!-- this will be explained in future article --> <classloader name="custom-classloader" xmlns="urn:jboss:classloader:1.0" export-all="NON_EMPTY" import-all="true"/> <bean name="CustomCL" class="org.jboss.demos.ioc.classloader.CustomClassLoader"> <constructor> <parameter><inject bean="custom-classloader:0.0.0"/></parameter> </constructor> <property name="pattern">org\.jboss\.demos\.ioc\..+</property> </bean> <bean name="CB1" class="org.jboss.demos.ioc.classloader.CustomBean"/> <bean name="CB2" class="org.jboss.demos.ioc.classloader.CustomBean"> <classloader><inject bean="CustomCL"/></classloader> </bean>
Example 7.19. Custom ClassLoader Test
public class CustomClassLoader extends ClassLoader { private Pattern pattern; public CustomClassLoader(ClassLoader parent) { super(parent); } public Class<?> loadClass(String name) throws ClassNotFoundException { if (pattern == null || pattern.matcher(name).matches()) return super.loadClass(name); else throw new ClassNotFoundException("Name '" + name + "' does not match pattern: " + pattern); } public void setPattern(String regexp) { pattern = Pattern.compile(regexp); } }
7.10. Controller Mode
Example 7.20. Bean Controller Mode
<bean name="OptionalService" class="org.jboss.demos.ioc.mode.OptionalService" mode="On Demand"/> <bean name="OptionalServiceUser" class="org.jboss.demos.ioc.mode.OptionalServiceUser"/> <bean name="ManualService" class="org.jboss.demos.ioc.mode.ManualService" mode="Manual"/> <bean name="ManualServiceUser" class="org.jboss.demos.ioc.mode.ManualServiceUser"> <start> <parameter><inject bean="ManualService" fromContext="context" state="Not Installed"/></parameter> </start> </bean>
Note
inject
class, you can inject beans, as well as their unmodifiable Microcontainer component representation.
OptionalServiceUser
and ManualServiceUser
for how to use the Microcontainer API for ON_DEMAND and MANUAL bean handling.
7.11. Cycle
Example 7.21. Bean life cycle separation
<bean name="cycleA" class="org.jboss.demos.ioc.cycle.CyclePojo"> <property name="dependency"><inject bean="cycleB"/></property> </bean> <bean name="cycleB" class="org.jboss.demos.ioc.cycle.CyclePojo"> <constructor><parameter><inject bean="cycleA" state="Instantiated"/></parameter></constructor> </bean> <bean name="cycleC" class="org.jboss.demos.ioc.cycle.CyclePojo"> <property name="dependency"><inject bean="cycleD"/></property> </bean> <bean name="cycleD" class="org.jboss.demos.ioc.cycle.CyclePojo"> <property name="dependency"><inject bean="cycleC" state="Instantiated"/></property> </bean>
7.12. Demand and Supply
Example 7.22. Static Code Usage
<bean name="TMDemand" class="org.jboss.demos.ioc.demandsupply.TMDemander"> <demand>TM</demand> </bean> <bean name="SimpleTMSupply" class="org.jboss.demos.ioc.demandsupply.SimpleTMSupplyer"> <supply>TM</supply> </bean>
7.13. Installs
Entry
invokes RepositoryManager
's add
and removeEntry
methods to register and unregister itself.
Example 7.23. Invoking Methods in Different-States
<bean name="RepositoryManager" class="org.jboss.demos.ioc.install.RepositoryManager"> <install method="addEntry"> <parameter><inject fromContext="name"/></parameter> <parameter><this/></parameter> </install> <uninstall method="removeEntry"> <parameter><inject fromContext="name"/></parameter> </uninstall> </bean> <bean name="Entry" class="org.jboss.demos.ioc.install.SimpleEntry"> <install bean="RepositoryManager" method="addEntry" state="Instantiated"> <parameter><inject fromContext="name"/></parameter> <parameter><this/></parameter> </install> <uninstall bean="RepositoryManager" method="removeEntry" state="Configured"> <parameter><inject fromContext="name"/></parameter> </uninstall> </bean>
7.14. Lazy Mock
Example 7.24. Lazy Mock
<bean name="lazyA" class="org.jboss.demos.ioc.lazy.LazyImpl"> <constructor> <parameter> <lazy bean="lazyB"> <interface>org.jboss.demos.ioc.lazy.ILazyPojo</interface> </lazy> </parameter> </constructor> </bean> <bean name="lazyB" class="org.jboss.demos.ioc.lazy.LazyImpl"> <constructor> <parameter> <lazy bean="lazyA"> <interface>org.jboss.demos.ioc.lazy.ILazyPojo</interface> </lazy> </parameter> </constructor> </bean> <lazy name="anotherLazy" bean="Pojo" exposeClass="true"/> <bean name="Pojo" class="org.jboss.demos.ioc.lazy.Pojo"/>
7.15. Life cycle
create
, start
, and destroy
methods when it moves through the various states. However, you may not want the Microcontainer to invoke them. For this reason, an ignore flag is available.
Example 7.25. Bean Life cycles
<bean name="FullLifecycleBean-3" class="org.jboss.demos.ioc.lifecycle.FullLifecycleBean"/> <bean name="FullLifecycleBean-2" class="org.jboss.demos.ioc.lifecycle.FullLifecycleBean"> <create ignored="true"/> </bean> <bean name="FullLifecycleBean-1" class="org.jboss.demos.ioc.lifecycle.FullLifecycleBean"> <start ignored="true"/> </bean>
Chapter 8. The Virtual File System
Example 8.1. Resource Duplication Problem
public static URL[] search(ClassLoader cl, String prefix, String suffix) throws IOException { Enumeration[] e = new Enumeration[]{ cl.getResources(prefix), cl.getResources(prefix + "MANIFEST.MF") }; Set all = new LinkedHashSet(); URL url; URLConnection conn; JarFile jarFile; for (int i = 0, s = e.length; i < s; ++i) { while (e[i].hasMoreElements()) { url = (URL)e[i].nextElement(); conn = url.openConnection(); conn.setUseCaches(false); conn.setDefaultUseCaches(false); if (conn instanceof JarURLConnection) { jarFile = ((JarURLConnection)conn).getJarFile(); } else { jarFile = getAlternativeJarFile(url); } if (jarFile != null) { searchJar(cl, all, jarFile, prefix, suffix); } else { boolean searchDone = searchDir(all, new File(URLDecoder.decode(url.getFile(), "UTF-8")), suffix); if (searchDone == false) { searchFromURL(all, prefix, suffix, url); } } } } return (URL[])all.toArray(new URL[all.size()]); } private static boolean searchDir(Set result, File file, String suffix) throws IOException { if (file.exists() && file.isDirectory()) { File[] fc = file.listFiles(); String path; for (int i = 0; i < fc.length; i++) { path = fc[i].getAbsolutePath(); if (fc[i].isDirectory()) { searchDir(result, fc[i], suffix); } else if (path.endsWith(suffix)) { result.add(fc[i].toURL()); } } return true; } return false; }
8.1. VFS Public API
Uses for VFS
- simple resource navigation
- visitor pattern API (Application Programmer Interface)
Example 8.2. The VirtualFile Resource Type
public class VirtualFile implements Serializable { /** * Get certificates. * * @return the certificates associated with this virtual file */ Certificate[] getCertificates() /** * Get the simple VF name (X.java) * * @return the simple file name * @throws IllegalStateException if the file is closed */ String getName() /** * Get the VFS relative path name (org/jboss/X.java) * * @return the VFS relative path name * @throws IllegalStateException if the file is closed */ String getPathName() /** * Get the VF URL (file://root/org/jboss/X.java) * * @return the full URL to the VF in the VFS. * @throws MalformedURLException if a url cannot be parsed * @throws URISyntaxException if a uri cannot be parsed * @throws IllegalStateException if the file is closed */ URL toURL() throws MalformedURLException, URISyntaxException /** * Get the VF URI (file://root/org/jboss/X.java) * * @return the full URI to the VF in the VFS. * @throws URISyntaxException if a uri cannot be parsed * @throws IllegalStateException if the file is closed * @throws MalformedURLException for a bad url */ URI toURI() throws MalformedURLException, URISyntaxException /** * When the file was last modified * * @return the last modified time * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */ long getLastModified() throws IOException /** * Returns true if the file has been modified since this method was last called * Last modified time is initialized at handler instantiation. * * @return true if modifed, false otherwise * @throws IOException for any error */ boolean hasBeenModified() throws IOException /** * Get the size * * @return the size * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */ long getSize() throws IOException /** * Tests whether the underlying implementation file still exists. * @return true if the file exists, false otherwise. * @throws IOException - thrown on failure to detect existence. */ boolean exists() throws IOException /** * Whether it is a simple leaf of the VFS, * i.e. whether it can contain other files * * @return true if a simple file. * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */ boolean isLeaf() throws IOException /** * Is the file archive. * * @return true if archive, false otherwise * @throws IOException for any error */ boolean isArchive() throws IOException /** * Whether it is hidden * * @return true when hidden * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */ boolean isHidden() throws IOException /** * Access the file contents. * * @return an InputStream for the file contents. * @throws IOException for any error accessing the file system * @throws IllegalStateException if the file is closed */ InputStream openStream() throws IOException /** * Do file cleanup. * * e.g. delete temp files */ void cleanup() /** * Close the file resources (stream, etc.) */ void close() /** * Delete this virtual file * * @return true if file was deleted * @throws IOException if an error occurs */ boolean delete() throws IOException /** * Delete this virtual file * * @param gracePeriod max time to wait for any locks (in milliseconds) * @return true if file was deleted * @throws IOException if an error occurs */ boolean delete(int gracePeriod) throws IOException /** * Get the VFS instance for this virtual file * * @return the VFS * @throws IllegalStateException if the file is closed */ VFS getVFS() /** * Get the parent * * @return the parent or null if there is no parent * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */ VirtualFile getParent() throws IOException /** * Get a child * * @param path the path * @return the child or <code>null</code> if not found * @throws IOException for any problem accessing the VFS * @throws IllegalArgumentException if the path is null * @throws IllegalStateException if the file is closed or it is a leaf node */ VirtualFile getChild(String path) throws IOException /** * Get the children * * @return the children * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */ List<VirtualFile> getChildren() throws IOException /** * Get the children * * @param filter to filter the children * @return the children * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed or it is a leaf node */ List<VirtualFile> getChildren(VirtualFileFilter filter) throws IOException /** * Get all the children recursively<p> * * This always uses {@link VisitorAttributes#RECURSE} * * @return the children * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */ List<VirtualFile> getChildrenRecursively() throws IOException /** * Get all the children recursively<p> * * This always uses {@link VisitorAttributes#RECURSE} * * @param filter to filter the children * @return the children * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed or it is a leaf node */ List<VirtualFile> getChildrenRecursively(VirtualFileFilter filter) throws IOException /** * Visit the virtual file system * * @param visitor the visitor * @throws IOException for any problem accessing the virtual file system * @throws IllegalArgumentException if the visitor is null * @throws IllegalStateException if the file is closed */ void visit(VirtualFileVisitor visitor) throws IOException }
VFS
class, with the help of URL or URI parameter.
Example 8.3. Using the VFS
Class
public class VFS { /** * Get the virtual file system for a root uri * * @param rootURI the root URI * @return the virtual file system * @throws IOException if there is a problem accessing the VFS * @throws IllegalArgumentException if the rootURL is null */ static VFS getVFS(URI rootURI) throws IOException /** * Create new root * * @param rootURI the root url * @return the virtual file * @throws IOException if there is a problem accessing the VFS * @throws IllegalArgumentException if the rootURL */ static VirtualFile createNewRoot(URI rootURI) throws IOException /** * Get the root virtual file * * @param rootURI the root uri * @return the virtual file * @throws IOException if there is a problem accessing the VFS * @throws IllegalArgumentException if the rootURL is null */ static VirtualFile getRoot(URI rootURI) throws IOException /** * Get the virtual file system for a root url * * @param rootURL the root url * @return the virtual file system * @throws IOException if there is a problem accessing the VFS * @throws IllegalArgumentException if the rootURL is null */ static VFS getVFS(URL rootURL) throws IOException /** * Create new root * * @param rootURL the root url * @return the virtual file * @throws IOException if there is a problem accessing the VFS * @throws IllegalArgumentException if the rootURL */ static VirtualFile createNewRoot(URL rootURL) throws IOException /** * Get the root virtual file * * @param rootURL the root url * @return the virtual file * @throws IOException if there is a problem accessing the VFS * @throws IllegalArgumentException if the rootURL */ static VirtualFile getRoot(URL rootURL) throws IOException /** * Get the root file of this VFS * * @return the root * @throws IOException for any problem accessing the VFS */ VirtualFile getRoot() throws IOException }
getVFS
createNewRoot
getRoot
getVFS
returns a VFS instance but does not yet create a VirtualFile instance. This is important because there are methods which help with configuring a VFS instance (see VFS class API javadocs), before instructing it to create a VirtualFile root.
createNewRoot
and getRoot
is in caching details, which are covered later.
Example 8.4. Using getVFS
URL rootURL = ...; // get root url VFS vfs = VFS.getVFS(rootURL); // configure vfs instance VirtualFile root1 = vfs.getRoot(); // or you can get root directly VirtualFile root2 = VFS.crateNewRoot(rootURL); VirtualFile root3 = VFS.getRoot(rootURL);
Example 8.5. Recursively Gathering Resources
public interface VirtualFileVisitor { /** * Get the search attribues for this visitor * * @return the attributes */ VisitorAttributes getAttributes(); /** * Visit a virtual file * * @param virtualFile the virtual file being visited */ void visit(VirtualFile virtualFile); } VirtualFile root = ...; // get root VirtualFileVisitor visitor = new SuffixVisitor(".class"); // get all classes root.visit(visitor);
8.2. VFS Architecture
VFSContext
instance is created. This creation is done via VFSContextFactory
. Different protocols map to different VFSContextFactory
instances. For example, file/vfsfile
maps to FileSystemContextFactory
, while zip/vfszip
maps to ZipEntryContextFactory
.
VirtualFile
instance is created, its matching VirtualFileHandler is created. This VirtualFileHandler instance knows how to handle different resource types properly; the VirtualFile
API only delegates invocations to its VirtualFileHandler reference.
VFSContext
instance knows how to create VirtualFileHandler instances accordingly to a resource type. For example, ZipEntryContextFactory creates ZipEntryContext
, which then creates ZipEntryHandler
.
8.3. Existing Implementations
Example 8.6. Implementation of Assembled VirtualFileHandlers
AssembledDirectory sar = AssembledContextFactory.getInstance().create("assembled.sar"); URL url = getResource("/vfs/test/jar1.jar"); VirtualFile jar1 = VFS.getRoot(url); sar.addChild(jar1); url = getResource("/tmp/app/ext.jar"); VirtualFile ext1 = VFS.getRoot(url); sar.addChild(ext); AssembledDirectory metainf = sar.mkdir("META-INF"); url = getResource("/config/jboss-service.xml"); VirtualFile serviceVF = VFS.getRoot(url); metainf.addChild(serviceVF); AssembledDirectory app = sar.mkdir("app.jar"); url = getResource("/app/someapp/classes"); VirtualFile appVF = VFS.getRoot(url); app.addPath(appVF, new SuffixFilter(".class"));
Example 8.7. Implementation of In-Memory VirtualFileHandlers
URL url = new URL("vfsmemory://aopdomain/org/acme/test/Test.class"); byte[] bytes = ...; // some AOP generated class bytes MemoryFileFactory.putFile(url, bytes); VirtualFile classFile = VFS.getVirtualFile(new URL("vfsmemory://aopdomain"), "org/acme/test/Test.class"); InputStream bis = classFile.openStream(); // e.g. load class from input stream
8.4. Extension Hooks
Assembled
and Memory
. All you need is a combination of VFSContexFactory
, VFSContext
, VirtualFileHandler
, FileHandlerPlugin
, and URLStreamHandler
implementations. The VFSContextFactory
is trivial, while the others depend on the complexity of your task. You could implement rar
, tar
, gzip
, or even remote
access.
8.5. Features
gema.ear/ui.war/WEB-INF/lib/struts.jar
.
struts.jar
we have two options:
- handle resources in memory
- create top level temporary copies of nested jars, recursively
VFSRegistry
, VFSCache
, and TempInfo
.
getRoot
, not createNewRoot
), VFS asks the VFSRegistry
implementation to provide the file. The existing DefaultVFSRegistry
first checks if a matching root VFSContext
exists for the provided URI. If it does, DefaultVFSRegistry
first tries to navigate to the existing TempInfo
(link to a temporary files), falling back to regular navigation if no such temporary file exists. In this way you completely reuse any temporary files which have already been unpacked, saving time and disk space. If no matching VFSContext
is found in cache, the code will create a new VFSCache
entry and continue with default navigation.
VFSCache
handles cached VFSContext entries depends on the implementation used. VFSCache
is configurable via VFSCacheFactory
. By default, nothing is cached, but there are a few useful existing VFSCache
implementations, using algorithms such as Least Recently Used (LRU)
or timed cache
.
Chapter 9. The ClassLoading Layer
- classloader
- classloading
- classloading-vfs
classloader
contains a custom java.lang.ClassLoader
extension without any specific classloading policy. A classloading policy includes knowledge of where to load from and how to load.
Classloading
is an extension of Microcontainer’s dependency mechanisms. Its VFS-backed implementation is classloading-vfs
. See Chapter 8, The Virtual File System for more information on VFS.
9.1. ClassLoader
ClassLoader
implementation supports pluggable policies and is itself a final class, not meant to be altered. To write your own ClassLoader implementations, write a ClassLoaderPolicy
which provides a simpler API for locating classes and resources, and for specifying other rules associated with the classloader.
ClassLoaderPolicy
and register it with a ClassLoaderSystem
to create a custom ClassLoader
. You can also create a ClassLoaderDomain
to partition the ClassLoaderSystem
.
ClassLoader
layer also includes the implementation of things like DelegateLoader model, classloading, resource filters, and parent-child delegation policies.
Example 9.1. ClassLoaderPolicy
Class
ClassLoaderPolicy
controls the way your classloading works.
public abstract class ClassLoaderPolicy extends BaseClassLoaderPolicy { public DelegateLoader getExported() public String[] getPackageNames() protected List<? extends DelegateLoader> getDelegates() protected boolean isImportAll() protected boolean isCacheable() protected boolean isBlackListable() public abstract URL getResource(String path); public InputStream getResourceAsStream(String path) public abstract void getResources(String name, Set<URL> urls) throws IOException; protected ProtectionDomain getProtectionDomain(String className, String path) public PackageInformation getPackageInformation(String packageName) public PackageInformation getClassPackageInformation(String className, String packageName) protected ClassLoader isJDKRequest(String name) } }
ClassLoaderPolicy
. The first one retrieves resources based on regular expressions, while the second one handles encrypted resources.
Example 9.2. ClassLoaderPolicy with Regular Expression Support
public class RegexpClassLoaderPolicy extends ClassLoaderPolicy { private VirtualFile[] roots; private String[] packageNames; public RegexpClassLoaderPolicy(VirtualFile[] roots) { this.roots = roots; } @Override public String[] getPackageNames() { if (packageNames == null) { Set<String> exportedPackages = PackageVisitor.determineAllPackages(roots, null, ExportAll.NON_EMPTY, null, null, null); packageNames = exportedPackages.toArray(new String[exportedPackages.size()]); } return packageNames; } protected Pattern createPattern(String regexp) { boolean outside = true; StringBuilder builder = new StringBuilder(); for (int i = 0; i < regexp.length(); i++) { char ch = regexp.charAt(i); if ((ch == '[' || ch == ']' || ch == '.') && escaped(regexp, i) == false) { switch (ch) { case '[' : outside = false; break; case ']' : outside = true; break; case '.' : if (outside) builder.append("\\"); break; } } builder.append(ch); } return Pattern.compile(builder.toString()); } protected boolean escaped(String regexp, int i) { return i > 0 && regexp.charAt(i - 1) == '\\'; } public URL getResource(String path) { Pattern pattern = createPattern(path); for (VirtualFile root : roots) { URL url = findURL(root, root, pattern); if (url != null) return url; } return null; } private URL findURL(VirtualFile root, VirtualFile file, Pattern pattern) { try { String path = AbstractStructureDeployer.getRelativePath(root, file); Matcher matcher = pattern.matcher(path); if (matcher.matches()) return file.toURL(); List<VirtualFile> children = file.getChildren(); for (VirtualFile child : children) { URL url = findURL(root, child, pattern); if (url != null) return url; } return null; } catch (Exception e) { throw new RuntimeException(e); } } public void getResources(String name, Set<URL> urls) throws IOException { Pattern pattern = createPattern(name); for (VirtualFile root : roots) { RegexpVisitor visitor = new RegexpVisitor(root, pattern); root.visit(visitor); urls.addAll(visitor.getUrls()); } } private static class RegexpVisitor implements VirtualFileVisitor { private VirtualFile root; private Pattern pattern; private Set<URL> urls = new HashSet<URL>(); private RegexpVisitor(VirtualFile root, Pattern pattern) { this.root = root; this.pattern = pattern; } public VisitorAttributes getAttributes() { return VisitorAttributes.RECURSE_LEAVES_ONLY; } public void visit(VirtualFile file) { try { String path = AbstractStructureDeployer.getRelativePath(root, file); Matcher matcher = pattern.matcher(path); if (matcher.matches()) urls.add(file.toURL()); } catch (Exception e) { throw new RuntimeException(e); } } public Set<URL> getUrls() { return urls; } } }
RegexpClassLoaderPolicy
uses a simplistic mechanism to find matching resources. Real-world implementations would be more comprehensive and elegant.
public class RegexpService extends PrintService { public void start() throws Exception { System.out.println(); ClassLoader cl = getClass().getClassLoader(); Enumeration<URL> urls = cl.getResources("config/[^.]+\\.[^.]{1,4}"); while (urls.hasMoreElements()) { URL url = urls.nextElement(); print(url.openStream(), url.toExternalForm()); } } }
config/[^.]+\\.[^.]{1,4}
to list resources under the config/
/ directory. The suffix length is limited, such that file names such as excluded.properties will be ignored.
Example 9.3. ClassLoaderPolicy with Encryption Support
public class CrypterClassLoaderPolicy extends VFSClassLoaderPolicy { private Crypter crypter; public CrypterClassLoaderPolicy(String name, VirtualFile[] roots, VirtualFile[] excludedRoots, Crypter crypter) { super(name, roots, excludedRoots); this.crypter = crypter; } @Override public URL getResource(String path) { try { URL resource = super.getResource(path); return wrap(resource); } catch (IOException e) { throw new RuntimeException(e); } } @Override public InputStream getResourceAsStream(String path) { InputStream stream = super.getResourceAsStream(path); return crypter.crypt(stream); } @Override public void getResources(String name, Set<URL> urls) throws IOException { super.getResources(name, urls); Set<URL> temp = new HashSet<URL>(urls.size()); for (URL url : urls) { temp.add(wrap(url)); } urls.clear(); urls.addAll(temp); } protected URL wrap(URL url) throws IOException { return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile(), new CrypterURLStreamHandler(crypter)); } }
META-INF/
directory.
public class EncryptedService extends PrintService { public void start() throws Exception { ClassLoader cl = getClass().getClassLoader(); URL url = cl.getResource("config/settings.txt"); if (url == null) throw new IllegalArgumentException("No such settings.txt."); InputStream is = url.openStream(); print(is, "Printing settings:\n"); is = cl.getResourceAsStream("config/properties.xml"); if (is == null) throw new IllegalArgumentException("No such properties.xml."); print(is, "\nPrinting properties:\n"); } }
ClassLoaderSystem
and deployers.
ClassLoaderSystem
is discussed later in this chapter.
9.2. ClassLoading
ClassLoader
abstraction directly, you can create ClassLoading
modules which contain declarations of ClassLoader
dependencies. Once the dependencies are specified the ClassLoaderPolicy
s are constructed and wired together accordingly.
ClassLoaders
before they actually exist, the abstraction includes a ClassLoadingMetaData
model.
ClassLoadingMetaData
can be exposed as a Managed Object within the new JBoss EAP profile service. This helps system administrators to deal with more abstract policy details rather than the implementation details.
Example 9.4. ClassLoadingMetaData Exposed as a Managed Object
public class ClassLoadingMetaData extends NameAndVersionSupport { /** The serialVersionUID */ private static final long serialVersionUID = -2782951093046585620L; /** The classloading domain */ private String domain; /** The parent domain */ private String parentDomain; /** Whether to make a subdeployment classloader a top-level classloader */ private boolean topLevelClassLoader = false; /** Whether to enforce j2se classloading compliance */ private boolean j2seClassLoadingCompliance = true; /** Whether we are cacheable */ private boolean cacheable = true; /** Whether we are blacklistable */ private boolean blackListable = true; /** Whether to export all */ private ExportAll exportAll; /** Whether to import all */ private boolean importAll; /** The included packages */ private String includedPackages; /** The excluded packages */ private String excludedPackages; /** The excluded for export */ private String excludedExportPackages; /** The included packages */ private ClassFilter included; /** The excluded packages */ private ClassFilter excluded; /** The excluded for export */ private ClassFilter excludedExport; /** The requirements */ private RequirementsMetaData requirements = new RequirementsMetaData(); /** The capabilities */ private CapabilitiesMetaData capabilities = new CapabilitiesMetaData(); ... setters & getters
Example 9.5. ClassLoading API Defined in XML
<classloading xmlns="urn:jboss:classloading:1.0" name="ptd-jsf-1.0.war" domain="ptd-jsf-1.0.war" parent-domain="ptd-ear-1.0.ear" export-all="NON_EMPTY" import-all="true" parent-first="true"/>
Example 9.6. ClassLoading API Defined in Java
ClassLoadingMetaData clmd = new ClassLoadingMetaData(); if (name != null) clmd.setDomain(name + "_Domain"); clmd.setParentDomain(parentDomain); clmd.setImportAll(true); clmd.setExportAll(ExportAll.NON_EMPTY); clmd.setVersion(Version.DEFAULT_VERSION);
jboss-classloading.xml
.
Example 9.7. Adding ClassLoadingMetaData Using jboss-classloading.xml
<classloading xmlns="urn:jboss:classloading:1.0" domain="DefaultDomain" top-level-classloader="true" export-all="NON_EMPTY" import-all="true"> </classloading>
Example 9.8. Typical Domain-Level Isolation
<classloading xmlns="urn:jboss:classloading:1.0" domain="IsolatedDomain" export-all="NON_EMPTY" import-all="true"> </classloading>
Example 9.9. Isolation with a Specific Parent
<classloading xmlns="urn:jboss:classloading:1.0" domain="IsolatedWithParentDomain" parent-domain="DefaultDomain" export-all="NON_EMPTY" import-all="true"> </classloading>
Example 9.10. Non-Compliance with j2seClassLoadingCompliance
<classloading xmlns="urn:jboss:classloading:1.0" parent-first="false"> </classloading>
.war
deployments use this method by default. Instead of doing default parent-first lookups, you first check your own resources.
Example 9.11. Typical OSGi Implementation
<classloading xmlns="urn:jboss:classloading:1.0"> <requirements> <package name="org.jboss.dependency.spi"/> </requirements> <capabilities> <package name="org.jboss.cache.api"/> <package name="org.jboss.kernel.spi"/> </capabilities> </classloading>
Example 9.12. Importing and Exporting Whole Modules and Libraries, Rather than Fine-Grained Packages
<classloading xmlns="urn:jboss:classloading:1.0"> <requirements> <module name="jboss-reflect.jar"/> </requirements> <capabilities> <module name="jboss-cache.jar"/> </capabilities> </classloading>
<classloading xmlns="urn:jboss:classloading:1.0"> <requirements> <package name="si.acme.foobar"/> <module name="jboss-reflect.jar"/> </requirements> <capabilities> <package name="org.alesj.cl"/> <module name="jboss-cache.jar"/> </capabilities> </classloading>
ClassLoader
project, the connection between deployment and classloading is done through the Module
class, which holds all of the required information to properly apply restrictions on the visitor pattern, such as filtering.
Example 9.13. The ResourceVisitor
and ResourceContext
Interfaces
public interface ResourceVisitor { ResourceFilter getFilter(); void visit(ResourceContext resource); } public interface ResourceContext { URL getUrl(); ClassLoader getClassLoader(); String getResourceName(); String getClassName(); boolean isClass(); Class<?> loadClass(); InputStream getInputStream() throws IOException; byte[] getBytes() throws IOException; }
Module::visit
method. This feature is used in the deployment framework to index annotations usage in deployments.
9.3. ClassLoading VFS
ClassLoaderPolicy
that uses a JBoss Virtual File System project to load classes and resources. You can use this idea directly or in combination with a classloading framework.
Example 9.14. Classloading Module Deployer
<deployment xmlns="urn:jboss:bean-deployer:2.0"> <classloader name="anys-classloader" xmlns="urn:jboss:classloader:1.0" import-all="true" domain="Anys" parent-domain="DefaultDomain"> <capabilities> <package name="org.jboss.test.deployers.vfs.reflect.support.web"/> </capabilities> <root>${jboss.tests.url}</root> </classloader> <bean name="AnyServlet" class="org.jboss.test.deployers.vfs.reflect.support.web.AnyServlet"> <classloader><inject bean="anys-classloader:0.0.0"/></classloader> </bean> </deployment>
VFSClassLoaderFactory
class transforms the XML deployer into a VFSClassLoaderPolicyModule
, which then creates the actual ClassLoader
instance. You can then directly use this new ClassLoader
instance with your beans.
Note
VFSClassLoaderFactory
extends ClassLoadingMetaData
, so all examples pertaining to ClassLoadingMetaData
apply in this case as well.
Chapter 10. The Virtual Deployment Framework
10.1. Agnostic Handling of Deployment Types
Deployment
instance.
10.2. Separation of Structure Recognition From Deployment life-cycle logic
my-jboss-beans.xml
, web.xml
, ejb-jar.xml
. Classpaths are classloader roots, such as WEB-INF/classes
or myapp.ear/lib
.
A typical deployment life cycle
- The
MainDeployer
passes the deployment to the set ofStructuralDeployer
s for recognition, and receives back a Deployment context. - Next, the
MainDeployer
passes the resulting Deployment context to theDeployers
for handling by the appropriateDeployer
.
Handling StructuredMetaData Information
- VFS-based deployments
- the structure recognition is forwarded to a set of StructureDeployers.
- JEE-specification-defined structures
- we have matching StructureDeployer implementations:
- EarStructure
- WarStructure
- JarStructure
- DeclarativeStructures
- looks for
META-INF/jboss-structure.xml
file inside your deployment, and parses it to construct a properStructureMetaData
. - FileStructures
- only recognizes known configuration files, such as files like
-jboss-beans.xml
or-service.xml
.Example 10.1. An example of
jboss-structure.xml
<structure> <context comparator="org.jboss.test.deployment.test.SomeDeploymentComparatorTop"> <path name=""/> <metaDataPath> <path name="META-INF"/> </metaDataPath> <classpath> <path name="lib" suffixes=".jar"/> </classpath> </context> </structure>
StructureDeployer
with the help of the generic GroupingStructure
class provided by the generic StructureDeployer
interface.
DeploymentStage
.
Example 10.2. Deployment Stages
public interface DeploymentStages { /** The not installed stage - nothing is done here */ DeploymentStage NOT_INSTALLED = new DeploymentStage("Not Installed"); /** The pre parse stage - where pre parsing stuff can be prepared; altDD, ignore, ... */ DeploymentStage PRE_PARSE = new DeploymentStage("PreParse", NOT_INSTALLED); /** The parse stage - where metadata is read */ DeploymentStage PARSE = new DeploymentStage("Parse", PRE_PARSE); /** The post parse stage - where metadata can be fixed up */ DeploymentStage POST_PARSE = new DeploymentStage("PostParse", PARSE); /** The pre describe stage - where default dependencies metadata can be created */ DeploymentStage PRE_DESCRIBE = new DeploymentStage("PreDescribe", POST_PARSE); /** The describe stage - where dependencies are established */ DeploymentStage DESCRIBE = new DeploymentStage("Describe", PRE_DESCRIBE); /** The classloader stage - where classloaders are created */ DeploymentStage CLASSLOADER = new DeploymentStage("ClassLoader", DESCRIBE); /** The post classloader stage - e.g. aop */ DeploymentStage POST_CLASSLOADER = new DeploymentStage("PostClassLoader", CLASSLOADER); /** The pre real stage - where before real deployments are done */ DeploymentStage PRE_REAL = new DeploymentStage("PreReal", POST_CLASSLOADER); /** The real stage - where real deployment processing is done */ DeploymentStage REAL = new DeploymentStage("Real", PRE_REAL); /** The installed stage - could be used to provide valve in future? */ DeploymentStage INSTALLED = new DeploymentStage("Installed", REAL); }
DeploymentControllerContext
. The Microcontainer's state machine handles dependencies.
parent-first
property. This property is set to true
by default.
all
, top level
, components only
, or no components
.
Warning
Example 10.3. Simple Deployer which Outputs Information About Its Deployment
public class StdioDeployer extends AbstractDeployer { public void deploy(DeploymentUnit unit) throws DeploymentException { System.out.println("Deploying unit: " + unit); } @Override public void undeploy(DeploymentUnit unit) { System.out.println("Undeploying unit: " + unit); } }
-jboss-beans.xml
files in deployers/
directory in JBoss Application Server, and MainDeployerImpl
bean will pick up this deployer via the Microcontainer's IoC callback handling.
Example 10.4. Simple Deployment Descriptor
<bean name="StdioDeployer" class="org.jboss.acme.StdioDeployer"/>
10.3. Natural Flow Control in the form of Attachments
java.util.Map
, whose entries each represent an attachment.
Warning
10.4. Client, User, and Server Usage and Implementation Details
ControllerContexts
inside the Microcontainer. See Why Components Map 1:1 with the ControllerContexts
for the reasons behind this assertion.
Why Components Map 1:1 with the ControllerContexts
- Naming
- The component unit's name is the same as the
ControllerContext
's name. - get*Scope() and get*MetaData()
- return the same MDR context that will be used by the Microcontainer for that instance.
- IncompleteDeploymentException (IDE)
- In order for the IDE to print out what dependencies are missing for a deployment, it needs to know the ControllerContext names. It discovers the name by collecting the Component DeploymentUnit's names in Component Deployers that specify them, such as
BeanMetaDataDeployer
or thesetUseUnitName()
method inAbstractRealDeployer
.
10.5. Single State Machine
jboss-dependency.xml
configuration file in your deployments.
Example 10.5. jboss-dependency.xml
<dependency xmlns="urn:jboss:dependency:1.0"> <item whenRequired="Real" dependentState="Create">TransactionManager</item> (1) <item>my-human-readable-deployment-alias</item> (2) </dependency>
TransactionManager
to be created before the deployment is in the 'Real' stage.
aliases.txt
into your deployment. Each line of the file contains an alias, giving a deployment archive one or more simple names used to refer to it.
10.6. Scanning Classes for Annotations
jboss-scanning.xml
.
Example 10.6. jboss-scanning.xml
<scanning xmlns="urn:jboss:scanning:1.0"> <path name="myejbs.jar"> <include name="com.acme.foo"/> <exclude name="com.acme.foo.bar"/> </path> <path name="my.war/WEB-INF/classes"> <include name="com.acme.foo"/> </path> </scanning>
Appendix A. Revision History
Revision History | |||
---|---|---|---|
Revision 5.2.0-100.400 | 2013-10-31 | Rüdiger Landmann | |
| |||
Revision 5.2.0-100 | Wed 23 Jan 2013 | Russell Dickenson | |
| |||
Revision 5.1.2-100 | Thu 8 December 2011 | Russell Dickenson | |
|