fried cake server ----我的油炸糕(3)远端类加载器

前两章主要贴出了服务器通讯核心和资源执行器的实现,这两个东西其实都不难,只要花些时间,我相信大部分的程序员都能做出来。两章结束后我们的服务器应该已经能够实现html等资源的执行了,而我们也即将遇到服务器开发中的第一个难点:远端类加载!
远端类加载是所有服务器都无法回避的第一道门槛,web服务器与web 项目是两个不同的域(或者说是两个不同的项目),app server又需要在运行中动态解析项目(包括拆war包),因此我们无法够预先定义web服务器的classPath使其支持web 项目中的类,所以远端加载势在必行。
java里面所有的类加载,都必须使用classloader,既然如此,我们就重载一个:

package util;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import server.AppRunTime;
/**
 * 这个类专门用来解析web工程里用到的class文件
 * 包括jar文件,classes文件夹,和系统类文件
 * @author 刘宇航
 */
public class LocalClassLoader extends ClassLoader {
	
	private Map<String, Class<?>> loadedClasses = new Hashtable<String, Class<?>>();
	private static Map<String,LocalClassLoader> loaders= new Hashtable<String, LocalClassLoader>();
	private String projectPath = "";
	int cacheSize=6;//默认的class文件大小
	/**
	 * 防止其他初始化途径
	 */
	private LocalClassLoader(){
		cacheSize = (Integer)AppRunTime.getInstance().getProperty(Constant.SERVER_CACHE);
	}
	/**
	 * 针对每一个工程,返回一个单例
	 * @param projPath 传入一个项目根路径 用来区分不同项目的LocalClassLoader
	 * @return LocalClassLoader
	 */
    public static LocalClassLoader getInstance(String projPath){
    	if(loaders.containsKey(projPath)==false)
    	{
    		LocalClassLoader instance  =  new LocalClassLoader();
    		instance.projectPath = projPath;
    		loaders.put(projPath, instance);
    	}
    		return loaders.get(projPath);
    	
    }
    
    /**
     * 加载相关类
     */
    public synchronized Class<?> loadClass(String className,boolean resolve)throws ClassNotFoundException{
    	Class<?> newClass ;
    	byte[] classData=null;
    	//检查是否已经加载
    	newClass = (Class<?>) loadedClasses.get(className);
    	if(newClass!=null){
    		if(resolve) this.resolveClass(newClass);
    		return newClass;
    	}
    	//如果还没有加载,那么第一件事则是去系统中加载
    	//之所以这么费事,是因为被加载的类可能用到系统类,根据约定这些
    	//系统类会被同一个classloader加载进来,所以必须加入以下代码
    	try {
			newClass =super.findSystemClass(className);
			if(resolve) this.resolveClass(newClass);
			return newClass;
		} catch (ClassNotFoundException e) {
			System.out.println(className.trim()+"并非系统类,将调用外部加载程序……");
		}
		//下面的是针对外部工程的加载程序
		try{
    	String classFileName = projectPath+Constant.PROJECT_CLASSES_PATCH+className.trim();
    	classData = getClassData(classFileName);
		}catch(Exception e)
		{
			e.printStackTrace();
		}
		//如果是jsp
		if(null==classData){
		    System.out.println(className+" 不在工程目录下,开始尝试加载work文件夹");
		    String classFileName = projectPath+Constant.FILE_SP+className;
		    System.out.println("尝试加载"+classFileName);
	    	classData = getClassData(classFileName);
			}
		//对jar文件和路径还需要特殊加载
		if(null==classData){
	    System.out.println(className+" 不在/classes目录下,开始尝试加载jar文件");
		classData = loadFromJarFile(className);
		}
		//其他操作
		newClass = defineClass(null,classData,0,classData.length);
		loadedClasses.put(className, newClass);
		if(resolve) this.resolveClass(newClass);
		return newClass;
    	
    }
    /**
     * 从jar文件中返回class文件的字节数组,当返回值为null时,表示没找到对应类
     * @param className 类名
     * @return 以字节数组形式保存的类的内容
     */
    private synchronized byte[] loadFromJarFile(String className) {
    	String jarFilePatch = projectPath+Constant.PROJECT_JAR_PATCH;
    	File patch = new File (jarFilePatch);
    	if(patch.isDirectory())
    	{
    		File[] fileArray = patch.listFiles(new FileTypeFilter(Constant.FileType_SP_jar));
    		for(File jar:fileArray)
    		{
    		 byte[] values = getDataFromFile(jar,className);
    		 if(values!=null&&values.length>0) return values;
    		}
    	}
    	//否则返回null
		return null;
	}
	private byte[] getDataFromFile(File jar,String className) {
		className = className.replace(".", "/")+Constant.FileType_SP_Class;
		int length=0;
		byte[] data = new byte[1024*8];//8m的class文件,大小应该在改为从配置文件读取
		try {
			ZipFile jarFile = new ZipFile(jar);
			ZipEntry entity = jarFile.getEntry(className);
			InputStream in = jarFile.getInputStream(entity);
			length= in.read(data);
			//数据处理
			byte[] returnValue = new byte[length]; 
			System.arraycopy(data, 0, returnValue, 0, length);
			return returnValue;
		} catch (Exception e) {
			e.printStackTrace();
		} 
		return null;
	}
	/**
     * 获得类的内容 ,当返回值为null时,表示没找到对应类
     * 但是这个类现在有一个问题,当classFileName中含有
     * 作为非类分隔符而存在的'.'字符时,路径解析会不正确
     * @param classFileName
     * @return 字节数组
     */
	private synchronized byte[] getClassData(String classFileName) {
		byte[] data = new byte[1024*cacheSize];//8m的class文件,应该够大了吧
		int length=0;
		classFileName = classFileName.replace(".", "\\");
		classFileName+=Constant.FileType_SP_Class;
		System.out.println("正在加载" +classFileName);
		File classFile = new File(classFileName);
		if(!classFile.exists()||!classFile.canRead()) return null;
		try {
			FileInputStream in = new FileInputStream(classFile);
			length = in.read(data);
		} catch (Exception e) {
			e.printStackTrace();
		}
		byte[] returnValue = new byte[length]; 
		System.arraycopy(data, 0, returnValue, 0, length);
		return returnValue;
	}
}


tomcat的代码里面做了三个classloader,分别对应系统类、外部类和jar包,我的实现里只做了一个,这样会牺牲一些效率,但是在调用加载器的时候就不必在做复杂的判断了。
下面是servlet执行器代码,其中调用了新的classLoader
/**
 * @author:	    刘宇航			@create:	2009-4-13
 * @modifier:	刘宇航			@modify:	2009-4-13
 * @reviewer:	刘宇航			@review:	2009-4-13
 * 执行servlet的执行器
 */
package executor.impl;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import project.ProjectInfo;
import servlet.ServletInfo;
import util.Constant;
import util.LocalClassLoader;
import executor.CheckableExecutor;

public class ServletExecutor implements  CheckableExecutor {

	private ProjectInfo project;

	private ServletInfo info;

	public void init(ProjectInfo project) {
		this.project = project;
	}

	/**
	 * 执行servlet的方法体
	 */
	@Override
	public void forward(HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		// 执行部分
		ClassLoader loader = LocalClassLoader.getInstance(project.getPatch());
		HttpServlet servlet = (HttpServlet) loader.loadClass(
				info.getServletClass()).newInstance();
		// 先执行filter 然后是servlet本身
		FilterChain chain = info.getFilterChain();
		chain.doFilter(request, response);
		servlet.init(info.getServletConfig());
		servlet.service(request, response);
		servlet.destroy();
	}

	/**
	 * 判断资源是否可以执行
	 */
	@Override
	public boolean isExecutable(HttpServletRequest request) {
		//先创建ServletInfo对象
		ServletInfo info =getServletInfo(request);
		if(info != null)
		{
			this.info = info;
			return true;
		}
		else
			return false;
		
			
	}
	/**
	 * 匹配servlet
	 * @param request
	 * @return
	 */
	private ServletInfo getServletInfo(HttpServletRequest request) {
		for(ServletInfo info :project.getServletInfo())
		{
		 Pattern p = Pattern.compile(Constant.WEB_SP+project.getWebPath()+info.getUrl_pattern());
		 Matcher m = p.matcher(request.getRequestURI());
		 if(m.matches()) return info;
		}
		return null;
	}
}

呵呵,这样我们的服务器就从web服务器进化到servlet容器了:》
不过我贴出来的代码只是比较关键的类,至于数据结构,有经验的程序员应该很容易猜出来——全局有一个appRuntime类,里面包含了若干个projectInfo类(储存项目信息),projectInfo里储存了每个web项目中web.xml文件的信息,包括servlet与路径的对应关系等等。
好久不写东西了,语言和思维有一些僵硬,慢慢来吧,下一章打算贴一点http协议解析的东西。

你可能感兴趣的:(数据结构,tomcat,jsp,Web,servlet)