上一篇我们写了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实例的容器。