【从零写javaweb框架】(三)开发一个类加载器
程序员文章站
2022-06-13 21:02:18
...
上一篇我们写了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实例的容器。
上一篇: c#基础 接口
下一篇: Java8——抽象类+接口