功能
用来加载某包下的所有类,比如使用了某些注解的所有类,比如所有的@Service,或者Controller类
实现类加载器的3个功能
- 获取类加载器
- 根据类名称加载类
- 获取指定包下的所有类
获取类加载器
获取类加载器的实现非常简单,简单到只需要一句话,获取当前线程的ClassLoader即可。
/**
* 获取类加载器
*
* @return
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
根据类名称加载类
根据类名称加载类,这个方法是利用Class.forName实现的。相当于在Class.forName修饰了一层。实现如下:
/**
* 加载类
*
* @param className 类名
* @param isInitialized 此处的是否初始化标志指的是是否执行类的静态代码块
* @return
*/
public static Class<?> loadClass(String className, boolean isInitialized) {
Class<?> cls;
try {
cls = Class.forName(className, isInitialized, getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return cls;
}
一个值得注意的点是: 参数 boolean isInitialized表达的标志位指的是是否执行类的静态代码块,为了提高性能可以设置为false。
获取指定包下的所有类
获取指定包下的所有类是最麻烦的一件事情,因为包可以是file,也可是jar包。另外在包名和实际包所在的路径需要转换,比如需要把”.”转为”/” 把”%20”转换为” “;对于jar包的类,使用JarURLConnnection连接
/**
* 获取指定包下的所有类
*
* @param packageName
* @return
*/
public static Set<Class<?>> getClassSet(String packageName) {
Set<Class<?>> classSet = new HashSet<Class<?>>();
try {
/*获取资源,将包名替换为包路径*/
Enumeration<URL> urlEnumeration = getClassLoader().getResources(packageName.replaceAll(".", "/"));
while (urlEnumeration.hasMoreElements()) {
URL url = urlEnumeration.nextElement();
if (url != null) {
String protocol = url.getProtocol();
/*file方式处理*/
if (protocol.equals("file")) {
/*%20换成空格*/
String packagePath = url.getPath().replaceAll("%20", " ");
addClass(classSet, packagePath, packageName);
} else if (protocol.equals("jar")) { /*jar包处理*/
/*打开连接*/
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
if (jarURLConnection != null) {
JarFile jarFile = jarURLConnection.getJarFile();
if (jarFile != null) {
/*遍历元素*/
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
/*以class结尾*/
if (jarEntry.getName().endsWith(".class")) {
/*获取类名,并将"/"转换为"."*/
String className = jarEntry.getName().substring(0, jarEntry.getName().lastIndexOf(".")).replaceAll("/", ".");
addClassAction(classSet, className);
}
}
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classSet;
}
/**
* 内部调用loadClass,并将Class添加到Set中
*
* @param classSet
* @param className
*/
private static void addClassAction(Set<Class<?>> classSet, String className) {
Class<?> cls = loadClass(className, false);
classSet.add(cls);
}
/**
* @param classSet
* @param packagePath
* @param packageName
*/
private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
File[] files = new File(packagePath).listFiles(new FileFilter() {
/*将所有的是class文件和目录文件返回*/
public boolean accept(File file) {
return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
}
});
for (File file : files) {
String fileName = file.getName();
/*文件方式处理*/
if (file.isFile()) {
String className = fileName.substring(0, fileName.lastIndexOf("."));
if (packageName != null) {
className = packageName + "." + className;
}
addClassAction(classSet, className);
} else { /*文件夹方式处理*/
String subPackagePath = fileName;
if (packagePath != null) {
/*子目录*/
subPackagePath = packagePath + "/" + subPackagePath;
}
String subPackageName = fileName;
if (packageName != null) {
/*子包名*/
subPackageName = packageName + "." + subPackageName;
}
/*递归处理*/
addClass(classSet, subPackagePath, subPackageName);
}
}
}
函数注释写得很清楚,可以细细琢磨。
一个值得注意的细节,关于匿名内部类,如果要访问外部类的变量,则需要再外部变量加final修饰 如下所示:
原因是执行匿名内部类的时候,形成的函数的栈帧无法访问到外部类栈帧的局部变量,所有把外部变量final化则可以访问到这个变量,具体可以参考其他文章。只是突然想到这个知识点并稍微说说而已。
*/
private static void addClass(Set<Class<?>> classSet, String packagePath, final String packageName) {
File[] files = new File(packagePath).listFiles(new FileFilter() {
/*将所有的是class文件和目录文件返回,一个细节,如果匿名内部类里要使用 packageName 则在addClass参数中要加入修饰词 final
* 即 final String packageName
public boolean accept(File file) {
return (packageName != null && file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
}
});