求助:动态生成类,方法可见性的问题

raistlic 2014-03-14 12:46:38
这个问题是在做一个类似ORM的东西时遇到的。

任务目标是在运行时,根据某目标类型,动态生成一个帮助访问的类并加载(该类实现一个名为 RecordAccessor 的接口),并创建它的一个实例,然后通过 RecordAccessor 接口的方法来访问目标类型的对象。

主要是为了避免每次访问该目标类型的对象都用反射。

生成代码、加载、创建实例都没问题,访问时遇到了一点问题。

生成的 RecordAccessor 是通过目标类型对象的方法来访问它的(方法调用的硬编码写在了动态生成的类型中,没有用反射,是直接调用),其中允许目标类型的某些方法是“包可见”,——为了访问这些方法,生成类的时候用了与目标类型相同的包名,就是说,生成的类型应该是跟目标类型在同一个包下。

打印 recordAccessor.getClass().getPackage() 的结果也显示,生成的类型与目标类型在同一个包下。

可是 recordAccessor 内部试图调用目标对象的“包可见”方法时,抛出了 java.lang.IllegalAccessError:

tried to access method target.path.TargetType.setReference(Ljava/lang/String;)V from class target.path.DynamicRecordAccessorForTargetType


当目标类型的方法为 public 时,访问没有问题。

生成和加载类参照了这里的实现:http://www.javablogging.com/dynamic-in-memory-compilation/



请问有谁知道是为什么吗?谢谢!
...全文
182 4 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
gaofuqi 2014-03-14
  • 打赏
  • 举报
回复
楼主想要的效果是什么,是不是下面代码的效果

package com.test.dyna;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaFileObject.Kind;

public class DynaCompTest {

	public static void main(String[] args) throws InstantiationException,
			IllegalAccessException, ClassNotFoundException {
		// TODO Auto-generated method stub

		// Full name of the class that will be compiled.
		// If class should be in some package,
		// fullName should contain it too
		// (ex. "testpackage.DynaClass")
		String fullName = "com.test.dyna.DynaClass";

		// Here we specify the source code of the class to be compiled
		StringBuilder src = new StringBuilder();
		src.append("package com.test.dyna;\n");
		src.append("import com.test.dyna.RecordAccessor;\n");
		src.append("public class DynaClass extends RecordAccessor{\n");
		src.append("    public String toString() {\n");
		src.append("        return \"Hello, I am \" + ");
		src.append("this.getClass().getSimpleName();\n");
		src.append("    }\n");
		src.append("    protected void sayHello() {\n");
		src.append("        System.out.println(\"Hello World!!!!\");\n");
		src.append("    }\n");
		src.append("}\n");

		System.out.println(src);

		// We get an instance of JavaCompiler. Then
		// we create a file manager
		// (our custom implementation of it)
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		JavaFileManager fileManager = new ClassFileManager(
				compiler.getStandardFileManager(null, null, null));

		// Dynamic compiling requires specifying
		// a list of "files" to compile. In our case
		// this is a list containing one "file" which is in our case
		// our own implementation (see details below)
		List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
		jfiles.add(new CharSequenceJavaFileObject(fullName, src));

		// We specify a task to the compiler. Compiler should use our file
		// manager and our list of "files".
		// Then we run the compilation with call()
		compiler.getTask(null, fileManager, null, null, null, jfiles).call();

		// Creating an instance of our compiled class and
		// running its toString() method
		Object instance = fileManager.getClassLoader(null).loadClass(fullName)
				.newInstance();
		System.out.println(instance);

		RecordAccessor rd = (RecordAccessor) instance;
		rd.sayHello();

	}
}

class ClassFileManager extends ForwardingJavaFileManager {
	/**
	 * Instance of JavaClassObject that will store the compiled bytecode of our
	 * class
	 */
	private JavaClassObject jclassObject;

	/**
	 * Will initialize the manager with the specified standard java file manager
	 * 
	 * @param standardManger
	 */
	public ClassFileManager(StandardJavaFileManager standardManager) {
		super(standardManager);
	}

	/**
	 * Will be used by us to get the class loader for our compiled class. It
	 * creates an anonymous class extending the SecureClassLoader which uses the
	 * byte code created by the compiler and stored in the JavaClassObject, and
	 * returns the Class for it
	 */
	@Override
	public ClassLoader getClassLoader(Location location) {
		return new SecureClassLoader() {
			@Override
			protected Class<?> findClass(String name)
					throws ClassNotFoundException {
				byte[] b = jclassObject.getBytes();
				return super.defineClass(name, jclassObject.getBytes(), 0,
						b.length);
			}
		};
	}

	/**
	 * Gives the compiler an instance of the JavaClassObject so that the
	 * compiler can write the byte code into it.
	 */
	@Override
	public JavaFileObject getJavaFileForOutput(Location location,
			String className, Kind kind, FileObject sibling) throws IOException {
		jclassObject = new JavaClassObject(className, kind);
		return jclassObject;
	}
}

class CharSequenceJavaFileObject extends SimpleJavaFileObject {

	/**
	 * CharSequence representing the source code to be compiled
	 */
	private CharSequence content;

	/**
	 * This constructor will store the source code in the internal "content"
	 * variable and register it as a source code, using a URI containing the
	 * class full name
	 * 
	 * @param className
	 *            name of the public class in the source code
	 * @param content
	 *            source code to compile
	 */
	public CharSequenceJavaFileObject(String className, CharSequence content) {
		super(URI.create("string:///" + className.replace('.', '/')
				+ Kind.SOURCE.extension), Kind.SOURCE);
		this.content = content;
	}

	/**
	 * Answers the CharSequence to be compiled. It will give the source code
	 * stored in variable "content"
	 */
	@Override
	public CharSequence getCharContent(boolean ignoreEncodingErrors) {
		return content;
	}
}

class JavaClassObject extends SimpleJavaFileObject {

	/**
	 * Byte code created by the compiler will be stored in this
	 * ByteArrayOutputStream so that we can later get the byte array out of it
	 * and put it in the memory as an instance of our class.
	 */
	protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();

	/**
	 * Registers the compiled class object under URI containing the class full
	 * name
	 * 
	 * @param name
	 *            Full name of the compiled class
	 * @param kind
	 *            Kind of the data. It will be CLASS in our case
	 */
	public JavaClassObject(String name, Kind kind) {
		super(
				URI.create("string:///" + name.replace('.', '/')
						+ kind.extension), kind);
	}

	/**
	 * Will be used by our file manager to get the byte code that can be put
	 * into memory to instantiate our class
	 * 
	 * @return compiled byte code
	 */
	public byte[] getBytes() {
		return bos.toByteArray();
	}

	/**
	 * Will provide the compiler with an output stream that leads to our byte
	 * array. This way the compiler will write everything into the byte array
	 * that we will instantiate later
	 */
	@Override
	public OutputStream openOutputStream() throws IOException {
		return bos;
	}
}
输出结果如下: package com.test.dyna; import com.test.dyna.RecordAccessor; public class DynaClass extends RecordAccessor{ public String toString() { return "Hello, I am " + this.getClass().getSimpleName(); } protected void sayHello() { System.out.println("Hello World!!!!"); } } Hello, I am DynaClass Hello World!!!!
raistlic 2014-03-14
  • 打赏
  • 举报
回复
引用 2 楼 lxbccsu 的回复:
你可以直接把class RecordAccessor做为参数给JavaFileManager
谢谢你的回复 :-) 不过你应该是没弄明白我的问题
lxbccsu 2014-03-14
  • 打赏
  • 举报
回复
引用 1 楼 raistlic 的回复:
好吧,貌似找到原因了,是 ClassLoader 的问题: http://stackoverflow.com/questions/21960255/accessing-package-private-element-between-parent-child-classloaders 如何在子 ClassLoader 加载的类中访问父 ClassLoader 加载的类型的 ‘包可见’ 方法? 这里说如果不用反射,是无解的。 换个思路,已知目标类型的 ClassLoader 都是 JVM 的 application classloader,有没有办法让动态生成的类也用这个 application classloader 来加载?
看了下你提供的例子,你可以直接把class RecordAccessor做为参数给JavaFileManager: 示例:

package lxb.csdn;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class DynaCompTest {

	public static void main(String[] args) throws Exception {
		//你的实际包名
		String fullName = "lxb.csdn.DynaClass";
		
		StringBuilder src = new StringBuilder();
		src.append("package lxb.csdn; \n");//你的实际包名
		src.append("public class DynaClass{\n");
		src.append("   String print(){\n");
		src.append("     return \"hello, I am \" + ");
		src.append("this.getClass().getSimpleName();\n");
		src.append("  }\n}\n");
		
		System.out.println(src);
		
		
		JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
		//把DynaCompTest class实例作为为参数给JavaFileManager
		JavaFileManager fileManager = new ClassFileManager(javaCompiler.getStandardFileManager(null, null, null), 
				DynaCompTest.class);

		List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
        jfiles.add(new CharSequenceJavaFileObject(fullName, src));

        javaCompiler.getTask(null, fileManager, null, null,null, jfiles).call();
        
        Object instance = fileManager.getClassLoader(null).loadClass(fullName).newInstance();
       
        Method m = instance.getClass().getDeclaredMethod("print", null);
        m.setAccessible(true);
        
        System.out.println(m.invoke(instance, null));

	}

}

@SuppressWarnings("rawtypes")
class ClassFileManager extends ForwardingJavaFileManager{
	 private JavaClassObject jclassObject;
	 private Class parent;//增加一个class字段
	 
	 @SuppressWarnings("unchecked")
	public ClassFileManager(StandardJavaFileManager standardManager, Class parent) {
		        super(standardManager);
		        this.parent = parent;
	 }

	 public ClassLoader getClassLoader(Location location) {
		    //获取目标类型的 ClassLoader 
	        return new SecureClassLoader(parent.getClassLoader()) {
	            @Override
	            protected Class<?> findClass(String name) throws ClassNotFoundException {
	                byte[] b = jclassObject.getBytes();
	                return super.defineClass(name, jclassObject.getBytes(), 0, b.length);
	            }
	        };
	 }
	 
	 public JavaFileObject getJavaFileForOutput(Location location, String className, 
	    		Kind kind, FileObject sibling)throws IOException {
	            jclassObject = new JavaClassObject(className, kind);
	        return jclassObject;
	    }


}

class JavaClassObject extends SimpleJavaFileObject{
	
	protected ByteArrayOutputStream bos = new ByteArrayOutputStream();
	
	public JavaClassObject(String name, Kind kind) {
		super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
	}

	public byte[] getBytes(){
		return bos.toByteArray();
	}
	
	@Override
	public OutputStream openOutputStream() throws IOException {
		return bos;
	}
}

class CharSequenceJavaFileObject extends SimpleJavaFileObject {
	private CharSequence content;

	public CharSequenceJavaFileObject(String className, CharSequence content) {
	        super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
	        this.content = content;
	    }
	
	@Override
	public CharSequence getCharContent(boolean ignoreEncodingErrors) {
	        return content;
	    }

}
raistlic 2014-03-14
  • 打赏
  • 举报
回复
好吧,貌似找到原因了,是 ClassLoader 的问题: http://stackoverflow.com/questions/21960255/accessing-package-private-element-between-parent-child-classloaders 如何在子 ClassLoader 加载的类中访问父 ClassLoader 加载的类型的 ‘包可见’ 方法? 这里说如果不用反射,是无解的。 换个思路,已知目标类型的 ClassLoader 都是 JVM 的 application classloader,有没有办法让动态生成的类也用这个 application classloader 来加载?

62,630

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧