2 Replies Latest reply on Jul 3, 2005 11:35 PM by Shane Bryzak

    HotSwap in standalone app with custom classloader

    Shane Bryzak Master

      I have a standalone application that supports hot deployment of simple services which are contained within their own .jar files. Each of these service .jars is allocated their own classloader, which is responsible for loading the classes, resources etc from that one file. What I would like to do is support the ability to define aspects within each service jar that can bind to pointcuts in classes either within the same service jar or in the host application itself.

      Some additional info - I'm developing with JDK 1.5.0 and JBoss AOP 1.3, and defining my aspects using annotations. I'm running JBoss AOP using the -javaagent method, with the -hotSwap parameter.

      I've read chapters 6 and 10 from the reference documentation, but couldn't find anything dealing with my particular scenario. Is it possible to achieve what I've described? Not knowing any better, I've attempted loading the aspects using org.jboss.aop.AspectAnnotationLoader. While this seems to get my aspects registered with the AspectManager, there is still the question of how to instrument my classes when they're loaded with a custom classloader. Any guidance on how I should go about this is greatly appreciated.

        • 1. Re: HotSwap in standalone app with custom classloader
          Kabir Khan Master

          By using the -javaagent switch all classloaders will get intercepted with the chance to instrument classes. Maybe try getting it to work with the "main" classloader first, and come back to us with any issues that may arise with the custom classloaders.

          • 2. Re: HotSwap in standalone app with custom classloader
            Shane Bryzak Master

            I've now got Aspects that are loaded with a custom classloader working with other classes loaded by the same classloader. What I still can't get working is an aspect defined with a custom-classloaded class to intercept a method in a class loaded by the system classloader. I don't know if what I'm trying to achieve is possible, because it requires runtime instrumentation of a class (though isn't that what the -hotSwap parameter enables?). I've hacked together a quick and nasty test case that demonstrates what I'm trying to achieve:

            package aoptest;
            
            import java.io.ByteArrayInputStream;
            import java.io.File;
            import java.io.FileInputStream;
            import java.io.FileOutputStream;
            import java.io.InputStream;
            import java.lang.reflect.Method;
            import java.util.ArrayList;
            import java.util.HashMap;
            import java.util.List;
            import java.util.Map;
            import org.jboss.aop.Aspect;
            import org.jboss.aop.AspectAnnotationLoader;
            import org.jboss.aop.AspectManager;
            import org.jboss.aop.Bind;
            import org.jboss.aop.joinpoint.MethodInvocation;
            
            @Aspect public class AOPTest
            {
             private static final String HELLO_CLASS_SRC = "package aoptest;\n" +
             "import org.jboss.aop.Aspect;\n " +
             "import org.jboss.aop.Bind;\n " +
             "import org.jboss.aop.joinpoint.MethodInvocation;\n " +
             "@Aspect public class HelloTest { \n" +
             " @Bind (pointcut=\"execution(void aoptest.AOPTest->test())\") \n" +
             " public Object injectionAdvice(MethodInvocation invocation) throws Throwable { \n" +
             " System.out.println(\"aoptest.HelloTest >>> Intercepted call to AOPTest.test()\"); \n" +
             " return invocation.invokeNext(); } \n" +
             " @Bind (pointcut=\"execution(void aoptest.AOPTest->test2())\") \n" +
             " public Object injection2Advice(MethodInvocation invocation) throws Throwable { \n" +
             " System.out.println(\"aoptest.HelloTest >>> Intercepted call to AOPTest.test2()\"); \n" +
             " return invocation.invokeNext(); } \n" +
             " @Bind (pointcut=\"execution(void aoptest.HelloTest->hello())\") \n" +
             " public Object helloInjectionAdvice(MethodInvocation invocation) throws Throwable { \n" +
             " System.out.println(\"aoptest.HelloTest >>> Intercepted call to HelloTest.hello()\"); \n" +
             " return invocation.invokeNext(); } \n" +
             " public void hello() { System.out.println(\"Hello\"); } \n}\n";
            
             static Map<String,byte[]> classData = new HashMap<String,byte[]>();
            
             static ClassLoader cl;
            
             public static void main(String[] args)
             throws Exception
             {
             compile();
            
             Class helloClass = Class.forName("aoptest.HelloTest", true, cl);
             Method helloMethod = helloClass.getMethod("hello", new Class[] {});
            
             // Have to include the following lines otherwise the aspects defined in
             // aoptest.HelloTest aren't loaded.
             ClassLoader sysCl = Thread.currentThread().getContextClassLoader();
             Thread.currentThread().setContextClassLoader(cl);
             List<InputStream> cs = new ArrayList<InputStream>();
             cs.add(cl.getResourceAsStream("aoptest.HelloTest"));
             AspectAnnotationLoader loader = new AspectAnnotationLoader(AspectManager.instance());
             loader.deployInputStreamIterator(cs.iterator());
             Thread.currentThread().setContextClassLoader(sysCl);
            
             AOPTest t = new AOPTest();
            
             // Intercepted by both AOPTest and HelloTest, as expected.
             t.test();
            
             // Intercepted by both AOPTest and HelloTest, as expected.
             helloMethod.invoke(helloClass.newInstance(), new Object[] {});
            
             // Expected to be intercepted by HelloTest, but isn't.
             t.test2();
             }
            
             public AOPTest()
             {
             cl = new CustomClassLoader();
             }
            
             @Bind (pointcut="execution(void aoptest.AOPTest->test())")
             public Object injectionAdvice(MethodInvocation invocation)
             throws Throwable
             {
             System.out.println("aoptest.AOPTest >>> Intercepted call to AOPTest.test()");
             return invocation.invokeNext();
             }
            
             @Bind (pointcut="execution(void aoptest.HelloTest->hello())")
             public Object helloInjectionAdvice(MethodInvocation invocation)
             throws Throwable
             {
             System.out.println("aoptest.AOPTest >>> Intercepted call to HelloTest.hello()");
             return invocation.invokeNext();
             }
            
             public void test()
             {
             System.out.println("AOPTest.test()");
             }
            
             public void test2()
             {
             System.out.println("AOPTest.test2()");
             }
            
             static void compile()
             throws Exception
             {
             String tmpDir = System.getProperty("java.io.tmpdir").replaceAll("\\\\", "/");
             // Create the source file
             File srcDir = new File(tmpDir + "/aoptest");
             if (!srcDir.exists())
             srcDir.mkdir();
             File srcFile = new File(srcDir, "HelloTest.java");
             FileOutputStream out = new FileOutputStream(srcFile);
             out.write(HELLO_CLASS_SRC.getBytes());
             out.flush();
             out.close();
             // Compile HelloTest.java
             String classPath = org.jboss.aop.Aspect.class.getProtectionDomain().getCodeSource().getLocation().getPath();
             String[] options = new String[] {"-sourcepath", tmpDir, "-source", "1.5",
             "-nowarn", "-classpath", classPath, tmpDir + "aoptest/HelloTest.java"};
             com.sun.tools.javac.Main.compile(options);
             // Load the class data
             File clsFile = new File(srcDir + "/HelloTest.class");
             byte[] data = new byte[(int) clsFile.length()];
             InputStream in = new FileInputStream(clsFile);
             int read = 0;
             do
             { read += in.read(data, read, data.length - read); }
             while (read < data.length);
             classData.put("aoptest.HelloTest", data);
             }
            
             class CustomClassLoader extends ClassLoader
             {
             public Class findClass(String name)
             {
             try
             { return Class.forName(name); }
             catch (ClassNotFoundException ex) { }
             byte[] b = classData.get(name);
             if (b != null)
             {
             try
             { return defineClass(name, b, 0, b.length); }
             catch (Exception ex1) { }
             }
             return null;
             }
             public InputStream getResourceAsStream(String name)
             {
             if (classData.containsKey(name))
             return new ByteArrayInputStream(classData.get(name));
             else
             return getParent().getResourceAsStream(name);
             }
             }
            }
            


            What I'm trying to determine is whether an aspect defined in aoptest.HelloTest (loaded by custom classloader) can intercept a method in AOPTest (loaded by system classloader) that hasn't been previously instrumented. The call to test1() is intercepted as aoptest.AOPTest itself defines an aspect that binds to this method. The call to test2() however doesn't get intercepted.