|This affects AMPS-Plugin Extensions. That page will need to be changed to reflect the new features that will be implemented based on the information you see on this page.|
|http://svn.rhq-project.org/repos/rhq/trunk/etc/classloaders-test/ has maven modules that builds plugins along with some dependency jars (dummy-1.0.jar, dummy-2.0.jar and dummy-3.0.jar) that you can use to test this stuff.|
|Classloading OpenOffice Document that shows some diagrams illustrating some use-cases and patterns that help me better visualize the classloaders.|
Suppose I am writing plugin "Z" - there are plugins A, B, C and D that may or may not be deployed. Here's Z's descriptor:
This means that if I deploy Z, then:
- plugin A must be deployed as well (i.e. if A does not exist, Z will not deploy).
- plugin D may or may not exist, if it doesn't exist, then Z2.server is ignored and that resource type doesn't really exist.
- plugin B and plugin C may or may not exist. If they don't exist, then Z1.server has no parent types. If one doesn't exist, it simply has the parent types that do exist.
- When Z1.server is detected, the component's classloader is a "resource-level classloader" if classLoader="instance". This means that this resource's classloader will be its own classloader - no other resource will use it. If classLoader="shared" (or it is not specified), it means it will share a classloader with its parent resource (which could be the main plugin classloader if this resource does not depend on another resource via injection or extension as defined in the plugin descriptor). This means every resource of type Z1.server will use the same classloader, that of its parent (or the plugin itself).
- If classLoader="instance", then the ResourceDiscoveryComponent implementation can optionally define a "ClassLoaderFacet" whose method "getAdditionalClasspathUrls" can return a List<URL> that points to additional jars that should be placed in the resource's classloader. When the PC needs to create a classloader for a resource, it should check if the resource's discovery component implements this facet and, if so, it should get the additional classpath URLs and add them to the classloader when it creates it. These will need to be added to the jars that are in the plugin's classloader to make up the resource classloader.
- Would it make sense to allow for a ClassLoaderFacet to provide "hideClasses()" that returns a regex that allows the plugin to tell us its classloader should hide classes (i.e. classes that the plugin wants to provide itself and not have the parent provide) - this is useful if, for example, the plugin wants to use its own JMX classes and not rely on the JMX classes that the parent classloader provides. The first draft implementation does nothing like this.
- What if there are TWO or more <depends> tags - say <depends plugin="E"> in addition to above? We will want the Z plugin classloader to include A and E plugin classes however in the first draft of this implementation, we will not support "multiple inheritance". Having multiple <depends> will currently work like it always has - that being the plugins are required to be deployed but only one has a useClasses attribute that is set to "true" to mark which plugin has its classes in the parent CL.
- When the plugin container is instantiated, it will need to create a "root plugin classloader". This root plugin classloader will hide the agent's classes from the plugins themselves. This means that a plugin will not be able to access any classes in any agent libraries, with some exceptions where those exceptions are configurable (i.e. the agent will be able to tell the plugin container to pass through log4j.jar so the plugins can use log4j for logging without having to include their own logging jar). This root plugin classloader will be configurable such that it can pass through all classes (to assist embedded jopr - it needs to allow all plugins to access the embedding JBossAS server classes).
- A discovery component needs to be created in a proper classloader. Top level discovery components (that is, discover components that look for resources which parent will be the top level platform) are created in the plugin classloader itself. Discovery components for child services (i.e. the result of service scans whose discovered resources will have parent resources of servers, specifically NOT the top platform) will be created in a "discovery classloader" that consists of the discovery component's plugin classloader whose parent classloader is that of the parent resource's classloader.
- If a resource is involved in Injection (runs-inside) or Extension (sourcePlugin/sourceType), its classloader depends on its "classLoader" attribute value and its parent's "classLoader" attribute. This table describes the classloader for a resource based on the different combinations:
|Resource classLoader||Parent classLoader||Classloader Description|
|shared||shared||Both resource and parent are willing to share their classloader. If we reached the top of the resource hierarchy (i.e. the parent is the platform resource), our resource's classloader needs only be its own plugin classloader. If we are not at the top, the resource is running inside its parent resource and thus needs to have that parent resource's classloader, but it also needs to have its own classes from its own plugin. Therefore, in both cases, the resource gets assigned its own plugin classloader. This means that its plugin classloader must also have the parent plugin classloader as its parent. (e.g. RHQ-Server plugin resource runs inside JBossAS server)|
|instance||shared||Resource wants its own classloader because it needs to provide additional jars in order to connect properly to the managed resource; this is so, even though the parent is willing to share its classloader. If we reached the top of the resource hierarchy, our resource's new classloader needs to follow up its plugin classloader hierachy. If we are not at the top, the resource is running inside its parent resource and thus needs to have that parent resource's classloader. (e.g. JBossAS 5 server)|
|shared||instance||Resource is willing to share its own classloader, but the parent has created its own instance. So, even though this resource says it can be shared, it really needs to have its own instance because the parent has its own instance. Therefore, the resource has its own instance of a classloader whose parent classloader is that of its parent resource (e.g. Hibernate running in JBossAS).|
|instance||instance||Same as (shared,instance), the resource has its own instance of a classloader whose parent CL is that of the classloader of its parent resource.|
Note that if a child resource and its parent resource are both from the same plugin, the child will always use the parent's classloader no matter what.
- Source is in SVN branches:
- org.rhq.core.pc.plugin.ClassLoaderManager.obtainResourceClassLoader implements the shared,instance table above
- Having a "depends" tag implicitly means the depending plugin requires that the dependant plugin exists. So if plugin A "depends" on plugin B, having plugin B is required (it is not optional). To deploy plugin A you must also deploy plugin B.
- Using the Extension Model (sourcePlugin attribute on server/service tags) or the Injection Model (using runs-inside element) implicitly means the depending plugin optionally wants that dependent plugin to exist, but it doesn't have to. So if plugin A defines a type that has "sourcePlugin" attributes or has "runs-side" elements that reference plugin B, then plugin B may or may not be deployed with plugin A, it is optional.
- Any types that rely on optional plugins via the Extension model (sourcePlugin attribute) where those optional plugins do not exist, then the types will be ignored and will not exist.
- Any types that rely on optional plugins via the Injection model (runs-inside element) where those optional plugins do not exist, then the types will exist, but they will not have parent resource types for those parent resource types whose plugins are missing.
- All plugin classloaders have a root plugin classloader as their parent. This root plugin classloader is a hiding classloader that prohibits the plugins from accessing many classes deployed in the agent's own core classloader. You can define what classes the agent hides from plugins by setting the agent preference "rhq.agent.plugins.root-plugin-classloader-regex". This is one big regular expression that, when it matches a classname, means that class name is to be hidden from plugins. You normally do not have to set this preference, its default is the most appropriate for all plugins.
- All plugin classloaders are parent-first. Therefore, as soon as one plugin loads a class, all descendants of that classloader will get that version of the class. To be more specific, say there is a plugin "A" and plugin "B". Plugin "A" contains /lib/class1.jar and plugin "B" contains /lib/class2.jar (where class1.jar and class2.jar each have "MyClass.class", but are different versions). Plugin "B" depends on plugin "A". The "MyClass.class" in Plugin "A" will always "win" - the plugin "B" class will never be seen because parent-first semantics say that plugin "A"'s class will load first.
- If a plugin A <depends> on another plugin B (that is, plugin classloader A has plugin classloader B as its parent classloader), then the plugin classloader B is used as resource A's classloader's parent (as opposed to resource B's classloader). (so far it looks like this is only true for those resource's whose parent resource is a platform - unsure if this is the right thing to do).
These use cases are why we need RHQ-2059:
- the Agent has a different version of JBoss Remoting than the one that the AS5 plugin requires to connect to AS5 instances. With the current schema, the version from the agent "wins" and the plugin can not connect to the AS
- SOLUTION: the plugin classloader's root classloader is a hiding classloader and it never sees the remoting library the agent has. The plugin needs to ship with or reference its classloader to its own remoting jar.
- the JBossAS plugin could e.g. supply a version of commons-foobar-1.0 while another plugin that runs within it (e.g. Jboss cache) could need a non-interface-compatible version commons-foobar-2.0. Here the other plugin needs a child first CL to enable commons-foobar-2.0
- SOLUTION: the jboss cache plugin needs to define the following in the <server> element for the cache resource: classLoaderScope="child-first".
- WARNING: CURRENT DESIGN DOESN'T SOLVE THIS ONE
- the JBossAS plugin connects to different versions of JBossAS for the different JBossAS resources it manages. This could mean different jars per resource are needed to connect to them. This requires each resource to have its own classloader so it can connect using the proper jars for the version of the resource.
- SOLUTION: the <server> resource needs to specify classLoader="instance" and the server discovery component needs to implement ClassLoaderFacet that provides the plugin container with the URLs to the plugins that need to be added to the resource's classloader.
- Some companies have "allowed" versions of libraries (commons-foobar-1.0-companyFix) that they are required to use when writing their own plugins, so a agent or as-plugin supplied version of commons-foobar-1.0 can not be used by their policy
- WARNING: CURRENT DESIGN DOESN'T SOLVE THIS ONE
- the hiding / child first behavior needs to be multi-level:
- agent may need version 1.0 of a lib
- as5 plugin may need version 2.0
- fooBar plugin running within the as5 plugin needs version 3.0
- WARNING: CURRENT DESIGN DOESN'T SOLVE THIS ONE