类的加载、连接、初始化

  1、类的加载、连接、初始化

  当使用java命令运行一个java程序的时候就会启动一个java虚拟机进程对该字节码进行加载和执行。当程序使用某个类时,如果该类还未加载到内存中,则JVM会通过加载、连接、初始化三个步骤来对该类进行初始化,有时会统称这三个步骤为类的加载或初始化:

    类的加载:类的加载由类加载器完成,加载即为为该类创建一个java.lang.Class实例对象,一个类对应一个java.lang.Class实例对象。类的加载器一般会从ClassLoader派生,通过loadClass()成员方法来加载某个类。

    类的连接:类被加载生成Class对象后会进入连接阶段,它负责把类的二进制数据合并到JRE中。

    类的初始化:主要是对类变量进行初始化,当使用new或反射来创建类的实例、调用类的类方法、访问类的类变量、使用反射来创建某个类或接口的Class对象(如调用Class.forName("ClassName"))的时候都会初始化该类或接口。

package xu;

import java.lang.reflect.*;

class Tester
{
	static
	{
		System.out.println("Tester类的静态初始化块...");
	}
	public static void func1()
	{
		System.out.println("func1 called");
	}
	public static void func2(String str, int num)
	{
		System.out.println("func2 called");
	}
}

public class Test
{
	public static void main(String[] args) throws Exception
	{
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		Class<?> objClass1 = cl.loadClass("xu.Tester"); // 该语句仅仅是加载Tester类,不会初始化类
		Method mt1 = objClass1.getMethod("func1"); // 该语句会初始化Tester类,导致类的静态初始化块执行
		mt1.invoke(null); //执行Tester中func1方法
		
		Class<?> objClass2 = Class.forName("xu.Tester");// 因为前面已经初始化过Tester类,所以此处不再执行静态初始化块
		String str = new String("java");
		Method mt = objClass2.getMethod("func2", new Class[]{str.getClass(), int.class});
		mt.invoke(null, "java", 100); //执行Tester中func2方法
	}
}

2、类加载器

  JVM启动后会形成三个层次的类加载器:Bootstrap ClassLoader根(引导)类加载器、Extension ClassLoader扩展类加载器、System ClassLoader系统(应用)类加载器。根加载器是由JVM实现的,所以除根加载器外其他的加载器都是ClassLoader子类的实例,他们通过成员方法LoadClass()来加载指定的类:

  ①、根类加载器不是java.lang.ClassLoader的子类,它由JVM自身实现,它负责加载Java核心类。下面代码输出根类加载器加载的核心类的路径,运行该代码可以看到有一行输出为file:/D:/Java/jdk_1.8.05/jre/lib/rt.jar,这个jar包中就包含了String、System等类。  

public class BootstrapTest
{
	public static void main(String[] args)
	{
		// 获取根类加载器所加载的全部URL路径数组
		URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
		// 遍历根类加载器加载的全部URL路径
		for (int i = 0; i < urls.length; i++)
		{
			System.out.println(urls[i].toExternalForm());
		}
	}
}

  ②、扩展类加载器用来加载扩展目录%JAVA_HOME%/jre/lib/ext或java.ext.dirs系统属性指定的路径中jar包的类。

  ③、系统类加载器用来加载java命令中-classpath或java.class.path系统属性或CLASSPATH环境变量或者当前路径(如果以上都没设置)的路径中类。

3、ClassLoader

  类对应的Class对象的getClassLoader()方法可以获得类的加载器,如果该类是通过根类加载器加载的话该方法返回null。

  ClassLoader里包含的一些方法:

    Class<?> loadClass(String name):加载指定的类,返回指定类的Class对象,可以通过Class的newInstance()方法来创建由该类对象表示的类的实例。

    findSystemClass(String name):从本地文件系统中寻找指定类文件,找到的话它会使用defineClass()将原始字节转换为Class对象。

    findLoadedClass(String name)获得指定的类对应的Class对象,如果Java虚拟机已经加载了该名为name的类的话。

  因为ClassLoader中的loadClass()会调用findClass()等方法来实现加载指定的类,所以我们可以通过实现ClassLoader的子类,然后重写findClass()等方法来创建自己的类加载器,通过调用ClassLoader的loadClass()加载要运行的类。比如可以通过下面的CompileClassLoader类来直接编译、加载和运行Test.java中的类:先编译CompileClassLoader类然后运行java xu.CompileClassLoader + Test。

  CompileClassLoader中重写了ClassLoader的findClass方法,在该方法中先编译指定的源文件类,然后读取编译后生成的字节码文件到数据数组中,然后调用defineClass方法通过读取的数据数组获得类对应的java.lang.Class实例对象,通过这个Class对象就可以获得其中的方法,然后调用invoke调用类中指定的方法。

package xu;
import java.io.*;
import java.lang.reflect.*;


public class CompileClassLoader extends ClassLoader
{
	// 重写ClassLoader的findClass方法
	@Override
	protected Class<?> findClass(String name)throws ClassNotFoundException
	{
		Class clazz = null;
		// 将包路径中的点(.)替换成斜线(/)。
		String fileStub = name.replace("." , "/");
		String javaFilename = fileStub + ".java";
		String classFilename = fileStub + ".class";
		File javaFile = new File(javaFilename);
		File classFile = new File(classFilename);
		// 当指定Java源文件存在,且class文件不存在、或者Java源文件的修改时间比class文件修改时间更晚,重新编译
		if(javaFile.exists() && (!classFile.exists()
			|| javaFile.lastModified() > classFile.lastModified()))
		{
			try
			{
				// 如果编译失败,或者该Class文件不存在
				if(!compile(javaFilename) || !classFile.exists())
				{
					throw new ClassNotFoundException(
						"ClassNotFoundExcetpion:" + javaFilename);
				}
			}
			catch (IOException ex)
			{
				ex.printStackTrace();
			}
		}
		// 如果class文件存在,系统负责将该文件转换成Class对象
		if (classFile.exists())
		{
			try
			{
				// 将class文件的二进制数据读入数组
				byte[] raw = getBytes(classFilename);
				// 调用ClassLoader的defineClass方法将二进制数据转换成Class对象
				clazz = defineClass(name,raw,0,raw.length);
			}
			catch(IOException ie)
			{
				ie.printStackTrace();
			}
		}
		// 如果clazz为null,表明加载失败,则抛出异常
		if(clazz == null)
		{
			throw new ClassNotFoundException(name);
		}
		return clazz;
	}
	// 读取一个文件的内容
	private byte[] getBytes(String filename)throws IOException
	{
		File file = new File(filename);
		long len = file.length();
		byte[] raw = new byte[(int)len];
		try(
			FileInputStream fin = new FileInputStream(file))
		{
			// 一次读取class文件的全部二进制数据
			int r = fin.read(raw);
			if(r != len)
			throw new IOException("无法读取全部文件:"
				+ r + " != " + len);
			return raw;
		}
	}
	// 编译指定Java文件的方法
	private boolean compile(String javaFile)throws IOException
	{
		System.out.println("CompileClassLoader:正在编译 " + javaFile + "...");
		// 调用系统的javac命令
		Process p = Runtime.getRuntime().exec("javac " + javaFile);
		try
		{
			// 其他线程都等待这个线程完成
			p.waitFor();
		}
		catch(InterruptedException ie)
		{
			System.out.println(ie);
		}
		// 获取javac线程的退出值
		int ret = p.exitValue();
		// 返回编译是否成功
		return ret == 0;
	}
	public static void main(String[] args) throws Exception
	{
		// 如果运行该程序时没有参数,即没有目标类
		if (args.length < 1)
		{
			System.out.println("缺少目标类,请按如下格式运行Java源文件:");
			System.out.println("java CompileClassLoader ClassName");
			return;
		}
		
		String progClass = args[0];// 第一个参数是需要运行的类
		// 剩下的参数将作为运行目标类时的参数,将这些参数复制到一个新数组中
		String[] progArgs = new String[args.length-1];
		System.arraycopy(args , 1 , progArgs, 0 , progArgs.length);
		
		CompileClassLoader ccl = new CompileClassLoader();
		Class<?> clazz = ccl.loadClass(progClass); // 加载需要运行的类
		Method main = clazz.getMethod("main" , (new String[0]).getClass()); // 获取需要运行的类的主方法
		Object[] argsArray = {progArgs};
		main.invoke(null,argsArray); //执行要运行的类的主方法
	}
}

  4、URLClassLoader

    URLClassLoader也是扩展类加载器和系统类加载器的父类,其构造函数URL可以为从本地文件系统加载(file:前缀)或互联网加载(http:、ftp:前缀)。比如下面的程序在代码中使用URLClassLoader来从文件系统中加载MySql驱动Driver,并使用该驱动来获取数据库连接,这样就不用将MySql驱动添加到CLASSPATH环境变量中:

import java.sql.*;
import java.util.*;
import java.net.*;

public class URLClassLoaderTest
{
	private static Connection conn;
	public static Connection getConn(String url, String user, String pass) throws Exception
	{
		if (conn == null)
		{
			// 创建一个URL数组,以默认的ClassLoader作为父ClassLoader,创建URLClassLoader,传入URL数组
			URL[] urls = {new URL("file:mysql-connector-java-5.1.30-bin.jar")};
			URLClassLoader myClassLoader = new URLClassLoader(urls);
			
			// 加载MySQL的JDBC驱动,并创建默认实例
			Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance();
			
			// 创建一个设置JDBC连接属性的Properties对象,至少需要为该对象传入user和password两个属性
			Properties props = new Properties();
			props.setProperty("user" , user);
			props.setProperty("password" , pass);
			
			// 调用Driver对象的connect方法来取得数据库连接
			conn = driver.connect(url , props);
		}
		return conn;
	}
	public static void main(String[] args)throws Exception
	{
		System.out.println(getConn("jdbc:mysql://localhost:3306/mysql", "root" , "32147"));
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值