0

Following on from another question, if you add an object to the context attributes that was instatiated during the main method (System class loader), but then used in the Web App classloader context (ie, in a servlet), there are issues that I can't seem to solve:

One of two things happens:

  1. with no extra changes, you get a ClassCastException when trying to get the object out of the attributes of the servlet context, or,
  2. By adding WebAppContext.addSystemClasses(server, SomeClass.class.getName()), you get java.lang.LinkageError: loader constraint violation: when resolving method...'s instead. This is likely because I only included the base

Short of forcing the use of the SystemClassLoader in the WebApp, is there a way around that? Is that a viable solution, and what are the drawbacks of doing that?

Ben
  • 4,785
  • 3
  • 27
  • 39

1 Answers1

1

Don't attempt to resolve the class at runtime. Meaning, if you just use the Class, the minute its loaded, its fixed at a whatever the current context classloader is. Be mindful of this. Better yet, use Strings, or packages, always here.

Using the Class in the Binding directly binds the Class on the wrong System Classloader.

Example from jetty-webapp-logging (to capture all logging at the server level).

public class CentralizedWebAppLoggingBinding implements AppLifeCycle.Binding
{
    public String[] getBindingTargets()
    {
        return new String[]
        { "deploying" };
    }

    public void processBinding(Node node, App app) throws Exception
    {
        ContextHandler handler = app.getContextHandler();
        if (handler == null)
        {
            throw new NullPointerException("No Handler created for App: " + app);
        }

        if (handler instanceof WebAppContext)
        {
            WebAppContext webapp = (WebAppContext)handler;
            // Older API
            webapp.addSystemClass("org.apache.log4j.");
            webapp.addSystemClass("org.slf4j.");
            webapp.addSystemClass("org.apache.commons.logging.");
        }
    }
}

You could alternatively access the Class from the point of view of the WebApp, this would ensure your ClasspathPattern is sane as well.

package jetty.deploy;

import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.thread.ThreadClassLoaderScope;
import org.eclipse.jetty.webapp.ClasspathPattern;
import org.eclipse.jetty.webapp.WebAppContext;

public class ExampleClassLoaderBinding implements AppLifeCycle.Binding
{
    @Override
    public String[] getBindingTargets()
    {
        return new String[]{AppLifeCycle.DEPLOYING};
    }

    @Override
    public void processBinding(Node node, App app) throws Exception
    {
        ContextHandler handler = app.getContextHandler();
        if (handler == null)
        {
            throw new NullPointerException("No Handler created for App: " + app);
        }

        if (handler instanceof WebAppContext)
        {
            WebAppContext webapp = (WebAppContext) handler;
            try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(webapp.getClassLoader()))
            {
                // Demonstration of newer API
                ClasspathPattern systemClasspathPattern = webapp.getSystemClasspathPattern();
                systemClasspathPattern.add("org.apache.log4j.");
                systemClasspathPattern.add("org.slf4j.");
                systemClasspathPattern.add("org.apache.commons.logging.");

                // Example of accessing class via WebApp scope
               Class<?> clazz = Class.forName("com.corp.MyClass", true, scope.getScopedClassLoader());
               Object obj = clazz.getDeclaredConstructor().newInstance();
            }
        }
    }
}
Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136