AspectJ: ClassLoading issue when trying to use ext

2019-02-15 10:42发布

问题:

I m trying to externalize the configuration of aop.xml so I removed the aop.xml from META-INF and made it available in the server for manual configuration by sys admins.

When I try to use an external aop.xml using

-Dorg.aspectj.weaver.loadtime.configuration="file:D:\Workspace\tomcat7\shared\lib\aop.xml"

I get java.lang.RuntimeException: Cannot register non aspect: aspectclass.... mainly because the aj casses are not loaded by AppClassLoader yet at that time. And the next time it tries to register the aspects from the WebAppClassLoader ( after all the classes are loaded), it works fine, but i still get the exceptions logged from the 1st attempt to register it.

The exception is caught and logged at ClassLoaderWeavingAdaptor.java line 307.

when the following line is called: success = registerAspects(weaver, loader, definitions);

the exception is caught and logged.

    try {
        registerOptions(weaver, loader, definitions);
        registerAspectExclude(weaver, loader, definitions);
        registerAspectInclude(weaver, loader, definitions);
        success = registerAspects(weaver, loader, definitions);
        registerIncludeExclude(weaver, loader, definitions);
        registerDump(weaver, loader, definitions);
    } catch (Exception ex) {
        trace.error("register definition failed", ex);
        success = false;
        warn("register definition failed", (ex instanceof AbortException) ? null : ex);
    }

the exception is thrown excactly in the following line in BcelWeaver.java

if (type.isAspect()) {
      ......
} else {
        // FIXME AV - better warning upon no such aspect from aop.xml
        RuntimeException ex = new RuntimeException("Cannot register non aspect: " + type.getName() + " , " + aspectName);
        if (trace.isTraceEnabled()) {
            trace.exit("addLibraryAspect", ex);
        }
        throw ex;
    }

How can I prevent the classLoader from logging the error to the console, when the aspects are not loaded yet. I was thinking of commenting the line that logs the exception from the source file and rebuilding the aspectjweaver jar file, but was looking for a better solution without modifying the aspectj source.

回答1:

I am not sure that there is an easy way out of your problem. As I said I haven't worked with AspectJ before but I believe this is a mis-behaviour of the weaver.

Problem description: During boot the agent tries to apply weaving other not only to the WebAppClassLoader but to the whole classloader chain (once per classloader) i.e. to: sun.misc.Launcher$AppClassLoader, sun.misc.Launcher$ExtClassLoader, org.apache.catalina.loader.StandardClassLoader (the tomcat's classloader). When you use the META-INF/aop.xml approach it disables weaving for the above classloaders because "a configuration file is not available" (if you enable verbose mode you can see those messages in console). When you use the file configuration approach, a configuration is available for all the classloaders in the chain. Since it does find a configuration file, the agent parses the definitions, it does not find the aspects' class and shows the error.

The weird thing is that, as described in the configuration documentation if you use the WeavingURLClassLoader approach for load time weaving, "... it also allows the user to explicitly restrict by class loader which classes can be woven". So this is actually a feature (!) that the classloader approach can have but the agent approach doesn't. (Unfortunately I was not able to use this approach)

The good (and the bad) news: The good news is that you can easily create your own agent that will ignore the weaving for the aforementioned classloaders. The bad news is that restricting weaving per classloader is not enough because if you have other applications in the same server, Tomcat would still use the WebAppClassLoader to load them so you would still get error messages for those applications. (Perhaps you could extend the classes below to filter packages/classes as well, in that case).

Below you can find two class for the modified agent. To use them you would need to do the following:

  • Un-jar the aspectjweaver.jar to a folder
  • Under org/aspectj/weaver/loadtime create a new folder filter to match the package name and put there the two new classes after you compile them.
  • Edit the META-INF/MANIFEST.MF file and change the line

    Premain-Class: org.aspectj.weaver.loadtime.Agent to
    Premain-Class: org.aspectj.weaver.loadtime.filter.FilterAgent

  • Re-jar and you have your new agent ready.

  • When starting the JVM you can now pass a new system property with a comma separated list of the classloaders you would like to ignore i.e. -Dorg.aspectj.weaver.loadtime.filter=sun.misc.Launcher$AppClassLoader,sun.misc.Launcher$ExtClassLoader,org.apache.catalina.loader.StandardClassLoader ( I have set CATALINA_OPTS to do that).

The classes are a modified copy of the original agent's classes Agent and ClassPreProcessorAgentAdapter. The only code I have added is the part that parses the above system property if it exists and to ignore calls for the classloaders we are not interested in.

Use at your own risk :) I hope that helps

package org.aspectj.weaver.loadtime.filter;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;

public class FilterAgent {

    private static Instrumentation s_instrumentation;

    // Use our own version of ClassFileTransformer that would filter out selected classloaders 
    private static ClassFileTransformer s_transformer = new ClassPreprocessorFilteredAdapter();

    /**
     * JSR-163 preMain Agent entry method
     *
     * @param options
     * @param instrumentation
     */
    public static void premain(String options, Instrumentation instrumentation) {
        /* Handle duplicate agents */
        if (s_instrumentation != null) {
            return;
        }
        s_instrumentation = instrumentation;
        s_instrumentation.addTransformer(s_transformer);
    }

    public static Instrumentation getInstrumentation() {
        if (s_instrumentation == null) {
            throw new UnsupportedOperationException("Java 5 was not started with preMain -javaagent for AspectJ");
        }
        return s_instrumentation;
    }
}
//-----------------------------------------------------------------------------------
package org.aspectj.weaver.loadtime.filter;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Map;

import org.aspectj.weaver.loadtime.Aj;
import org.aspectj.weaver.loadtime.ClassPreProcessor;


public class ClassPreprocessorFilteredAdapter implements ClassFileTransformer {

    /**
     * Concrete preprocessor.
     */
    private static ClassPreProcessor s_preProcessor;

    private static Map<String, String> ignoredClassloaderNames = new HashMap<String, String>();

    static {
        try {
            s_preProcessor = new Aj();
            s_preProcessor.initialize();


            String ignoredLoaders = System.getProperty("org.aspectj.weaver.loadtime.filter", "");
            if (ignoredLoaders.length() > 0) {
                String[] loaders = ignoredLoaders.split(",");

                for (String s : loaders) {
                    s = s.trim();
                    ignoredClassloaderNames.put(s, s);
                    System.out.println("---> Will filtered out classloader: " + s);
                }
            }

        } catch (Exception e) {
            throw new ExceptionInInitializerError("could not initialize JSR163 preprocessor due to: " + e.toString());
        }
    }

    /**
     * Invokes the weaver to modify some set of input bytes.
     * 
     * @param loader the defining class loader
     * @param className the name of class being loaded
     * @param classBeingRedefined is set when hotswap is being attempted
     * @param protectionDomain the protection domain for the class being loaded
     * @param bytes the incoming bytes (before weaving)
     * @return the woven bytes
     */
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
        if (classBeingRedefined != null) {
            System.err.println("INFO: (Enh120375):  AspectJ attempting reweave of '" + className + "'");
        }

        String loaderName = loader.getClass().getName();
        if (shouldIgnoreClassLoader(loaderName)) {
            return bytes;
        }
        return s_preProcessor.preProcess(className, bytes, loader, protectionDomain);
    }

    private boolean shouldIgnoreClassLoader(String loaderName) {
        boolean result = false;
        String ignoredLoader = ignoredClassloaderNames.get(loaderName);
        if (ignoredLoader != null) {
            result = true;    // if the loader name exists in the map we will ignore weaving
        }
        return result;
    }
}


回答2:

If you need the feature to exclude classloaders from weaving with the agent approach, there is a developer build available providing a new command line switch -Daj.weaving.loadersToSkip to do that. The topic is being discussed on a thread of the AspectJ users mailing list. The feature will probably make it into AspectJ 1.7.4, but is not available in 1.7.3 yet.

Update:

The feature did make it into AspectJ 1.7.4 even though it is not explicitly mentioned in the release notes, but listed under resolved issues for that release.



回答3:

What I ended up doing is changing the LOG Level for the error message from ERROR to DEBUG, as I don't see this as an ERROR ( at least in my case ). this case I can still see the error when I enable the DEBUG level. so I modified the source file below and rebuild my aspectjweaver-1.7.1.jar

try {
    registerOptions(weaver, loader, definitions);
    registerAspectExclude(weaver, loader, definitions);
    registerAspectInclude(weaver, loader, definitions);
    success = registerAspects(weaver, loader, definitions);
    registerIncludeExclude(weaver, loader, definitions);
    registerDump(weaver, loader, definitions);
} catch (Exception ex) {
    //(CHANGE 1) trace.error("register definition failed", ex);
    trace.debug( "register definition failed" + ex.getMessage());
    success = false;
    // (CHANGE 2) warn("register definition failed", (ex instanceof AbortException) ? null : ex);
    debug("register definition failed" + ((ex instanceof AbortException) ? null : ex));
}