Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Class.newInstance always throw an exception?

I want to define a custom class loader to load my own class at run-time.

However, Class.newInstance always fails even if I defined a zero-argument constructor.

The exception message is:

java.lang.IllegalAccessException: Class Hello can not access a member of class Test with modifiers "public"

Why?

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

class CustomClassLoader extends ClassLoader {

    private Map<String, Class<?>> classes = new HashMap<String, Class<?>>();

    public String toString() {
        return CustomClassLoader.class.getName();
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        if (classes.containsKey(name)) {
            return classes.get(name);
        }

        byte[] classData;

        try {
            classData = loadClassData(name);
        } catch (IOException e) {
            throw new ClassNotFoundException("Class [" + name
                    + "] could not be found", e);
        }

        Class<?> c = defineClass(name, classData, 0, classData.length);
        resolveClass(c);
        classes.put(name, c);

        return c;
    }

    private byte[] loadClassData(String name) throws IOException {
        BufferedInputStream in = new BufferedInputStream(
                ClassLoader.getSystemResourceAsStream(name.replace(".", "/")
                        + ".class"));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int i;

        while ((i = in.read()) != -1) {
            out.write(i);
        }

        in.close();
        byte[] classData = out.toByteArray();
        out.close();

        return classData;
    }
}

class Test
{
    public Test()
    {}

    public void Hello()
    {}
}

public class Hello {

    public static void main(String[] args)
    {
        try {
            CustomClassLoader loader = new CustomClassLoader();
            Class<?> c = loader.findClass("Test"); // OK!
            Object o = c.newInstance(); // ALWAYS FAIL!
        }
        catch (Exception e)
        { 
            String s = e.getMessage();
            // s is "java.lang.IllegalAccessException: Class Hello can not access"
            // " a member of class Test with modifiers "public""
        }
    }
}
like image 219
xmllmx Avatar asked Dec 14 '22 17:12

xmllmx


2 Answers

The problem is that your Test class is declared with default (package) scope, but at runtime, your Hello and Test classes are in different packages.

The name (unique identifier) of a class in Java is a combination of the fully-qualified class name and the class loader that loaded it:

At run time, a class or interface is determined not by its name alone, but by a pair: its binary name (§4.2.1) and its defining class loader. Each such class or interface belongs to a single run-time package. The run-time package of a class or interface is determined by the package name and defining class loader of the class or interface. (JVMS 5.3)

In this case, the Test class is being loaded by a downstream class loader, and so its (default) package is not considered to be the same (default) package that Hello (loaded by the system class loader) is in. Therefore, you don't have access to the constructor since the class itself is not public.

This example will work if you either make Test a separate public top-level class or use reflection to make it available.

like image 171
chrylis -cautiouslyoptimistic- Avatar answered Dec 17 '22 07:12

chrylis -cautiouslyoptimistic-


You need to make the class public:

public class Test { ... }

Or, get a reference to the constructor and make it accessible:

Constructor<?> constructor = c.getConstructor();
constructor.setAccessible(true);
Object o = constructor.newInstance();
like image 24
Boann Avatar answered Dec 17 '22 05:12

Boann