一、Java是动态语言吗?
1、动态语言
动态语言是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如JavaScript、Python都是典型的动态语言,而C、C++、Java等语言则不属于动态语言。
动态类型语言,就是类型的检查是在运行时做的,是不是合法的要到运行时才判断,例如JavaScript就没有编译错误,只要运行错误。
2、静态类型
静态类型语言的类型判断是在运行前判断(如编译阶段),比如java就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,而动态类型语言却不需要。
(1)优点:?
在于其结构非常规范,便于调试,方便类型安全;
(2)缺点:
需要写更多的类型相关代码,导致不便于阅读、不清晰明了。动态类型语言的优点在于方便阅读,不需要写非常多的类型相关的代码;缺点自然就是不方便调试,命名不规范时会造成读不懂,不利于理解等。
3、为什么Java可以称之为"准动态语言"?
体现在以下几个方面:
1.反射机制
2.动态编译
3.动态执行javascript代码
4.动态字节码操作
5.动态转换类型
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("setId", Integer.class);
method.invoke(object, 1);
method = clazz.getDeclaredMethod("setName", String.class);
method.invoke(object, "哪吒");
System.out.println(object);
method = clazz.getDeclaredMethod("hello", String.class);
method.invoke(object, "我命由我不由天");
}
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, "GBK"));
StringBuffer buffer=new StringBuffer();
while ((line = br.readLine()) != null) {
buffer.append(line+"\n");
}
} catch (Exception e) {
return false;
}
return true;
}
六、编译非文件形式源代码
1、通过JavaCompiler动态编译
public static void test() {
try {
String beanName = "Student";
String appendTxt = "private String realName";
AppendBeanUtil.appendBean(beanName, appendTxt);
//动态编译
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
String className = "D:\\workspace\\yygh_parent\\myTest\\src\\main\\java\\com\\guor\\bean\\Student.java";
int status = javac.run(null, null, null, "-d", System.getProperty("user.dir")+"\\target\\classes",className);
if(status!=0){
System.out.println("没有编译成功!");
}
ClassLoader classLoader = Student.class.getClassLoader();
Class<?> classLoad = classLoader.loadClass(Student.class.getName());
Object newInstance = classLoad.newInstance();
Method setName = classLoad.getDeclaredMethod("setName", String.class);
setName.invoke(newInstance,"哪吒");
System.out.println("动态载入属性成功+++++"+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。
代码实例:
(1)动态编译工程内存在的bean
private void test02() {
try {
String beanName = "Student";
String appendTxt = "private String realName;";
AppendBeanUtil.appendBean(beanName, appendTxt);
String class_name = "com.guor.bean.Student";
URLClassLoader contextClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
CusCompiler compiler = new CusCompiler(contextClassLoader);
String script = FileUtils.readFileToString(new File("D:\\workspace\\yygh_parent\\myTest\\src\\main\\java\\com\\guor\\bean\\Student.java"),"utf-8");
Map<String, byte[]> results = compiler.compile(class_name, script);
Class<?> clazz = compiler.loadClass(class_name, results);
System.out.println("+++++++"+clazz);
Object newInstance = clazz.newInstance();
Method setName = clazz.getDeclaredMethod("setRealName", String.class);
setName.invoke(newInstance,"哪吒");
System.out.println("动态载入属性成功+++++"+newInstance);
} catch (Exception e) {
System.out.println(e);
}
}
(2)Compiler工具类
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.tools.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.*;
import java.nio.CharBuffer;
import java.util.*;
import java.util.jar.JarEntry;
public class CusCompiler {
private final static Logger log = LoggerFactory.getLogger(CusCompiler.class);
static class CustomJavaFileObject implements JavaFileObject {
private String binaryName;
private URI uri;
private String name;
public String binaryName() {
return binaryName;
}
public CustomJavaFileObject(String binaryName, URI uri) {
this.uri = uri;
this.binaryName = binaryName;
name = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getPath();
}
@Override
public Kind getKind() {
return Kind.CLASS;
}
@Override
public boolean isNameCompatible(String simpleName, Kind kind) {
String baseName = simpleName + kind.extension;
return kind.equals(getKind()) && (baseName.equals(getName()) || getName().endsWith("/" + baseName));
}
@Override
public NestingKind getNestingKind() {
throw new UnsupportedOperationException();
}
@Override
public Modifier getAccessLevel() {
throw new UnsupportedOperationException();
}
@Override
public URI toUri() {
return uri;
}
@Override
public String getName() {
return name;
}
@Override
public InputStream openInputStream() throws IOException {
return uri.toURL().openStream();
}
@Override
public OutputStream openOutputStream() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public Writer openWriter() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public long getLastModified() {
return 0;
}
@Override
public boolean delete() {
throw new UnsupportedOperationException();
}
}
static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
final String code;
MemoryInputJavaFileObject(String name, String code) {
super(URI.create(name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
}
static class MemoryOutputJavaFileObject extends SimpleJavaFileObject {
final String name;
Map<String, byte[]> class_out;
MemoryOutputJavaFileObject(String name, Map<String, byte[]> out) {
super(URI.create(name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.CLASS);
this.name = name;
this.class_out = out;
}
@Override
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
@Override
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
class_out.put(name, bos.toByteArray());
}
};
}
}
static class SpringBootJarFileManager implements JavaFileManager {
private URLClassLoader classLoader;
private StandardJavaFileManager standardJavaFileManager;
final Map<String, byte[]> classBytes = new HashMap<>();
SpringBootJarFileManager(StandardJavaFileManager standardJavaFileManager, URLClassLoader systemLoader) {
this.classLoader = new URLClassLoader(systemLoader.getURLs(), systemLoader);
this.standardJavaFileManager = standardJavaFileManager;
}
@Override
public ClassLoader getClassLoader(Location location) {
return classLoader;
}
private List<JavaFileObject> find(String packageName) {
List<JavaFileObject> result = new ArrayList<>();
String javaPackageName = packageName.replaceAll("\\.", "/");
try {
Enumeration<URL> urls = classLoader.findResources(javaPackageName);
while (urls.hasMoreElements()) {
URL ll = urls.nextElement();
String ext_form = ll.toExternalForm();
String jar = ext_form.substring(0, ext_form.lastIndexOf("!"));
String pkg = ext_form.substring(ext_form.lastIndexOf("!") + 1);
JarURLConnection conn = (JarURLConnection) ll.openConnection();
conn.connect();
Enumeration<JarEntry> jar_items = conn.getJarFile().entries();
while (jar_items.hasMoreElements()) {
JarEntry item = jar_items.nextElement();
if (item.isDirectory() || (!item.getName().endsWith(".class"))) {
continue;
}
if (item.getName().lastIndexOf("/") != (pkg.length() - 1)) {
continue;
}
String name = item.getName();
URI uri = URI.create(jar + "!/" + name);
String binaryName = name.replaceAll("/", ".");
binaryName = binaryName.substring(0, binaryName.indexOf(JavaFileObject.Kind.CLASS.extension));
result.add(new CustomJavaFileObject(binaryName, uri));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds,
boolean recurse) throws IOException {
Iterable<JavaFileObject> ret = null;
if (location == StandardLocation.PLATFORM_CLASS_PATH) {
ret = standardJavaFileManager.list(location, packageName, kinds, recurse);
} else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
ret = find(packageName);
if (ret == null || (!ret.iterator().hasNext())) {
ret = standardJavaFileManager.list(location, packageName, kinds, recurse);
}
} else {
ret = Collections.emptyList();
}
return ret;
}
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
String ret = "";
if (file instanceof CustomJavaFileObject) {
ret = ((CustomJavaFileObject) file).binaryName;
} else {
ret = standardJavaFileManager.inferBinaryName(location, file);
}
return ret;
}
@Override
public boolean isSameFile(FileObject a, FileObject b) {
throw new UnsupportedOperationException();
}
@Override
public boolean handleOption(String current, Iterator<String> remaining) {
return standardJavaFileManager.handleOption(current, remaining);
}
@Override
public boolean hasLocation(Location location) {
return location == StandardLocation.CLASS_PATH || location == StandardLocation.PLATFORM_CLASS_PATH;
}
@Override
public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind)
throws IOException {
throw new UnsupportedOperationException();
}
@Override
public FileObject getFileForInput(Location location, String packageName, String relativeName)
throws IOException {
throw new UnsupportedOperationException();
}
@Override
public FileObject getFileForOutput(Location location, String packageName, String relativeName,
FileObject sibling) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void flush() throws IOException {
}
@Override
public void close() throws IOException {
classBytes.clear();
}
@Override
public int isSupportedOption(String option) {
return -1;
}
public Map<String, byte[]> getClassBytes() {
return new HashMap<String, byte[]>(this.classBytes);
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return new MemoryOutputJavaFileObject(className, classBytes);
} else {
return standardJavaFileManager.getJavaFileForOutput(location, className, kind, sibling);
}
}
}
public Class<?> loadClass(String name, Map<String, byte[]> classBytes) throws Exception {
ClassLoader loader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> r = null;
if (classBytes.containsKey(name)) {
byte[] buf = classBytes.get(name);
r = defineClass(name, buf, 0, buf.length);
} else {
r = systemClassLoader.loadClass(name);
}
return r;
}
};
return loader.loadClass(name);
}
private URLClassLoader systemClassLoader;
public CusCompiler(URLClassLoader loader) {
systemClassLoader = loader;
}
public Map<String, byte[]> compile(String className, String code) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
SpringBootJarFileManager springBootJarFileManager = new SpringBootJarFileManager(stdManager, systemClassLoader);
JavaFileObject javaFileObject = new MemoryInputJavaFileObject(className, code);
// List<String> options = new ArrayList<>();
// options.addAll(Arrays.asList("-classpath",
// System.getProperty("java.class.path"), "-bootclasspath",
// System.getProperty("sun.boot.class.path"), "-extdirs",
// System.getProperty("java.ext.dirs")));
JavaCompiler.CompilationTask task = compiler.getTask(null, springBootJarFileManager, null, null, null,
Arrays.asList(javaFileObject));
Boolean compileRet = task.call();
if (compileRet == null || (!compileRet.booleanValue())) {
throw new RuntimeException("java filter compile error");
}
for (String key : springBootJarFileManager.getClassBytes().keySet()) {
log.info("class: " + key + " len: "
+ Integer.valueOf(springBootJarFileManager.getClassBytes().get(key).length).toString());
}
return springBootJarFileManager.getClassBytes();
}
}
往期精彩内容:
Java知识体系总结
Spring框架总结
超详细的springBoot学习笔记
常见数据结构与算法整理总结
Java设计模式:23种设计模式全面解析
Java面试题总结(附答案)
Linux知识体系总结
Redis知识体系总结
|