| 
 | 
	
 
一、Java是动态语言吗? 
 
1、动态语言 
 
动态语言是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如JavaScript、Python都是典型的动态语言,而C、C++、Java等语言则不属于动态语言。 
动态类型语言,就是类型的检查是在运行时做的,是不是合法的要到运行时才判断,例如JavaScript就没有编译错误,只要运行错误。 
2、静态类型 
 
静态类型语言的类型判断是在运行前判断(如编译阶段),比如java就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,而动态类型语言却不需要。 
(1)优点: 
 
在于其结构非常规范,便于调试,方便类型安全; 
(2)缺点: 
 
需要写更多的类型相关代码,导致不便于阅读、不清晰明了。动态类型语言的优点在于方便阅读,不需要写非常多的类型相关的代码;缺点自然就是不方便调试,命名不规范时会造成读不懂,不利于理解等。 
3、为什么Java可以称之为"准动态语言"? 
 
体现在以下几个方面: 
 
- 反射机制
 
 - 动态编译
 
 - 动态执行javascript代码
 
 - 动态字节码操作
 
 - 动态转换类型
 
  Java的反射机制被视为Java为准动态语言的主要的一个关键性质,这个机制允许程序在运行时透过反射取得任何一个已知名称的class的内部信息,包括: 
正在运行中的类的属性信息,正在运行中的类的方法信息,正在运行中的类的构造信息,正在运行中的类的访问修饰符,注解等等。 
动态语言无时不刻在体现动态性,而静态语言也在通过其他方法来趋近于去弥补静态语言的缺陷。 
二、了解ClassLoader 
 
ClassLoader是类加载器,具体的作用就是将class文件加载到jvm虚拟机中,程序就可以正常运行了。但是,JVM启动的时候,并不会一次性的加载所有的class文件,而是根据需要动态的去加载。如果一次性加载全部的jar包,内存肯定会扛不住。 
1、ClassLoader 
 
所有类加载器的基类,它是抽象的,定义了类加载最核心的操作。所有继承classloader的加载器,都会先判断是否被父类加载器加载过,防止多次加载,防止加载冲突。 
2、Bootstrap classLoader 
 
位于java.lang.classload,所有的classload都要经过这个classload判断是否已经被加载过,采用native code实现,是JVM的一部分,主要加载JVM自身工作需要的类,如java.lang.、java.uti.等; 这些类位于$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。 
3、URLClassLoader 
 
继承自SecureClassLoader,支持从jar文件和文件夹中获取class,继承于classload,加载时首先去classload里判断是否由bootstrap classload加载过,1.7 新增实现closeable接口,实现在try 中自动释放资源,但捕获不了.close()异常。 
4、AppClassLoader 
 
应用类加载器,继承自URLClassLoader,也叫系统类加载器(ClassLoader.getSystemClassLoader()可得到它),它负载加载应用的classpath下的类,查找范围System.getProperty(“java.class.path”),通过-cp或-classpath指定的类都会被其加载,没有完全遵循双亲委派模型的,它重要的是loadClass方法。 
三、双亲委派机制 
 
主要体现在ClassLoader的loadClass()方法中,思路很简单:先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载器加载失败,抛出ClassNotFoundException异常后,调用自己的findClass()方法进行加载。 
四、JavaCompiler动态编译 
 
使用JavaCompiler进行动态编译。 
//使用ToolProvider.getSystemJavaCompiler来得到一个JavaCompiler接口的实例。 
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
//JavaCompiler中最核心的方法是run()。通过这个方法能编译java源代码。 
int run(InputStream in, OutputStream out, OutputStream err, String... arguments) 
参数分别用来为: 
 
- java编译器提供参数
 
 - 得到Java编译器的输出信息
 
 - 接收编译器的错误信息
 
 - 一个或多个Java源程式文件
 
  如果run编译成功,返回 0。 
如果前3个参数传入的是null,那么run方法将以标准的输入、输出代替,即System.in、System.out和System.err。如果我们要编译一个test.java文件,并将使用标准输入输出,run的使用方法如下: int results = tool.run(null, null, null, "D:\\test\\Student.java"); 
五、通过URLClassLoader加载程序外的jar包,并进行动态编译 
 
1、实体类Student 
 
package com.guor.bean; 
  
public class Student { 
    public Integer id; 
    public String name; 
     
    public void hello(String param) { 
        System.out.println(param); 
    } 
  
    public Integer getId() { 
        return id; 
    } 
  
    public void setId(Integer id) { 
        this.id = id; 
    } 
  
    public String getName() { 
        return name; 
    } 
  
    public void setName(String name) { 
        this.name = name; 
    } 
     
    @Override 
    public String toString() { 
        return "Student [id=" + id + ", name=" + name + "]"; 
    } 
} 
2、Java文件 -> class -> jar -> 动态编辑 -> 反射赋值 
 
private void test01() throws Exception { 
        final String javaPath = "D:\\test\\java"; 
        final String studentPath = javaPath + "\\Student.java"; 
        final String jarPath = "D:\\test\\jar\\student-1.0.0.jar"; 
        final String packageStudentPath = "com.guor.bean.Student"; 
        // 将java源文件编译成.class字节码文件 
        String cmd = "javac " + studentPath; 
        System.out.println(cmd); 
        boolean execSysCmd = execCmd(cmd); 
        System.out.println(execSysCmd); 
  
        // 打成jar包 
        cmd = "jar -cvf " + jarPath + " " + javaPath; 
        System.out.println(cmd); 
        execSysCmd = execCmd(cmd); 
        System.out.println(execSysCmd); 
  
        /** 
         *  URLClassLoader:继承自SecureClassLoader,支持从jar文件和文件夹中获取class, 
         *  继承于classload,加载时首先去classload里判断是否由bootstrap classload加载过 
         */ 
        URL url = new URL("file:" + jarPath); 
        URLClassLoader classLoader = new URLClassLoader(new URL[] { url }, 
                Thread.currentThread().getContextClassLoader()); 
        CusCompiler compiler = new CusCompiler(classLoader); 
  
        File file = new File(studentPath); 
        String beanTxt = FileUtils.readFileToString(file, "utf-8"); 
  
        Map<String, byte[]> results = compiler.compile(packageStudentPath, beanTxt); 
        Class<?> clazz = compiler.loadClass(packageStudentPath, results); 
  
        Object object = clazz.newInstance(); 
        Method method = clazz.getDeclaredMethod(&#34;setId&#34;, Integer.class); 
        method.invoke(object, 1); 
         
        method = clazz.getDeclaredMethod(&#34;setName&#34;, String.class); 
        method.invoke(object, &#34;哪吒&#34;); 
         
        System.out.println(object); 
         
        method = clazz.getDeclaredMethod(&#34;hello&#34;, String.class); 
        method.invoke(object, &#34;我命由我不由天&#34;); 
    } 
3、执行cmd命令 
 
private boolean execCmd(String cmd) { 
    try { 
        Runtime rt = Runtime.getRuntime(); 
        Process proc = rt.exec(cmd); 
        InputStream es = proc.getErrorStream(); 
        String line; 
        BufferedReader br; 
        br = new BufferedReader(new InputStreamReader(es, &#34;GBK&#34;)); 
        StringBuffer buffer=new StringBuffer(); 
        while ((line = br.readLine()) != null) { 
            buffer.append(line+&#34;\n&#34;); 
        } 
    } catch (Exception e) { 
        return false; 
    } 
    return true; 
} 
六、编译非文件形式源代码 
 
1、通过JavaCompiler动态编译 
 
public static void test() { 
    try { 
        String beanName = &#34;Student&#34;; 
        String appendTxt = &#34;private String realName&#34;; 
        AppendBeanUtil.appendBean(beanName, appendTxt); 
  
        //动态编译 
        JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); 
        String className = &#34;D:\\workspace\\yygh_parent\\myTest\\src\\main\\java\\com\\guor\\bean\\Student.java&#34;; 
        int status = javac.run(null, null, null, &#34;-d&#34;, System.getProperty(&#34;user.dir&#34;)+&#34;\\target\\classes&#34;,className); 
        if(status!=0){ 
            System.out.println(&#34;没有编译成功!&#34;); 
        } 
  
        ClassLoader classLoader = Student.class.getClassLoader(); 
        Class<?> classLoad = classLoader.loadClass(Student.class.getName()); 
  
        Object newInstance = classLoad.newInstance(); 
        Method setName = classLoad.getDeclaredMethod(&#34;setName&#34;, String.class); 
        setName.invoke(newInstance,&#34;哪吒&#34;); 
  
        System.out.println(&#34;动态载入属性成功+++++&#34;+newInstance); 
    } catch (Exception e) { 
        System.out.println(e); 
    } 
} 
2、springboot中动态编译工程内存在的bean 
 
JDK 6 的编译器 API 的另外一个强大之处在于,它可以编译的源文件的形式并不局限于文本文件。JavaCompiler 类依靠文件管理服务可以编译多种形式的源文件。比如直接由内存中的字符串构造的文件,或者是从数据库中取出的文件。这种服务是由 JavaFileManager 类提供的。 
在Java SE6中最佳的方法是使用StandardJavaFileManager类。这个类能非常好地控制输入、输出,并且能通过DiagnosticListener得到诊断信息,而DiagnosticCollector类就是listener的实现。新的 JDK 定义了 javax.tools.FileObject 和 javax.tools.JavaFileObject 接口。任何类,只要实现了这个接口,就可以被 JavaFileManager 识别。 
使用StandardJavaFileManager步骤: 
 
- 建立一个DiagnosticCollector实例
 
 - 通过JavaCompiler.getStandardFileManager()方法得到一个StandardFileManager对象。
 
 - 使用StandardFileManager获取需要编译的源代码。从文件或者字符流中获取源代码。
 
 - JavaCompiler.getTask()生成编译任务抽象。
 
 - 通过CompilationTask.call()方法编译源代码。
 
 - 关闭StandardFileManager。
 
  3、代码实例 
 
private void test02() { 
    try { 
        String beanName = &#34;Student&#34;; 
        String appendTxt = &#34;private String realName;&#34;; 
        AppendBeanUtil.appendBean(beanName, appendTxt); 
  
        String class_name = &#34;com.guor.bean.Student&#34;; 
        URLClassLoader contextClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader(); 
        CusCompiler compiler = new CusCompiler(contextClassLoader); 
  
        String script = FileUtils.readFileToString(new File(&#34;D:\\workspace\\yygh_parent\\myTest\\src\\main\\java\\com\\guor\\bean\\Student.java&#34;),&#34;utf-8&#34;); 
  
        Map<String, byte[]> results = compiler.compile(class_name, script); 
        Class<?> clazz = compiler.loadClass(class_name, results); 
        System.out.println(&#34;+++++++&#34;+clazz); 
  
        Object newInstance = clazz.newInstance(); 
        Method setName = clazz.getDeclaredMethod(&#34;setRealName&#34;, String.class); 
        setName.invoke(newInstance,&#34;哪吒&#34;); 
  
        System.out.println(&#34;动态载入属性成功+++++&#34;+newInstance); 
    } catch (Exception e) { 
        System.out.println(e); 
    } 
} |   
 
 
 
 |