Add code to package private library method - java

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
...
}
}

You could use a rather heavyweight approach.
Write a small Java agent SO post about that topic.
Use the provided Instrumentation interface to intercept the class loading
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

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());
}

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

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.

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.

Related

Mockito mocks locally final class but fails in Jenkins

I have written some unit tests for a static method. The static method takes only one argument. The argument's type is a final class. In terms of code:
public class Utility {
public static Optional<String> getName(Customer customer) {
// method's body.
}
}
public final class Customer {
// class definition
}
So for the Utility class I have created a test class UtilityTests in which I have written tests for this method, getName. The unit testing framework is TestNG and the mocking library that is used is Mockito. So a typical test has the following structure:
public class UtilityTests {
#Test
public void getNameTest() {
// Arrange
Customer customerMock = Mockito.mock(Customer.class);
Mockito.when(...).thenReturn(...);
// Act
Optional<String> name = Utility.getName(customerMock);
// Assert
Assert.assertTrue(...);
}
}
What is the problem ?
Whereas the tests run successfully locally, inside IntelliJ, they fail on Jenkins (when I push my code in the remote branch, a build is triggered and unit tests run at the end). The error message is sth like the following:
org.mockito.exceptions.base.MockitoException: Cannot mock/spy class
com.packagename.Customer Mockito
cannot mock/spy because :
- final class
What I tried ?
I searched a bit, in order to find a solution but I didn't make it. I note here that I am not allowed to change the fact that Customer is a final class. In addition to this, I would like if possible to not change it's design at all (e.g. creating an interface, that would hold the methods that I want to mock and state that the Customer class implements that interface, as correctly Jose pointed out in his comment). The thing that I tried is the second option mentioned at mockito-final. Despite the fact that this fixed the problem, it brake some other unit tests :(, that cannot be fixed in none apparent way.
Questions
So here are the two questions I have:
How that is possible in the first place ? Shouldn't the test fail both locally and in Jenkins ?
How this can be fixed based in the constraints I mentioned above ?
Thanks in advance for any help.
An alternative approach would be to use the 'method to class' pattern.
Move the methods out of the customer class into another class/classes, say CustomerSomething eg/CustomerFinances (or whatever it's responsibility is).
Add a constructor to Customer.
Now you don't need to mock Customer, just the CustomerSomething class! You may not need to mock that either if it has no external dependencies.
Here's a good blog on the topic: https://simpleprogrammer.com/back-to-basics-mock-eliminating-patterns/
How that is possible in the first place? Shouldn't the test fail both locally and in Jenkins ?
It's obviously a kind of env-specifics. The only question is - how to determine the cause of difference.
I'd suggest you to check org.mockito.internal.util.MockUtil#typeMockabilityOf method and compare, what mockMaker is actually used in both environments and why.
If mockMaker is the same - compare loaded classes IDE-Client vs Jenkins-Client - do they have any difference on the time of test execution.
How this can be fixed based in the constraints I mentioned above?
The following code is written in assumption of OpenJDK 12 and Mockito 2.28.2, but I believe you can adjust it to any actually used version.
public class UtilityTest {
#Rule
public InlineMocksRule inlineMocksRule = new InlineMocksRule();
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Test
public void testFinalClass() {
// Given
String testName = "Ainz Ooal Gown";
Client client = Mockito.mock(Client.class);
Mockito.when(client.getName()).thenReturn(testName);
// When
String name = Utility.getName(client).orElseThrow();
// Then
assertEquals(testName, name);
}
static final class Client {
final String getName() {
return "text";
}
}
static final class Utility {
static Optional<String> getName(Client client) {
return Optional.ofNullable(client).map(Client::getName);
}
}
}
With a separate rule for inline mocks:
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class InlineMocksRule implements TestRule {
private static Field MOCK_MAKER_FIELD;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);
MOCK_MAKER_FIELD = MockUtil.class.getDeclaredField("mockMaker");
MOCK_MAKER_FIELD.setAccessible(true);
int mods = MOCK_MAKER_FIELD.getModifiers();
if (Modifier.isFinal(mods)) {
modifiers.set(MOCK_MAKER_FIELD, mods & ~Modifier.FINAL);
}
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
#Override
public Statement apply(Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
Object oldMaker = MOCK_MAKER_FIELD.get(null);
MOCK_MAKER_FIELD.set(null, Plugins.getPlugins().getInlineMockMaker());
try {
base.evaluate();
} finally {
MOCK_MAKER_FIELD.set(null, oldMaker);
}
}
};
}
}
Make sure you run the test with the same arguments. Check if your intellij run configurations match the jenkins. https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html. You can try to run test on local machine with the same arguments as on jenkins(from terminal), if it will fail that means the problem is in arguments

ByteBuddy - Read class annotations in a java agent

I am trying to access annotations of a class before applying some transformation in a java agent implemented with ByteBuddy.
To access the annotations, I am trying to load the Class object, but it seems that this creates a duplicate class definition.
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class SimpleTestAgent {
public static void premain(String arg, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.isAnnotatedWith(SomeAnnotationType.class))
.transform((builder, type, classLoader, javaModule) -> {
try {
Class loadedClass = Class.forName(type.getName(), true, classLoader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return builder;
})
.installOn(inst);
}
}
A simple class that just creates an instance of the class TestClass that is annotated with the expected annotation, throws the following exception:
Exception in thread "main" java.lang.LinkageError: loader (instance of
sun/misc/Launcher$AppClassLoader): attempted duplicate class
definition for name: "TestClass"
public class AgentTest {
public static void main (String[] args) {
TestClass pet = new TestClass();
}
}
I am trying to implement the agent like in the example described in: Easily-Create-Java-Agents-with-ByteBuddy
Is there a way to load the Class object without causing this issue or a way to access the annotations using the arguments passed to the transformation() method?
The transformation should be able to implement new interfaces and not only override methods this is why I think I cannot use "ForAdvice".
UPDATE
The following loop finds the TestClass only after the Class.forName() is executed. This means that the class has not been loaded and so probably there is no hope to use Class.forName to get the annotations.
for (Class<?> t : inst.getAllLoadedClasses()) {
System.out.println("Class name: " + t.getName());
}
It is possible to get the annotations and the full information about a class using the net.bytebuddy.description.type.TypeDescription instance passed to the transform() method.
The problem is that, for example, I need the Method objects that can be called using reflection. It would be easier if I could somehow access the Class object for the class that is being transformed.
Byte Buddy exposes all information on annotations of a class via the TypeDescription API already, you should not load classes as a class that is loaded during a transformation loads this class before the transformation is applied and aborts class loading with the error you observe. Instead, implement your own matcher:
.type(type -> check(type.getDeclaredAnnotations().ofType(SomeAnnotation.class).load())
Byte Buddy will represent the annotation for you without loading the carrier class itself but rather represent it using its own class file processor.
You should make sure the annotation class is loaded before installing your agent builder to avoid a circularity for this exact annotation.

Keep getting NoClassDefFoundError while loading a class with URLClassLoader

Recently I'm creating something that have to load/unload external jar packages dynamically. I'm now trying to do this with URLClassLoader, but I keep getting NoClassDefFoundError while trying to make new instances.
It seems that the external class is loaded successfully since the codes in the constructor are executed, but ClassNotFoundException and NoClassDefFoundError still keep being thrown.
I made an small package that recreates the error and here are the codes:
The codes below are in ExternalObject.class ,which is put in a .jar file, that I'm trying to load dynamically:
package test.outside;
import test.inside.InternalObject;
public class ExternalObject
{
private final String str;
public ExternalObject()
{
this.str = "Creating an ExternalObject with nothing.";
this.print();
}
public ExternalObject(InternalObject inObj)
{
this.str = inObj.getString();
this.print();
}
public void print()
{
System.out.println(this.str);
}
}
And the codes below are in InternalObject.class:
package test.inside;
public class InternalObject
{
private final String str;
public InternalObject(String s)
{
this.str = s;
}
public String getString()
{
return this.str;
}
}
I tested the file with Main.class below:
package test.inside;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import test.outside.ExternalObject;
public class Main
{
public static void main(String[] args)
{
try
{
File externalJar = new File("F:\\Dev\\ext.jar");
URLClassLoader uclTest = new URLClassLoader(new URL[]{externalJar.toURI().toURL()});
Class<?> clazz = uclTest.loadClass("test.outside.ExternalObject");
InternalObject inObj = new InternalObject("Creating an ExternalObject with an InternalObject.");
try
{
System.out.println("Test 1: Attempt to create an instance of the ExternalObject.class with an InternalObject in the constructor.");
Constructor<?> conTest = clazz.getConstructor(InternalObject.class);
ExternalObject extObj = (ExternalObject)conTest.newInstance(inObj);
}
catch(Throwable t)
{
System.out.println("Test 1 has failed. :(");
t.printStackTrace();
}
System.out.println();
try
{
System.out.println("Test 2: Attempt to create an instance of the ExternalObject.class with a void constructor.");
Constructor<?> conTest = clazz.getConstructor();
ExternalObject extObj = (ExternalObject)conTest.newInstance();
}
catch(Throwable t)
{
System.out.println("Test 2 has failed. :(");
t.printStackTrace();
}
uclTest.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
Both InternalObject.class and Main.class are in a jar pack which is included in the classpath while launched.
And I got this in the console:
Console output screenshot
As the codes this.print() in both constructors of ExternalObject.class are executed, I have really no idea what's wrong. Help! :(
UPDATE: Thank you wero!!! But I actually want to make an instance of ExternalObject for further usage such as accessing methods in it from other classes. Is there any way that I can return the created instance as an ExternalObject? Or I have to use getMethod() and invoke() to access the methods?
Sincerely,
Zevin
Your Main class references ExternalObject and therefore the compiled Main.class has a dependency on ExternalObject.
Now when you run Main and ExternalObject is only available in ext.jar but not in the classpath used to run Main the following happens:
The uclTest classloader successfully loads ExternalObject from ext.jar. Also creation succeeds (seen by the print statement in the constructor).
But what fails are the assignments to local variables ExternalObject extObj.
Main cannot use the loaded class ExternalObject since it is loaded by a different classloader. There is also no ExternalObject in the classpath of Main and you get a NoClassDefFoundError.
Your test should run without problems when you remove the two assignments ExternalObject extObj = (ExternalObject).
I think because there are two classLoaders involved, and you try to cast an object from a classLoader to an object from another class loader. But is just a guess.
How you are running the Main class is causing the problem.
As you said, I have created jar called ext1.jar with ExternalObject and InternalObjct class files inside it.
And created ext.jar with Main and InternalObject class files.
If I run the following command, it throws Exception as you mentioned
java -classpath .;C:
\path\to\ext.jar test.inside.Main
But, If I run the following command, it runs fine without any Exception
java -classpath .;C:
\path\to\ext1.jar;C:
\path\to\ext.jar test.inside.Main
Hooray!! I just found a better way for my codes! What I did is creating an abstract class ExternalBase.class with all abstract methods I need, then inherit ExternalObject.class from ExternalBase.class. Therefore dynamically loaded class have to be neither loaded into the custom loader nor imported by the classes that use the object, and the codes work totally perfect for me. :)

Generate Testng.xlm dynamic to run test

We are having test automation project in selenum with testng. We have completed half of the automation project and running it successfully. Now, we have to add new feature to create flow of testing with multiple test methods. So, for this I have to create java ui which will display all the methods created for test, based on user requirement the methods will be selected for test flow, now when user click on save or run button I have to create the testng.xml file which will run the test for selected methods.
Now Questions I have is as follow:
1. Is it possible to generate dynamic xlm file?
2. In test class I have multiple methods which are depended on other methods of class, so if user select depended methods on ui how I should manage those methods?
3. How to show created methods from all the class on ui to generate flow?
4. Is it good to call bat file to run the generated flow?
I would appreciate your inputs.
Thanks,
Karim
Is it possible to generate dynamic xlm file?
Yes. We do this, we generate the xml from a text file which lists the classes they want to run.
In test class I have multiple methods which are depended on other methods of class, so if user select depended methods on ui how I should manage those methods?
Well you can add the dependent methods when the user selects a method automatically, in which case your code will need to have a map of dependent methods.
How to show created methods from all the class on ui to generate flow?
4. Is it good to call bat file to run the generated flow?
- I didnt understand this part.
if i understanded your question you need to generate xml files that would be tested or something like this
for [xml] (How to generate xml in Java?) or (Fastest and most efficient way to create XML)
all you have to do to create a class similar to XMLEncoder and XMLDecoder wich use beans.
Using below code you can get all the method with #Test annotation....
---
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Set;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;
import org.testng.annotations.Test;
import org.testng.internal.ClassHelper;
import org.testng.internal.annotations.AnnotationHelper;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.internal.annotations.JDK15AnnotationFinder;
public class FindTestMethodsInTestClass {
public static void main(String[] args) {
IAnnotationFinder finder = new JDK15AnnotationFinder(new DummyTransformer());
Set<Method> allMethods = ClassHelper.getAvailableMethods(MyFirstTestClass.class);
for (Method eachMethod : allMethods) {
ITestAnnotation value = AnnotationHelper.findTest(finder, eachMethod);
if (value != null) {
System.out.println(eachMethod.getName() + " is a test method");
} else {
System.out.println(eachMethod.getName() + " is NOT a test method");
}
}
}
public static class DummyTransformer implements IAnnotationTransformer {
#SuppressWarnings("rawtypes")
#Override
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor,
Method testMethod) {
}
}
public static class MyFirstTestClass {
public void foobar() {
}
#Test
public void foo() {
}
#Test
public void bar() {
}
}
}
The reference of above code is from: https://groups.google.com/forum/#!topic/testng-users/WbR2kxpT-7o

Java newbie problem: package with private access

Pack.java imports pack.TestPack; but it cannot access it. I cannot understand why it cannot access the class despite the import.
Error
Pack.java:7: TestPack() is not public in pack.TestPack; cannot be accessed from outside package
System.out.println(new TestPack().getHello());
^
1 error
Pack.java
import pack.TestPack;
import java.io.*;
public class Pack
{
public static void main(String[] args){
System.out.println(new TestPack().getHello());
}
}
TestPack.java
package pack;
import java.util.*;
import java.io.*;
public class TestPack
{
private String hello="if you see me, you ar inside class TestPack";
public String getHello(){return hello;}
TestPack(){}
}
You should make TestPack's constructor public.
public class TestPack
{
private String hello="if you see me, you ar inside class TestPack";
public String getHello(){return hello;}
public TestPack(){}
}
The thing is, even though TestPack visibility is public, its parameterless constructor visibility is package (which is the visibility when you don't specify one explicitly).
package visibility means that classes in the same package will be able to see it. Since TestPack and Pack are not in the same package, Pack can't call TestPack's constructor.
In the way you are using getHello function, you may start thinking using static methods
public class TestPack
{
private static String hello="if you see me, you ar inside class TestPack";
public static String getHello(){return hello;}
private TestPack(){}
}
then you just will do:
public class Pack
{
public static void main(String[] args){
System.out.println(TestPack.getHello());
}
}
I suggest that you don't make the class public but make the constructor public and have folks use a public interface that your class implements. It is a good idea to start the API to your package to be public interfaces (and perhaps some public abstract classes) and hide your implementation classes by not marking them as public so that you can change these over time. You can then provide a public factory methods in your package which instantiate your package private class and return them as the interface types. Here is an interface which is public:
package stackoverflow;
public interface Widget {
public void doWidgetWork(String work);
}
Here is the implementation which is "package private". The compiler wont let code outside of the same package import nor use this class at all:
package stackoverflow;
/*package*/ class WidgetHidden implements Widget {
public WidgetHidden(String configOptionA, String configOptionB){
// ...
}
public WidgetHidden(){
// ...
}
public void doWidgetWork(String work)[
// ...
}
}
notice there that the second occurrence of the word /package/ is a comment (it is not legal in java to use that word there) but many programmers use such a comment in that position to show people that it was not an accident that the class is not public; it signifies that the developer really intended that the class is deliberately "package private". To let people instantiate the class from outside of your package you provide a static factory class (else an instance factory class):
package stackoverflow;
public class WidgetFactory {
public static Widget newInstance( String configOptionA, String configOptionB) {
return new Widget( String configOptionA, String configOptionB);
}
}
The whole point of the factory class is that it hides your internal classes (the ones you hide as package private). Over time you can change your factory classes to return new classes or rename or delete the WidgetHidden class.
Many frameworks indicate which classes other developers should not use by putting them into a package with the name "internal" in it. The public interfaces would be in the main package (e.g. "com.stackoverflow.widget") and the hidden classes into your internal package which only exposes public factory classes (e.g. "com.stackoverflow.widget.internal").
A variation on the theme is to not use a static method on the factory class; make it a regular method. The alternatives are called "static factories" or "instance factories" depending on whether the method is static or not. Not making the method static seems like more work for people using your package as they first have to instantiate your factory object before using it to create Widget. Where is helpful is when people might want to set some defaults for all widgets on the constructor of the factory then use the none static newInstance methods to specify anything beyond the defaults:
public class WidgetInstanceFactory {
private String defaultOptionA = null;
public WidgetInstanceFactory( String defaultOptionA ) {
this.defaultOptionA = defaultOptionA;
}
public Widget newInstance( String optionB ) {
return new WidgetHidden( this.defaultOptionA, optionB );
}
}
It is possible to get around package private protection using reflection to find and invoke the constructor. A really nice feature of the Spring framework it that it will instantiate classes that are not public even when there is no factory class (although it is more polite to provide factory classes which Spring is happy to use also). The following code will work:
package stackoverflow.other;
class TestInstantiate {
private Widget myWidget = null;
public TestInstantiate(){
this.myWidget = instantiatePackagePrivateClass("stackoverflow.WidgetHidden");
}
private Widget instantiatePackagePrivateClass(String className)
throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
#SuppressWarnings("unchecked")
Class<FileUploadSequence> clazz = (Class<Widget>) Class.forName(className);
Constructor<Widget> constructor = clazz.getConstructor(new Class[]{});
constructor.setAccessible(true);
Widget widget = (Widget) constructor.newInstance((Object[])null);
return widget;
}
}
In that example I used the no arguments constructor but clearly you can find and invoke the two string constructor using the same approach. Clearly such code gets around the intention of the programmer who wrote WidgetHidden; they wanted to hide it as they are likely to change it. Anyone who uses such a back door to manipulate the package private object should be aware that the class WidgetHidden is not part of the public API of the framework they are using so it likely to be deleted or changed without prior notice by the developer who wrote the package you are using. Renaming it to be WidgetInternal and putting it into an "internal" package make it every more the case you are telling people "do not uses". The JVM has optional security setting which prevent people from doing such tricks; but the person running the JVM has to configure it externally to dis-allow such tricks which is only useful when you want to run someone else code you don't trust and prevent it from pulling such tricks.
The book Effective Java by Josha Block 2nd Edition has a lot of discussion and examples and details of the pitfalls when trying to write a good API. It has a lot of detail to explain why you should always look to hide as many classes as you can with lots of other good "tricks of the trade".

Categories

Resources