一:包扫描简述
1.为什么要使用包扫描?
包扫描主要用于找到带有注解的类。我们知道注解再Java里面用的很频繁,可以配置XML文件或者注解,然后通过反射机制执行想要执行的方法。
2.包扫描介绍
- 我们可以通过用户提供的包名或者类名,扫描该包地下的所有类或者该类所在的包。
- 通过包扫描,我们可以得到该包下我们所要找的类(例如:带有注解的类或者接口或者枚举类型等)这里主要用于扫描带有注解的类。
- 因为找到该类,我们可以通过注解信息得到该类里面带有注解的成员或方法的信息,然后通过反射机制执行。
3.包扫描分类
我们知道再Java里面一个工程下可能由许多包或者Jar包,有时我们所需要的类可能再Jar包里,因此包扫描的时候我们都需要扫描。
但是普通包扫描和Jar包扫描的处理方法又不同,因此我们需要采用分而治之的思想,分开处理。
二:包扫描过程
1.普通包扫描
代码示例
这里根据包名,经过一系列处理(具体看下面详细过程)得到url协议名称,然后判断是Jar包还是普通包,如果是普通包就调用packetScanner(String curFile, String packName)这个方法,进行普通包扫描
private void packetScanner(String curFile, String packName) {
if (!curFile.isDirectory()) {
return;
}
File[] files = curFile.listFiles();
for(File file : files) {
if (file.isFile() && file.getName().endsWith(".class")) {
String fileName = file.getName().replace(".class", "");
String className = packName + "." + fileName;
try {
Class<?> klass = Class.forName(className);
dealClass(klass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}else if(file.isDirectory()) {
packetScanner(file, packName + "." + file.getName());
}
}
}
}
public void scanPacket(String packetName) {
String packetPath = packetName.replace(".", "/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Enumeration<URL> resources = classLoader.getResources(packetPath);
while(resources.hasMoreElements()) {
URL url = resources.nextElement();
if (url.getProtocol().equals("jar")) {
scanJarPacket(url);
} else {
File file = new File(url.toURI());
if (!file.exists()) {
continue;
}
packetScanner(file, packetName);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
2.Jar包扫描
这里根据包名,经过一系列处理(具体看下面详细过程)得到url协议名称,然后判断是Jar包还是普通包,如果是Jar包就调用private void scanJarPacket(URL url) 这个方法,进行Jar包扫描
代码展示
private void scanJarPacket(URL url) throws IOException {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
Enumeration<JarEntry> jarEntries = jarFile.entries();
while(jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String jarName = jarEntry.getName();
if (jarEntry.isDirectory() || !jarName.endsWith(".class")) {
continue;
}
String className = jarName.replace(".class", "").replaceAll("/", ".");
try {
Class<?> klass = Class.forName(className);
if (klass.isAnnotation()
|| klass.isEnum()
|| klass.isInterface()
|| klass.isPrimitive()) {
continue;
}
dealClass(klass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public void scanPacket(String packetName) {
String packetPath = packetName.replace(".", "/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Enumeration<URL> resources = classLoader.getResources(packetPath);
while(resources.hasMoreElements()) {
URL url = resources.nextElement();
if (url.getProtocol().equals("jar")) {
scanJarPacket(url);
} else {
File file = new File(url.toURI());
if (!file.exists()) {
continue;
}
packetScanner(file, packetName);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
3.包扫描代码
下面是包扫描的全部代码
代码展示
package com.mec.packetScan.core;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public abstract class PacketScan {
public PacketScan() {
}
//这是一个抽象方法,由外面实现
public abstract void dealClass(Class<?> klass);
private void packetScanner(File curFile, String packName) {
//如果不是目录就结束方法的调用
if (!curFile.isDirectory()) {
return;
}
//该方法返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件
File[] files = curFile.listFiles();
for(File file : files) {
if (file.isFile() && file.getName().endsWith(".class")) {
String fileName = file.getName().replace(".class", "");
//去掉“.class”后就是文件名,路径名加文件名就是类名
String className = packName + "." + fileName;
try {
//根据类名称得到类类型
Class<?> klass = Class.forName(className);
dealClass(klass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}else if(file.isDirectory()) {
//如果该文件是目录就再一次调用此方法,将路径名加文件名(下一次路径)传过去
//一直这样递归调用,直到是文件为止
packetScanner(file, packName + "." + file.getName());
}
}
}
private void scanJarPacket(URL url) throws IOException {
/**
* 由API查得:
* 该方法返回一个URLConnection实例,表示与URL引用的远程对象的URL
* 如果对于URL的协议(如HTTP或JAR),则存在一个属于以下软件包或其子包之一的公共专用URLConnection子类:
* java.long, java.io, java.util, java.net,返回的连接将是该子类
*/
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
/**
* 由API查得:
* 返回此连接的JAR文件
* 如果连接是与JAR文件的条目的连接,则返回JAR文件对象
*/
JarFile jarFile = jarURLConnection.getJarFile();
/**
* 由API查得:
* 返回zip文件条目的枚举
*/
Enumeration<JarEntry> jarEntries = jarFile.entries();
while(jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String jarName = jarEntry.getName();
//如果它是一个目录或者不是“.class”文件,就跳过
if (jarEntry.isDirectory() || !jarName.endsWith(".class")) {
continue;
}
String className = jarName.replace(".class", "").replaceAll("/", ".");
try {
Class<?> klass = Class.forName(className);
//如果这个类是注解或者枚举或者接口或者八大基本类型就跳过
if (klass.isAnnotation()
|| klass.isEnum()
|| klass.isInterface()
|| klass.isPrimitive()) {
continue;
}
//调用抽象类
dealClass(klass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//对包扫描方法重载,可以扫描多个包
public void scanPacket(String[] packetNamees) {
for(String packetName : packetNamees) {
scanPacket(packetName);
}
}
//对包扫描方法重载,提供多个类名,可以扫描该类所在的包
public void scanPacket(Class<?>[] klasses) {
for(Class<?> klass : klasses) {
scanPacket(klass);
}
}
public void scanPacket(Class<?> klass) {
String path = klass.getPackage().getName();
scanPacket(path);
}
public void scanPacket(String packetName) {
/**
* 由API查得:
* 在windows下是\,但在编程语言\是转义字符的起时字符,所以路径中的\通常需要使用\\
* 如果是/就不需要转义了
* “\”一般表示本地目录的,比如电脑里的目录。
* “/”主要表示远程电脑或者网络上的。
* 这里将Java里的传过来的包名里的“.”转换为“/”,供下面的类加载器调用找到类加载器的资源
*/
String packetPath = packetName.replace(".", "/");
/**
* 由API查得:
* 返回此Thread的上下文ClassLoader,上下文ClassLoader是由线程的创建者提供,
* 以便在加载类和资源时在此线程中运行的代码使用。如果不是set,默认是父线程的ClassLoader上下文。
* 原始线程的上下文ClassLoader通常设置为用于加载应用程序的类加载器。
*/
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
/**
* 由API查得:
* 找到具有给定名称的资源,资源可以通过独立于代码位置的方式由类代码访问的一些资源(图像、音频、文本等)
* 资源的名称是标识资源的“/”分割路径名。
* 此方法首先将搜索父类加载器的资源;如果父级是null,则会搜索内置到虚拟机的类加载器的路径
*/
Enumeration<URL> resources = classLoader.getResources(packetPath);
while(resources.hasMoreElements()) {
//这里得到的是该类的包路径
URL url = resources.nextElement();
//url.getProtocol()获取此url协议的名称,如果是文件输出file;如果是jar包,输出jar
if (url.getProtocol().equals("jar")) {
//如果是“jar”包就调用“jar”包扫描的方法
scanJarPacket(url);
} else {
//这里实例化一个文件类型的对象,需要uri类型的参数,因此需要将url转换为uri
//这里得到的file对象是windows系统下的包路径
File file = new File(url.toURI());
//如果这个文件不存在,就继续查找
if (!file.exists()) {
continue;
}
//如果是普通包就调用普通包扫描的方法
packetScanner(file, packetName);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
包扫描的调用类
这里包扫描类是一个抽象类,我们实例化包扫描类的时候,需要首先先完成抽象方法
这里只是扫描到类,将其输出
package com.mec.packetScan.test;
import com.mec.packetScan.core.PacketScan;
public class Test {
public static void main(String[] args) {
PacketScan packetScan = new PacketScan() {
@Override
public void dealClass(Class<?> klass) {
System.out.println(klass);
}
};
packetScan.scanPacket("com.mec");
}
}