前两章主要贴出了服务器通讯核心和资源执行器的实现,这两个东西其实都不难,只要花些时间,我相信大部分的程序员都能做出来。两章结束后我们的服务器应该已经能够实现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协议解析的东西。