创建一个简单的web服务器(二):使用自定义的类加载器来替换URLClassLoader

本文介绍了一种使用自定义类加载器替代URLClassLoader的方法,该方法在处理Servlet请求时更具灵活性。通过创建FileSystemClassLoader,可以更精细地控制类文件的加载过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一章中我们加载Servlet用的是URLClassLoader,在这一章中我们使用自定义的类加载器来替换URLClassLoader。关于类加载器的文章请参考这里:深入探讨 Java 类加载器。这篇文章写得很棒。

具体的代码如下:

package com.zkn.imitate.tomcat.secondchapter.first;

import com.zkn.imitate.tomcat.secondchapter.Request;
import com.zkn.imitate.tomcat.secondchapter.Response;
import com.zkn.imitate.tomcat.secondchapter.StaticResourceProcessor;
import com.zkn.imitate.tomcat.utils.Constants;
import com.zkn.imitate.tomcat.utils.StringUtil;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by wb-zhangkenan on 2016/12/29.
 */
public class HttpServer02 {

    public static void main(String[] args){

        await();
    }

    private static void await() {

        ServerSocket serverSocket = null;
        try {
            boolean shutDown = false;
            serverSocket = new ServerSocket(8004,1, InetAddress.getByName("127.0.0.1"));
            while (!shutDown){
                Socket socket = serverSocket.accept();
                Request request = new Request(socket.getInputStream());
                request.parseRequest();
                Response response = new Response(socket.getOutputStream());
                String uri = request.getUri();
                if(uri !=null && uri.startsWith("/favicon.ico")){

                }else if(!StringUtil.isEmpty(uri) && uri.startsWith("/static/")){
                    StaticResourceProcessor resouce = new StaticResourceProcessor();
                    resouce.process(request,response);
                }else{
                    ServletProcessor01 servletProcessor = new ServletProcessor01();
                    servletProcessor.process(request,response);
                }
                socket.close();
                shutDown = Constants.SHUT_DOWN.equals(request.getUri());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class ServletProcessor01 {

    public void process(Request request, Response response) {

        String str = request.getUri();
        if(!StringUtil.isEmpty(str) && str.lastIndexOf("/") >= 0){
            str = str.substring(str.lastIndexOf("/")+1);
        }else {
            return;
        }
        FileSystemClassLoader fileClassLoader =
                new FileSystemClassLoader(Constants.WEB_ROOT);
        try {
            Class clazz = fileClassLoader.findClass(str);
            Servlet servlet = (Servlet) clazz.newInstance();
            servlet.service(request,response);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
自定义的类加载器:

package com.zkn.imitate.tomcat.utils;

import java.io.*;
import java.util.Arrays;

/**
 * Created by wb-zhangkenan on 2017/1/3.
 */
public class FileSystemClassLoader extends ClassLoader{
    /**
     * 应用根路径
     */
    private String rootDir;

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    public FileSystemClassLoader() {
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes = getByteData(name);
        System.out.println(Arrays.toString(bytes));
        return defineClass(null,bytes,0,bytes.length);
    }

    private byte[] getByteData(String path){

        String className = getClassName(path);
        byte[] bytes = new byte[1024];
        try {
            FileInputStream fis = new FileInputStream(className);
            //ByteArrayOutputStream内部有一个数组用来保存读取的字节流
            ByteArrayOutputStream baiStream = new ByteArrayOutputStream();
            int flag = 0;
            while ((flag = fis.read(bytes)) != -1){
                /**
                 * 这里需要注意:写入的时候,写入的范围一定是0,flag。
                 * 原因是:有可能读取的bytes不够1024个字节,这个时候如果不写入读取范围的话,
                 *       则会把bytes中存留的上次读取的数据也写入到ByteArrayOutputStream中。
                 *       这样在defineClass的时候会出现异常。
                 *       异常信息如下:
                 *Exception in thread "main" java.lang.ClassFormatError:
                 *      Extra bytes at the end of class file FirstServlet
                 */
                baiStream.write(bytes,0,flag);
            }
            return baiStream.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }

    private String getClassName(String className){

        if(StringUtil.isEmpty(className)){
             throw new RuntimeException("Class名称不能为空");
        }
        return rootDir+ File.separatorChar+className.replace('.',File.separatorChar)+".class";
    }

}
TomCat中的应用加载器是WebappLoader。WebappLoader创建一个org.apache.catalina.loader.WebappClassLoader类的实例作为它的类加载器。有时间的话我会写一个关于WebAppLoader的一个分析。


一个应用程序中同时加载两个版本的OJDBC驱动是比较困难的,因为Java类加载器默认使用委派模型,即当一个类需要被加载时,它首先会请求其父类加载器加载该类,如果父类加载器无法加载该类,则由当前类加载器尝试加载该类。 为了解决这个问题,我们可以使用自定义类加载器来加载不同版本的OJDBC驱动。自定义类加载器可以绕过默认的委派模型,从而使得我们可以在同一个应用程序中加载不同版本的类。 下面是一个简单的例子,演示如何使用自定义类加载器加载两个版本的OJDBC驱动: ```java import java.net.URL; import java.net.URLClassLoader; public class CustomClassLoader extends URLClassLoader { public CustomClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (name.startsWith("oracle.jdbc.")) { String version = System.getProperty("oracle.jdbc.version"); if (version != null && version.equals("11g")) { return loadClassFromVersion(name, "11g"); } else { return loadClassFromVersion(name, "12c"); } } return super.loadClass(name); } private Class<?> loadClassFromVersion(String name, String version) throws ClassNotFoundException { String className = name.replace(".", "/") + ".class"; URL url = getResource(className.replace(version, "common")); if (url == null) { throw new ClassNotFoundException(name); } byte[] bytes = null; try { bytes = IOUtils.toByteArray(url.openStream()); } catch (IOException e) { throw new ClassNotFoundException(name, e); } return defineClass(name, bytes, 0, bytes.length); } } ``` 在这个自定义类加载器中,我们重写了loadClass方法,并根据系统属性oracle.jdbc.version的值来判断应该加载哪个版本的OJDBC驱动。 如果oracle.jdbc.version的值为11g,则加载11g版本的驱动,否则加载12c版本的驱动。 在loadClass方法中,我们首先判断要加载的类是否以oracle.jdbc.开头,如果是,则调用loadClassFromVersion方法加载对应版本的类。 在loadClassFromVersion方法中,我们首先使用getResource方法获取该类对应的URL,然后读取该URL对应的字节码,并使用defineClass方法将该类加载到内存中。 最后,我们在应用程序中使用自定义类加载器来加载OJDBC驱动: ```java URL[] urls = new URL[] { new URL("file:///path/to/ojdbc11.jar"), new URL("file:///path/to/ojdbc12.jar") }; ClassLoader parent = ClassLoader.getSystemClassLoader(); CustomClassLoader loader = new CustomClassLoader(urls, parent); System.setProperty("oracle.jdbc.version", "11g"); // 设置oracle.jdbc.version属性为11g Class<?> driverClass = loader.loadClass("oracle.jdbc.driver.OracleDriver"); Driver driver = (Driver) driverClass.newInstance(); ``` 在这个例子中,我们首先创建一个CustomClassLoader对象,并将ojdbc11.jar和ojdbc12.jar的URL作为参数传入。 然后,我们设置oracle.jdbc.version属性为11g,并使用CustomClassLoader加载对应版本的OracleDriver类,并创建该类的实例。 这样,我们就可以在同一个应用程序中同时使用不同版本的OJDBC驱动了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值