java反射机制以及应用
JAVA反射机制+动态运行编译期不存在的JAVA程序
一、有关JAVA反射
在运行期间,在不知道某个类A的内部方法和属性时,能够动态的获取信息、获取类或对象的方法、属性的功能,称之为反射。
1.相关类:
(1)Class
(2)Method
(3)Field
2.相关方法:
(1)Class.forName("<完整包名+类名>"); 返回对应的类(同一JVM不管创建该类的多少对象,类只有一个)。
(2)class.getMethod(要获取的方法名,参数类型,参数类型.....); 返回该方法。
(3)执行方法method.invoke(实例,参数,参数.....) 返回该方法的返回值(Object类型,可强转为指定类型)。
(4)class.newInstance() 返回一个实例对象,通常与(3)一起使用。需要public的无参构造函数。
(5)class.getFields() 返回该对象的所有共有属性。
(6)class.getDeclaredFields 获取对象的共有+私有属性。
(7)有参构造函数调用:
Class c=Class.forName("com.chx.TestB");
Constructor ct=c.getDeclaredConstructor(new Class[]{int.class,String.class});
ct.setAccessible(true);//可调用private的构造函数。
TestB b2=(TestB) ct.newInstance(1,"2");
3.为什么能支持反射:由于方法区保存着类的信息,比如属性、方法、访问权限等等,类的信息以对象的形式存在着,叫做元对象。这给反射提供了土壤,让反射变为可能。
二、应用
1.简介:
公司有个项目,需要将提交上来的Java源码进行编译与运行,并返回结果。一般的思想是JVM-命令行-JVM的模式。但是这样不但执行速度慢,且获取编译异常、运行异常、运行结果都是非常困难的。所以通过JAVA反射机制,以及自带的编译工具完成功能。
2.代码:
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class RunStudentPro { private static String CLASSPATH; /** * 程序运行成功 */ private static final boolean SUCCESS = true; /** * 程序运行失败 */ private static final boolean DEFAULT = false; /** * 编译期的错误列表 */ private List<String> bianYiError; /** * 学生运行的结果 */ private Object studenResult; /** * 运行时异常 */ private List<String> runTimeErrors; /** * 运行学生程序的主方法 * * @param pro * 学生代码 * @param javaName * 学生类名 * @param methodName * 学生方法 * @return */ public boolean runStudentProgram(String pro, String javaName, String methodName, String studentID) { // 获取类所在的路径 CLASSPATH = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); if (System.getProperty("os.name").contains("dows")) { CLASSPATH = CLASSPATH.substring(1, CLASSPATH.length()); } if (CLASSPATH.endsWith(".jar")) { CLASSPATH = CLASSPATH.substring(0, CLASSPATH.lastIndexOf("/") + 1); } CLASSPATH = CLASSPATH + studentID + "/"; // 在本地写入java文件 writejavaFile(CLASSPATH, javaName, pro); // 编译java文件 boolean javaCompilerFile = JavaCompilerFile(javaName); if (!javaCompilerFile) { // 编译期失败 return DEFAULT; } boolean runStudentProgram = runStudentProgram(javaName, methodName); if (!runStudentProgram) { // 运行失败 return DEFAULT; } return SUCCESS; } /** * 编译学生的java文件 * * @param javaName * @return */ private boolean JavaCompilerFile(String javaName) { StandardJavaFileManager javafile = null; boolean result = SUCCESS; try { JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); javafile = javac.getStandardFileManager(null, null, null); DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); String filename = CLASSPATH + javaName + ".java"; Iterable<? extends JavaFileObject> units = javafile.getJavaFileObjects(filename); CompilationTask t = javac.getTask(null, javafile, collector, null, null, units); if (!t.call()) { List<Diagnostic<? extends JavaFileObject>> diagnostics = collector.getDiagnostics(); bianYiError = new ArrayList<String>(); for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) { bianYiError.add(diagnostic.toString()); } result = DEFAULT; } } finally { try { if (javafile != null) { javafile.close(); } } catch (IOException e) { e.printStackTrace(); } } return result; } /** * 运行学生的程序 * * @param javaName * 主类名 * @param methodName * 方法名 * @return */ private boolean runStudentProgram(String javaName, String methodName) { URLClassLoader classload = null; boolean result = SUCCESS; try { URL url = new URL("file:/" + CLASSPATH); URL[] urls = new URL[] { url }; classload = new URLClassLoader(urls); Class<?> clazz = classload.loadClass(javaName); Method method = clazz.getMethod(methodName); studenResult = method.invoke(clazz.newInstance()); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException | MalformedURLException e) { runTimeErrors = new ArrayList<String>(); runTimeErrors.add(getExceptionAllinformation(e)); result = DEFAULT; } finally { if (classload != null) { try { classload.close(); } catch (IOException e) { e.printStackTrace(); result = DEFAULT; } } } return result; } /** * 获取异常的所有信息并转换为字符串 * * @param ex * @return */ private static String getExceptionAllinformation(Exception ex) { ByteArrayOutputStream out = new ByteArrayOutputStream(); PrintStream pout = new PrintStream(out); ex.printStackTrace(pout); String ret = new String(out.toByteArray()); pout.close(); try { out.close(); } catch (Exception e) { } return ret; } /** * 创建java文件 * * @param path * 路径 * @param javaName * 文件名 * @param pro * 程序字符串 */ private static void writejavaFile(String path, String javaName, String pro) { File dir = new File(path); if (!dir.exists()) { dir.mkdir(); } File file = new File(path + javaName + ".java"); if (file.exists()) { file.delete(); } FileWriter fw = null; try { file.createNewFile(); fw = new FileWriter(file); fw.write(pro); } catch (IOException e) { e.printStackTrace(); } finally { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 获取编译时的错误信息 * * @return */ public List<String> getBianYiError() { return bianYiError; } /** * 获取学生运行后的结果 * * @return */ public Object getStudenResult() { return studenResult; } /** * 获取学生运行时的异常 * * @return */ public List<String> getRunTimeErrors() { return runTimeErrors; } }
3.详细讲解:
(1)程序入口为public boolean runStudentProgram(String pro, String javaName, String methodName, String studentID)
通过传入代码、主类名称、要执行的方法名、学生ID,进行保存代码,编译代码、运行代码。
其中,this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath()获取当前类所在的目录(主要是方便移植,此路径可写死或修改为指定目录),然后将.jar及开头的'/'符号去除。
(2)编译学生代码:
通过JavaCompiler类进行编译(JDK1.6提供),使用方式为JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
注意的是,编译器接口是在javax.tools包里,所以可能IDE所配置的JRE不存在此类,所以报错的话直接更改JRE。
可以通过CompilationTask t = javac.getTask(null, javafile, collector, null, null, units);中设置collector获取编译的错误信息。
其中编译方法的运行为call()方法,也可以javac.run();
(3)运行学生代码:
method.invoke(clazz.newInstance());因为我们规定了不允许传入参数,所以此处只是传入了实例,并没有传入参数。
运行时的错误信息,可以通过异常Exception指定输出流进行保存。本程序的getExceptionAllinformation方法即为实现方法。
三、运行效果截图
1.正常运行,传入的代码如下
运行结果如下:
2.模拟编译时异常,传入代码如下:
运行结果如下:
3.模拟运行时异常,传入代码如下:
运行结果如下:
二、未完成版反射深拷贝(后续补完)
/** * 用反射实现深拷贝,要求必须是同一个类。 * 要求: * 1.不支持继承类深拷贝 * 2.把a的值赋给b * 3.必须有get set方法 * * @param * @param a 参数1 * @param b 参数2 * @return * @author chx * @date 2020/6/9 16:33 */ public static <T> void deepCopy(T a, T b) { //首先判断是否属于同一类,有两种方案,可以用instanceof // 或者通过getClass().getSuperclass() Class<?> aClassa = a.getClass(); Class<?> aClassb = b.getClass(); if (aClassa != aClassb) { try { throw new Exception("请传入相同类型的参数进行拷贝"); } catch (Exception e) { e.printStackTrace(); } return; } //匹配上了,开始反射深拷贝 Field[] fields = aClassa.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { try { String name = fields[i].getName(); Field field = aClassa.getDeclaredField(name); if (field != null) { //获取A的get方法 String methodNameA = "get" + name.substring(0, 1).toUpperCase() + name.substring(1); Method method = aClassa.getMethod(methodNameA); Object result = method.invoke(a); //将A的值set进B中 String methodNameB = "set" + name.substring(0, 1).toUpperCase() + name.substring(1); Method methodB = aClassb.getMethod(methodNameB, String.class); methodB.invoke(b, result); } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }