【从零写javaweb框架】(三)开发一个类加载器

上一篇我们写了4个常用工具类,1个维护常量的类,并且通过依赖它们用ConfigHelper实现了配置文件的读取,上一篇链接:【从零写javaweb框架】(二)定义和加载配置项,现在需要开发一个类加载器,用来加载包名下的所有类。


现在写一个ClassUtil类,用于提供与类操作相关的方法(本篇文章都会在在框架项目中进行):

package org.smart4j.framework.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.net.www.protocol.jar.JarURLConnection;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * desc : 类操作工具类
 * Created by Lon on 2018/1/22.
 */
public final class ClassUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);

    /**
     * 获取类加载器
     * 获取当前线程中的ClassLoader即可
     */
    public static ClassLoader getClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 加载类
     * 加载类需要提供类名与是否初始化标志,初始化是指是否执行类的静态代码块。
     * 为了提高加载类的性能,可将isInitialized参数设为false
     *
     * 我这里特意百度了一下,
     * 之前一直没有用过带3个参数的forName(String name, boolean initialize, ClassLoader loader)方法:
     * 第一个参数是类的全名;
     * 第二个参数是是否初始化类;
     * 第三个参数是加载时使用的类加载器;
     * 如果使用的方法是Class.forName(String name)时,它等价于initialize的值为true,loader的值为当前类的类加载器
     */
    public static Class<?> loadClass(String className, boolean isInitialized){
        Class<?> cls;
        try {
            cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
            LOGGER.error("load class failure", e);
            throw new RuntimeException(e);
        }
        return cls;
    }

    /**
     * 获取指定包名下的所有类
     */
    public static Set<Class<?>> getClassSet(String packageName){
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        try {
            //得到包名下的资源URL枚举
            //Enumeration枚举接口,用法和Iterator相似,提供了遍历Vector和HashTable类型集合元素的功能,不支持元素的移除操作
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
            while (urls.hasMoreElements()){
                URL url = urls.nextElement();
                if (url != null){
                    String protocol = url.getProtocol();
                    if ("file".equals(protocol)){
                        //百度了一下,获取文件路径时,里面的路径空格会被"%20"代替,因此要重新替换成空格
                        String packagePath = url.getPath().replaceAll("%20", " ");
                        addClass(classSet, packagePath, packageName);
                    } else if ("jar".equals(protocol)){
                        JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();
                        if (jarURLConnection != null){
                            JarFile jarFile = jarURLConnection.getJarFile();
                            if (jarFile != null){
                                Enumeration<JarEntry> jarEntrys = jarFile.entries();
                                while (jarEntrys.hasMoreElements()){
                                    JarEntry jarEntry = jarEntrys.nextElement();
                                    String jarEntryName = jarEntry.getName();
                                    if (jarEntryName.endsWith(".class")){
                                        String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/",".");
                                        doAddClass(classSet, className);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("get class set failure", e);
            throw new RuntimeException(e);
        }
        return classSet;
    }

    /**
     * 开始加载类
     */
    private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName){
        //把该包路径下的全部.class文件与文件夹加入到files数组里
        File[] files = new File(packagePath).listFiles(new FileFilter() {
            public boolean accept(File file) {
                return (file.isFile() && file.getName().endsWith(".class") || file.isDirectory());
            }
        });
        //遍历文件数组
        for (File file : files){
            String fileName = file.getName();
            //如果是.class文件,则直接加载它
            if (file.isFile()){
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (StringUtil.isNotEmpty(packageName)){
                    className = packageName + "." + className;
                }
                doAddClass(classSet, className);
            }
            //如果是文件夹,则进行递归操作
            else {
                String subPackagePath = fileName;
                if (StringUtil.isNotEmpty(packagePath)){
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (StringUtil.isNotEmpty(packageName)){
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(classSet, subPackagePath, subPackageName);
            }
        }
    }

    /**
     * 加载类(但不初始化)
     * 并且把类加入到Set中
     */
    private static void doAddClass(Set<Class<?>> classSet, String className){
        Class<?> cls = loadClass(className, false);
        classSet.add(cls);
    }

}

现在加载类的功能完成了,接下来要做的是定义注解,让自定义的类加载器可以识别到要加载的类,就像Spring那样有Controller/Service/Component/Autowired等一系列注解来实现依赖注入功能(当然Spring也可以通过xml配置文件来实现依赖注入,但我们不实现这种)


控制器注解:

package org.smart4j.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * desc : 控制器注解
 * Created by Lon on 2018/1/23.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}

控制器里的方法注解:

package org.smart4j.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * desc : 控制器里的Action方法注解
 * Created by Lon on 2018/1/23.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {

    /**
     * 请求类型与路径
     */
    String value();

}

服务类注解:

package org.smart4j.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * desc : 服务类注解
 * Created by Lon on 2018/1/23.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}

依赖注入注解:

package org.smart4j.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * desc : 依赖注入注解
 * Created by Lon on 2018/1/23.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}

做到这里,我们已经有能力根据上面的标志注解来加载类了,下一步,我们要做一个类似于Spring这样可以管理Bean类(不是实例)的容器类。

新建一个ClassHelper类:

package org.smart4j.framework.helper;

import org.smart4j.framework.annotation.Controller;
import org.smart4j.framework.annotation.Service;
import org.smart4j.framework.util.ClassUtil;

import java.util.HashSet;
import java.util.Set;

/**
 * desc : 类操作助手类
 * Created by Lon on 2018/1/23.
 */
public final class ClassHelper {

    /**
     * 定义类集合(用于存放所加载的类)
     * 我个人理解成Spring里的Bean容器
     */
    private static final Set<Class<?>> CLASS_SET;

    static {
        String basePackage = ConfigHelper.getAppBasePackage();
        CLASS_SET = ClassUtil.getClassSet(basePackage);
    }

    /**
     * 获取应用包名下的所有类
     * 个人理解是获取当前已经被加载的所有类
     */
    public static Set<Class<?>> getClassSet(){
        return CLASS_SET;
    }

    /**
     * 获取应用包下所有Service类
     */
    public static Set<Class<?>> getServiceClassSet(){
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET){
            if (cls.isAnnotationPresent(Service.class)){
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 获取应用包下所有Controller类
     */
    public static Set<Class<?>> getControllerClassSet(){
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET){
            if (cls.isAnnotationPresent(Controller.class)){
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 获取应用包下所有Bean类(包括Service/Controller)
     */
    public static Set<Class<?>> getBeanClassSet(){
        Set<Class<?>> beanClassSet = new HashSet<Class<?>>();
        beanClassSet.addAll(getServiceClassSet());
        beanClassSet.addAll(getControllerClassSet());
        return beanClassSet;
    }

}

这样我们就随时随地有能力去拿到已经加载的类了。

现在看看完成这章后我们的框架项目结构:



总结:

在这章里,我们写了ClassUtil类,用于提供与类操作相关的方法,然后又写了Controller/Service注解来标识框架要加载的类,Action用于标识Controller里的方法,Inject用于实现依赖注入,最后再写了一个ClassHelper来实行类的加载和存取。但是想要实现真正的依赖注入的话,我们还差几步:实例化类的对象和实例对象的管理,下一篇将讲怎么样实现像Spring那样Bean实例的容器。





©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页