Java 9 and 11 often make it necessary to use a custom classloader. So I created a BootstrapClassloader
that preloads my classpaths, and I use Thread.currentThread().setContextClassLoader(classloader)
to make it the default.
I soon caught Spring reverting to the original application classloader when loading contexts with new ClassPathXmlApplicationContext()
.
No problem I thought, and wrote a variant I called BootstrapClassLoader
to try and force Spring to use my classloader. But Spring still ignores my classloader.
For example, I verified that aopalliance-1.0.jar is in the classloader's classpath and can manually load MethodInterceptor
. But if I have a context with a standard DBCP2 datasource bean (depends on aopalliance) and it throws a ClassNotFoundException
for org.aopalliance.intercept.MethodInterceptor
.
Can anyone see a flaw in the code below, or is there perhaps some special way to register the classloader so that Spring will always use it in the application?
Datasource snippet:
<bean id="msSqlDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
<property name="validationQuery" value="select 1" />
</bean>
<bean id="dbEmailDataSource" parent="msSqlDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" lazy-init="true">
<property name="url" value="" />
<property name="username" value="" />
<property name="password" value="" />
</bean>
Test application code:
public static void main(String[] args) {
try {
Bootstrap.init();
for(String urlName : Bootstrap.getClasspathUrlNames()) {
if(StringUtils.containsIgnoreCase(urlName, "aopalliance")) {
System.out.println(urlName);
}
}
ConfigurableApplicationContext ctx = new BootstrappedApplicationContext("DbEmailContext.xml");
ctx.close();
}catch(Throwable t) {
t.printStackTrace();
}
try {
Class clazz = Bootstrap.getClassLoader().loadClass("org.aopalliance.intercept.MethodInterceptor");
System.out.println("manually loaded: "+clazz.getName());
}catch(Throwable t) {
t.printStackTrace();
}
}
BootstrappedApplicationContext (similar to ClassPathXmlApplicationContext):
public class BootstrappedApplicationContext extends AbstractXmlApplicationContext {
private URLClassLoader classloader;
private Resource[] configResources;
public BootstrappedApplicationContext() {
super();
this.classloader = Bootstrap.getClassLoader();
super.setClassLoader(this.classloader);
}
public BootstrappedApplicationContext(ApplicationContext parent) {
this();
setParent(parent);
}
public BootstrappedApplicationContext(String configLocation)
throws BeansException {
this(new String[] {configLocation}, true, null);
}
public BootstrappedApplicationContext(String... configLocations)
throws BeansException {
this(configLocations, true, null);
}
public BootstrappedApplicationContext(String[] configLocations, ApplicationContext parent)
throws BeansException {
this(configLocations, true, parent);
}
public BootstrappedApplicationContext(String[] configLocations, boolean refresh)
throws BeansException {
this(configLocations, refresh, null);
}
public BootstrappedApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
this(parent);
setConfigLocations(configLocations);
if(refresh) {
refresh();
}
}
public BootstrappedApplicationContext(String path, Class<?>clazz)
throws BeansException {
this(new String[] {path}, clazz);
}
public BootstrappedApplicationContext(String[] paths, Class<?> clazz) throws BeansException {
this(paths, clazz, null);
}
public BootstrappedApplicationContext(String[] paths, Class<?> clazz, ApplicationContext parent)
throws BeansException {
this(parent);
Assert.notNull(paths, "Path array must not be null");
Assert.notNull(clazz, "Class argument must not be null");
this.configResources = new Resource[paths.length];
for (int i = 0; i < paths.length; i++) {
this.configResources[i] = new ClassPathResource(paths[i], clazz);
}
refresh();
}
@Override
public ClassLoader getClassLoader() {
if(classloader == null) {
classloader = Bootstrap.getClassLoader();
}
super.setClassLoader(classloader);
return classloader;
}
public void setClassLoader(URLClassLoader classloader) {
this.classloader = classloader;
super.setClassLoader(classloader);
}
@Override
protected Resource[] getConfigResources() {
return this.configResources;
}
}