0

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;
  }
}
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • Ehrm in over 20 years of java development with small and very large applications I never had to mess around with classloaders. So it looks more that you are trying to be smarter than the JDK and Spring. – M. Deinum Aug 19 '20 at 17:40
  • Neither did I until trying to migrate from Java 8 to Java 11, and I have also been coding in Java with both small and massive applications since 1996. Oracle really crippled a few common practices when it gave up on the backwards compatibility we had since Sun was the Java steward. I don't expect to need this ability when we slowly migrate to Java 17, but I need to quickly migrate over 100 applications that predate me, written with old practices. That means preserving most of their design. I know it is possible as most web containers do it "somehow". So I must figure it out. – user321974 Aug 19 '20 at 21:54
  • Even migrating to java11 or beyond I haven't needed this. Looks like you are using the wrong tool to solve something that shouldn't be solved. Messing with classloaders will open up a can of worms. – M. Deinum Aug 20 '20 at 05:38

0 Answers0