11

I have a library class with a package private method. Directly overriding this method by a subclass is no option. Is there any way, no matter how ugly, to execute own code when this package private method is called from inside the library, e.g. using AspectJ?

Here is a simplified example of the class (the packagePrivateMethod() actually is not invoked directly, but from native code):

public LibClass {

  public LibClass() {
    ...
    packagePrivateMethod();
    ...
  }

  void packagePrivateMethod() {
    // <-- here I want to execute additional code
    ...
  }
}
Thomas S.
  • 5,804
  • 5
  • 37
  • 72
  • Have you tried reflection? You might be able to get hold of the method definition and change its accessibility at runtime - you can't get much uglier than that! – DaveH Nov 23 '13 at 10:32
  • This does not solve the problem, because I don't want to launch this method myself, but add further code when this method is invoked by the library. – Thomas S. Nov 23 '13 at 13:36
  • Have you thought about a different way of solving this problem? Can you elaborate the scenario? I believe there should be a workaround without having to mess with the library package in its present form. – user1807337 Nov 25 '13 at 21:20

5 Answers5

6

You could use a rather heavyweight approach.

  1. Write a small Java agent SO post about that topic.
  2. Use the provided Instrumentation interface to intercept the class loading
  3. Use a byte code modification library (e.g. ASM or Java Assist (only Java 6 !) ) to instrument the byte code (e.g. to replace the method call with whatever you really want to do.

This would work as you can modify the byte code of everything, but it requires you to modify that byte code before it is executed.

Of course you can do that also statically by just modifying the class file, replacing the existing byte code with the byte code you create in step 3 above.

If you do not want / cannot statically replace the byte code of the class, you'll have to do the modification of the bytecode at runtime. For the using a Java agent is a good and solid idea.

Since this is all rather abstract until now, I have added an example which will intercept the loading of your library class, inject a method call in a package private method. When the main method executes, you can see from the output, that the injected method is called directly before the library classes' code. If you add return; as the injected code, you can also prevent the execution of that method alltogether.

So here is the code of an example to your problem solved with Java 6 and JavaAssist. If you want to go along that path and use something newer like Java 7, the you just have to replace the byte code manipulation with ASM. This is a little bit less readable, but also not exactly rocket science.

The main class:

package com.aop.example;

public class Main {

  public static void main(String[] args) {
    System.out.println("Main starts!");
    LibClass libClass = new LibClass();
    System.out.println("Main finished!");
  }
}

Your LibClass:

package com.aop.example;

public class LibClass {

  public LibClass() {
    packagePrivateMethod();
  }

  void packagePrivateMethod() {
    // <-- here I want to execute additional code
    System.out.println("In packagePrivateMethod");
  }
}

The Agent:

package com.aop.agent;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

public class Agent {

  public static void premain(String agentArgs, Instrumentation instr) {
    System.out.println("Agent starts!");
    instr.addTransformer(new ClassFileTransformer() {

      @Override
      public byte[] transform(ClassLoader classLoader, String className, Class<?> arg2, ProtectionDomain arg3,
          byte[] bytes)
          throws IllegalClassFormatException {
        System.out.println("Before loading class " + className);

        final String TARGET_CLASS = "com/aop/example/LibClass";

        if (!className.equals(TARGET_CLASS)) {
          return null;
        }

        LoaderClassPath path = new LoaderClassPath(classLoader);
        ClassPool pool = new ClassPool();
        pool.appendSystemPath();
        pool.appendClassPath(path);

        try {
          CtClass targetClass = pool.get(TARGET_CLASS.replace('/', '.'));
          System.out.println("Enhancing class " + targetClass.getName());
          CtMethod[] methods = targetClass.getDeclaredMethods();
          for (CtMethod method : methods) {
            if (!method.getName().contains("packagePrivateMethod")) {
              continue;
            }
            System.out.println("Enhancing method " + method.getSignature());
            String myMethodInvocation = "com.aop.agent.Agent.myMethodInvocation();";
            method.insertBefore(myMethodInvocation);
          }
          System.out.println("Enhanced bytecode");

          return targetClass.toBytecode();
        }
        catch (CannotCompileException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
        catch (IOException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
        catch (NotFoundException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      }

    });
  }

  public static void myMethodInvocation() {
    System.out.println("<<<My injected code>>>!");
  }
}

The command for running the example (you have to put a agent in a jar with the manifest having an attribute Premain-Class: com.aop.agent.Agent:

%JAVA_HOME%\bin\java -cp .;..\javassist-3.12.1.GA.jar -javaagent:..\..\agent.jar com.aop.example.Main

The output of this example running a command like this:

Agent starts!
Before loading class com/aop/example/Main
Main starts!
Before loading class com/aop/example/LibClass
Enhancing class com.aop.example.LibClass
Enhancing method ()V
Enhanced bytecode
<<<My injected code>>>!
In packagePrivateMethod
Main finished!
Before loading class java/lang/Shutdown
Before loading class java/lang/Shutdown$Lock
Community
  • 1
  • 1
Matthias
  • 3,582
  • 2
  • 30
  • 41
  • You can use Javassist with Java 7 and 8 too. Also, I had errors with my agent until I added an extra parameter to my manifest as I mentioned in the answer to this question: https://stackoverflow.com/questions/10423319/how-do-you-analyze-fatal-javaagent-errors – 11101101b Oct 30 '14 at 20:42
3

You can you Mockito or similar mock library to mock a package private method. Example:

// declared in a package
public class Foo {
    String foo(){
        return "hey!";
    }
}

@Test
public void testFoo() throws Exception {
    Foo foo = Mockito.spy(new Foo());

    Assert.assertEquals("hey!", foo.foo());

    Mockito.when(foo.foo()).thenReturn("bar!");

    Assert.assertEquals("bar!", foo.foo());

}
Andrey Chaschev
  • 16,160
  • 5
  • 51
  • 68
1

Can you add Spring to your project? It might be possible to use a ProxyFactory - see another SO post

Using the ProxyFactory, you can add an advice for a class instance and delegate the method execution to another class (which does packagePrivateMethod() and/or replaces it with the code you want).

Since the library is not spring-managed, you might have to use load-time weaving with spring: ltw xml & examples

Community
  • 1
  • 1
chzbrgla
  • 5,158
  • 7
  • 39
  • 56
  • Unfortunately, I don't have the ability to change the library internals ("*delegate the method execution to another class*"). Otherwise it would be trivial. – Thomas S. Nov 25 '13 at 18:29
  • You got me wrong. You intercept the original method call and delegate that to another class at that point. – chzbrgla Nov 27 '13 at 05:35
1

use the decorator pattern. Its specifically designed for this situation. If you need more details then ping me back else check this

Or you can also use reflections or a byte code manipulation mechanism to create your type dynamically at runtime.

Nazgul
  • 1,892
  • 1
  • 11
  • 15
1

Another idea: create a new class with the same name in the same package.

Say you want to replace LibraryClass in the below project:

Project structure:
  - library.jar (contains com.example.LibraryClass)
  - src
    - com
      - mycompany
        - MyClass.java

Just create the package and file with the same name.

Project structure:
  - library.jar (contains com.example.LibraryClass)
  - src
    - com
      - mycompany
        - MyClass.java
      - example
        - LibraryClass.java  <- create this package and file

This relies on the class loader picking up your file instead of the library's file, but if you are just trying to get a hack working for testing, it is worth a shot. I'm not sure how the class loader decides which file to load, so this may not work in all environments.

If you don't have the source code for LibraryClass, just copy the decompiled code, and make your changes.

For the project where I needed this ability, it was just some test prototyping code... I didn't need anything production quality, or to work in all environments.

Sean Adkinson
  • 8,425
  • 3
  • 45
  • 64