เป็นไปได้หรือไม่ที่จะหาคลาสหรืออินเตอร์เฟสทั้งหมดในแพ็คเกจที่กำหนด? (ดูอย่างรวดเร็วเช่นPackage
มันจะดูเหมือนไม่.)
เป็นไปได้หรือไม่ที่จะหาคลาสหรืออินเตอร์เฟสทั้งหมดในแพ็คเกจที่กำหนด? (ดูอย่างรวดเร็วเช่นPackage
มันจะดูเหมือนไม่.)
คำตอบ:
เนื่องจากลักษณะของคลาสโหลดเดอร์แบบไดนามิกจึงเป็นไปไม่ได้ ตัวโหลดคลาสไม่จำเป็นต้องแจ้งให้ VM ทราบว่าคลาสใดที่มันสามารถให้ได้แทนพวกมันเป็นเพียงแค่ส่งคำร้องขอคลาสและต้องส่งคืนคลาสหรือส่งข้อยกเว้น
อย่างไรก็ตามถ้าคุณเขียนคลาสโฮเดอร์ของคุณเองหรือตรวจสอบ classpaths และเป็นไหก็เป็นไปได้ที่จะหาข้อมูลนี้ นี่จะเป็นการดำเนินการผ่านระบบไฟล์ แต่ไม่ใช่การสะท้อนกลับ อาจมีห้องสมุดที่สามารถช่วยคุณทำสิ่งนี้ได้
หากมีคลาสที่สร้างขึ้นหรือส่งจากระยะไกลคุณจะไม่สามารถค้นพบคลาสเหล่านั้นได้
วิธีการปกติคือแทนที่จะลงทะเบียนเรียนที่คุณต้องการเข้าถึงในไฟล์หรืออ้างอิงในคลาสอื่นแทน หรือเพียงแค่ใช้การประชุมเมื่อมันมาถึงการตั้งชื่อ
ภาคผนวก: ไลบรารี Reflectionsจะอนุญาตให้คุณค้นหาคลาสใน classpath ปัจจุบัน สามารถใช้รับคลาสทั้งหมดในแพ็คเกจ:
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends Object>> allClasses =
reflections.getSubTypesOf(Object.class);
คุณน่าจะลองดูที่ไลบรารี่โอเพ่นซอร์ส ด้วยคุณสามารถบรรลุสิ่งที่คุณต้องการ
ขั้นแรกให้ตั้งค่าดัชนีการสะท้อนกลับ (มันค่อนข้างยุ่งเนื่องจากการค้นหาคลาสทั้งหมดจะถูกปิดใช้งานตามค่าเริ่มต้น):
List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));
จากนั้นคุณสามารถค้นหาวัตถุทั้งหมดในแพ็คเกจที่กำหนด:
Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
.addUrls(ClasspathHelper.forJavaClassPath())
แทนที่จะแก้ไขปัญหาข้างต้นให้ฉัน รหัสน้อยเช่นกัน!
Google Guava 14 มีคลาสใหม่ClassPath
พร้อมสามวิธีในการสแกนหาคลาสระดับบนสุด:
getTopLevelClasses()
getTopLevelClasses(String packageName)
getTopLevelClassesRecursive(String packageName)
ดูClassPath
javadocsสำหรับข้อมูลเพิ่มเติม
ClassPath
มีการติดแท็กด้วย@Beta
จึงไม่อาจเป็นความคิดที่ดีสำหรับบางคน ...
คุณสามารถใช้วิธีการนี้1ClassLoader
ที่ใช้
/**
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
*
* @param packageName The base package
* @return The classes
* @throws ClassNotFoundException
* @throws IOException
*/
private static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<File>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes.toArray(new Class[classes.size()]);
}
/**
* Recursive method used to find all classes in a given directory and subdirs.
*
* @param directory The base directory
* @param packageName The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
__________
1วิธีนี้เริ่มต้นจากhttp://snippets.dzone.com/posts/show/4831ซึ่งเก็บถาวรโดย Internet Archive ซึ่งเชื่อมโยงกับตอนนี้ ข้อมูลยังมีบริการที่https://dzone.com/articles/get-all-classes-within-package
%20
แต่ตัวnew File()
สร้างถือว่าเป็นตัวอักษรเปอร์เซ็นต์ที่ลงชื่อสองศูนย์ ฉันแก้ไขมันโดยการเปลี่ยนdirs.add(...)
บรรทัดเป็น: dirs.add(new File(resource.toURI()));
นี่ก็หมายความว่าฉันต้องเพิ่มURISyntaxException
ไปยังส่วนการโยนของgetClasses
ตัวอย่างนี้ใช้สำหรับ Spring 4 แต่คุณสามารถค้นหา classpath scanner ในเวอร์ชันก่อนหน้าได้เช่นกัน
// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));
// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");
// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
Class<?> clazz = Class.forName(bean.getBeanClassName());
// ... do your magic with the class ...
}
หมายเหตุ:ในเวอร์ชัน 14 API ยังคงถูกทำเครื่องหมายเป็น@Betaดังนั้นโปรดระวังในรหัสการผลิต
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
if (info.getName().startsWith("my.package.")) {
final Class<?> clazz = info.load();
// do something with your clazz
}
}
ClassPath
คลาสใน Guava นั้นถูกทำเครื่องหมายด้วย@Beta
: "APIs ที่มีคำอธิบายประกอบ @Beta ที่ระดับหรือวิธีการอาจมีการเปลี่ยนแปลงพวกเขาสามารถแก้ไขได้ในทางใดทางหนึ่งหรือแม้กระทั่งลบออกในรุ่นใหญ่ ๆ หากรหัสของคุณ เป็นไลบรารี่เอง (นั่นคือมันถูกใช้กับ CLASSPATH ของผู้ใช้ที่อยู่นอกการควบคุมของคุณเอง) คุณไม่ควรใช้เบต้า API เว้นแต่คุณจะทำการบรรจุใหม่ ... " code.google.com/p/guava-l ไลบรารี/
getAllClasses()
สามารถใช้วิธีการ
สวัสดี. ฉันมักจะมีปัญหาบางอย่างกับการแก้ปัญหาข้างต้น (และในเว็บไซต์อื่น ๆ )
ฉันในฐานะนักพัฒนากำลังเขียนโปรแกรมเสริมสำหรับ API API ป้องกันการใช้ไลบรารีภายนอกหรือเครื่องมือของบุคคลที่สาม การตั้งค่ายังประกอบด้วยการผสมผสานของรหัสในไฟล์ jar หรือ zip และไฟล์คลาสที่อยู่โดยตรงในบางไดเรกทอรี ดังนั้นรหัสของฉันจะต้องสามารถจัดการทุกการตั้งค่า หลังจากการวิจัยจำนวนมากฉันได้พบวิธีการที่จะทำงานอย่างน้อย 95% ของการตั้งค่าที่เป็นไปได้ทั้งหมด
รหัสต่อไปนี้นั้นเป็นวิธีการ overkill ที่สามารถใช้ได้เสมอ
รหัสนี้สแกนแพคเกจที่กำหนดสำหรับคลาสทั้งหมดที่รวมอยู่ในนั้น ClassLoader
มันจะทำงานสำหรับการเรียนทั้งหมดในปัจจุบัน
/**
* Private helper method
*
* @param directory
* The directory to start with
* @param pckgname
* The package name to search for. Will be needed for getting the
* Class object.
* @param classes
* if a file isn't loaded but still is in the directory
* @throws ClassNotFoundException
*/
private static void checkDirectory(File directory, String pckgname,
ArrayList<Class<?>> classes) throws ClassNotFoundException {
File tmpDirectory;
if (directory.exists() && directory.isDirectory()) {
final String[] files = directory.list();
for (final String file : files) {
if (file.endsWith(".class")) {
try {
classes.add(Class.forName(pckgname + '.'
+ file.substring(0, file.length() - 6)));
} catch (final NoClassDefFoundError e) {
// do nothing. this class hasn't been found by the
// loader, and we don't care.
}
} else if ((tmpDirectory = new File(directory, file))
.isDirectory()) {
checkDirectory(tmpDirectory, pckgname + "." + file, classes);
}
}
}
}
/**
* Private helper method.
*
* @param connection
* the connection to the jar
* @param pckgname
* the package name to search for
* @param classes
* the current ArrayList of all classes. This method will simply
* add new classes.
* @throws ClassNotFoundException
* if a file isn't loaded but still is in the jar file
* @throws IOException
* if it can't correctly read from the jar file.
*/
private static void checkJarFile(JarURLConnection connection,
String pckgname, ArrayList<Class<?>> classes)
throws ClassNotFoundException, IOException {
final JarFile jarFile = connection.getJarFile();
final Enumeration<JarEntry> entries = jarFile.entries();
String name;
for (JarEntry jarEntry = null; entries.hasMoreElements()
&& ((jarEntry = entries.nextElement()) != null);) {
name = jarEntry.getName();
if (name.contains(".class")) {
name = name.substring(0, name.length() - 6).replace('/', '.');
if (name.contains(pckgname)) {
classes.add(Class.forName(name));
}
}
}
}
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader
*
* @param pckgname
* the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException
* if something went wrong
*/
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
throws ClassNotFoundException {
final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
try {
final ClassLoader cld = Thread.currentThread()
.getContextClassLoader();
if (cld == null)
throw new ClassNotFoundException("Can't get class loader.");
final Enumeration<URL> resources = cld.getResources(pckgname
.replace('.', '/'));
URLConnection connection;
for (URL url = null; resources.hasMoreElements()
&& ((url = resources.nextElement()) != null);) {
try {
connection = url.openConnection();
if (connection instanceof JarURLConnection) {
checkJarFile((JarURLConnection) connection, pckgname,
classes);
} else if (connection instanceof FileURLConnection) {
try {
checkDirectory(
new File(URLDecoder.decode(url.getPath(),
"UTF-8")), pckgname, classes);
} catch (final UnsupportedEncodingException ex) {
throw new ClassNotFoundException(
pckgname
+ " does not appear to be a valid package (Unsupported encoding)",
ex);
}
} else
throw new ClassNotFoundException(pckgname + " ("
+ url.getPath()
+ ") does not appear to be a valid package");
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for "
+ pckgname, ioex);
}
}
} catch (final NullPointerException ex) {
throw new ClassNotFoundException(
pckgname
+ " does not appear to be a valid package (Null pointer exception)",
ex);
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for "
+ pckgname, ioex);
}
return classes;
}
วิธีการทั้งสามนี้ช่วยให้คุณสามารถค้นหาชั้นเรียนทั้งหมดในแพ็คเกจที่กำหนด
คุณใช้มันแบบนี้:
getClassesForPackage("package.your.classes.are.in");
ClassLoader
วิธีแรกที่ได้รับในปัจจุบัน จากนั้นจะดึงทรัพยากรทั้งหมดที่มีแพ็คเกจดังกล่าวและทำซ้ำของURL
s เหล่านี้ จากนั้นจะสร้างURLConnection
และกำหนดประเภทของ URl ที่เรามี มันสามารถเป็นไดเรคทอรี ( FileURLConnection
) หรือไดเร็กตอรี่ที่อยู่ในไฟล์ jar หรือ zip ( JarURLConnection
) ขึ้นอยู่กับประเภทของการเชื่อมต่อที่เรามีสองวิธีที่แตกต่างกันจะถูกเรียกว่า
FileURLConnection
ครั้งแรกที่ช่วยให้เห็นสิ่งที่เกิดขึ้นถ้ามันเป็น
มันตรวจสอบก่อนว่าไฟล์ที่ส่งผ่านมีอยู่และเป็นไดเรกทอรีหรือไม่ หากเป็นกรณีนี้จะตรวจสอบว่าเป็นไฟล์คลาสหรือไม่ หากดังนั้นวัตถุจะถูกสร้างขึ้นและนำมาใส่ในClass
ArrayList
ถ้ามันไม่ใช่ไฟล์คลาส แต่เป็นไดเรกทอรีเราก็ทำซ้ำมันและทำสิ่งเดียวกัน เคส / ไฟล์อื่น ๆ ทั้งหมดจะถูกละเว้น
ถ้าURLConnection
เป็นJarURLConnection
ตัวช่วยส่วนตัวอื่น ๆ จะถูกเรียก วิธีนี้วนซ้ำทุกรายการในไฟล์ zip / jar หากหนึ่งในรายการเป็นไฟล์เรียนและอยู่ภายในของแพคเกจที่วัตถุจะถูกสร้างขึ้นและเก็บไว้ในClass
ArrayList
หลังจากที่แยกวิเคราะห์ทรัพยากรทั้งหมดแล้ว (เมธอด main) จะส่งคืนการArrayList
บรรจุคลาสทั้งหมดในแพ็กเกจที่กำหนดซึ่งปัจจุบันClassLoader
รู้เกี่ยวกับ
หากกระบวนการล้มเหลว ณ จุดใด ๆClassNotFoundException
จะมีการโยนข้อมูลต่อไปโดยละเอียดเกี่ยวกับสาเหตุที่แน่นอน
sun.net.www.protocol.file.FileURLConnection
ซึ่งสร้างคำเตือนในเวลารวบรวม ("คำเตือน: sun.net.www.protocol.file.FileURLConnection เป็น API ที่เป็นกรรมสิทธิ์ของ Sun และอาจถูกลบออกในรุ่นอนาคต") มีทางเลือกอื่นในการใช้คลาสนั้นหรือไม่สามารถเตือนได้โดยใช้คำอธิบายประกอบ
if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }
มันเป็นสิ่งที่ฉันทำในรหัสของฉันเพื่อค้นหาคลาสที่มีคำอธิบายประกอบ JPA
โดยไม่ต้องใช้ไลบรารีเพิ่มเติม:
package test;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception{
List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
for(Class c:classes){
System.out.println("Class: "+c);
}
}
public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{
String dottedPackage = pack.replaceAll("[/]", ".");
List<Class> classes = new ArrayList<Class>();
URL upackage = cl.getResource(pack);
DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
String line = null;
while ((line = dis.readLine()) != null) {
if(line.endsWith(".class")) {
classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
}
}
return classes;
}
}
upackage
ก็คือnull
... :(
String pack = getPackage().getName();
นั้นคุณต้องเพิ่มpack = pack.replaceAll("[.]", "/");
ในตัวโหลดคลาสทั่วไปไม่อนุญาตให้สแกนผ่านคลาสทั้งหมดบน classpath แต่โดยปกติแล้ว class loader ที่ใช้เพียงอย่างเดียวคือ UrlClassLoader ซึ่งเราสามารถดึงรายชื่อของไดเรกทอรีและไฟล์ jar (ดูที่getURLs ) และเปิดมันทีละตัวเพื่อแสดงรายการคลาสที่มีอยู่ วิธีการนี้เรียกว่าการสแกนคลาสพา ธ ถูกนำไปใช้ในการสแกนและการสะท้อนกลับ
Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);
อีกวิธีหนึ่งคือการใช้Java Pluggable Annotation Processing APIเพื่อเขียนโพรเซสเซอร์หมายเหตุประกอบซึ่งจะรวบรวมคลาสหมายเหตุประกอบทั้งหมดในเวลารวบรวมและสร้างไฟล์ดัชนีสำหรับการใช้งานจริง กลไกนี้ถูกนำไปใช้ในไลบรารีClassIndex :
// package-info.java
@IndexSubclasses
package my.package;
// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
โปรดสังเกตว่าไม่จำเป็นต้องมีการตั้งค่าเพิ่มเติมเนื่องจากการสแกนเป็นไปโดยอัตโนมัติโดยสมบูรณ์ด้วยจาวาคอมไพเลอร์ Java โดยอัตโนมัติค้นพบโปรเซสเซอร์ที่พบใน classpath
กลไกที่แข็งแกร่งที่สุดสำหรับการแสดงคลาสทั้งหมดในแพ็กเกจที่กำหนดนั้นปัจจุบันคือClassGraphเนื่องจากมันจัดการกับกลไกข้อมูลจำเพาะ classpath ที่กว้างที่สุดเท่าที่จะเป็นไปได้รวมถึงระบบโมดูล JPMS ใหม่ (ฉันเป็นผู้เขียน)
List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
.enableClassInfo().scan()) {
classNames.addAll(scanResult.getAllClasses().getNames());
}
นี่คือวิธีที่ฉันทำ ฉันสแกนโฟลเดอร์ย่อยทั้งหมด (แพ็คเกจย่อย) และฉันไม่พยายามโหลดคลาสที่ไม่ระบุชื่อ:
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader, recursively, avoiding anonymous classes
*
* @param pckgname
* the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException
* if something went wrong
*/
private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
// This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
ArrayList<File> directories = new ArrayList<File>();
String packageToPath = pckgname.replace('.', '/');
try {
ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
// Ask for all resources for the packageToPath
Enumeration<URL> resources = cld.getResources(packageToPath);
while (resources.hasMoreElements()) {
directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
}
} catch (NullPointerException x) {
throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
} catch (UnsupportedEncodingException encex) {
throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
} catch (IOException ioex) {
throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
}
ArrayList<Class> classes = new ArrayList<Class>();
// For every directoryFile identified capture all the .class files
while (!directories.isEmpty()){
File directoryFile = directories.remove(0);
if (directoryFile.exists()) {
// Get the list of the files contained in the package
File[] files = directoryFile.listFiles();
for (File file : files) {
// we are only interested in .class files
if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
// removes the .class extension
int index = directoryFile.getPath().indexOf(packageToPath);
String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;
try {
String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);
classes.add(Class.forName(className));
} catch (NoClassDefFoundError e)
{
// do nothing. this class hasn't been found by the loader, and we don't care.
}
} else if (file.isDirectory()){ // If we got to a subdirectory
directories.add(new File(file.getPath()));
}
}
} else {
throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
}
}
return classes;
}
ฉันรวบรวมโปรเจ็กต์ github ง่ายๆที่แก้ปัญหานี้:
https://github.com/ddopson/java-class-enumerator
มันควรจะทำงานสำหรับ classpaths ที่ใช้ทั้งไฟล์และสำหรับไฟล์ jar
หากคุณเรียกใช้ 'ทำ' หลังจากตรวจสอบโครงการแล้วมันจะพิมพ์ออกมา:
Cleaning...
rm -rf build/
Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ .
jar cf build/ClassEnumerator.jar -C build/classes/ pro
Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class' => class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class' => class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg' => class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class' => class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class' => class 'test.TestClassEnumeration'
Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/' => class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF' => class 'null'
ClassDiscovery: JarEntry 'pro/' => class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/' => class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class' => class 'null'
ClassDiscovery: JarEntry 'test/' => class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class' => class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class' => class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/' => class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class' => class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class' => class 'test.TestClassEnumeration'
Tests Passed.
ใช่คุณสามารถใช้ API ได้เล็กน้อยนี่คือวิธีที่ฉันชอบทำประสบปัญหานี้ซึ่งฉันใช้แกนไฮเบอร์เนต & ต้องค้นหาคลาสที่มีหมายเหตุประกอบอยู่ด้วย
ทำให้คำอธิบายประกอบแบบกำหนดเองเหล่านี้ใช้ซึ่งคุณจะทำเครื่องหมายว่าคุณต้องการเลือกคลาสใด
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {
}
จากนั้นทำเครื่องหมายชั้นเรียนของคุณด้วยเช่น
@EntityToBeScanned
public MyClass{
}
ทำให้คลาสยูทิลิตี้นี้ซึ่งมีวิธีการดังต่อไปนี้
public class ClassScanner {
public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
Reflections reflections = new Reflections(".*");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
return annotated;
}
}
เรียกใช้เมธอด allFoundClassesAnnotatedWithEntityToBeScanned ()เพื่อรับชุดของคลาสที่พบ
คุณจะต้อง libs ที่ระบุด้านล่าง
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
คุณต้องค้นหารายการตัวโหลดคลาสทั้งหมดในคลาสพา ธ :
String pkg = "org/apache/commons/lang";
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
System.out.println(url.getFile());
File jar = new File(url.getFile());
// ....
}
หากรายการคือไดเรกทอรีให้ค้นหาในไดเรกทอรีย่อยที่ถูกต้อง:
if (jar.isDirectory()) {
File subdir = new File(jar, pkg);
if (!subdir.exists())
continue;
File[] files = subdir.listFiles();
for (File file : files) {
if (!file.isFile())
continue;
if (file.getName().endsWith(".class"))
System.out.println("Found class: "
+ file.getName().substring(0,
file.getName().length() - 6));
}
}
หากรายการนั้นเป็นไฟล์และเป็น jar ให้ตรวจสอบรายการ ZIP ของมัน:
else {
// try to open as ZIP
try {
ZipFile zip = new ZipFile(jar);
for (Enumeration<? extends ZipEntry> entries = zip
.entries(); entries.hasMoreElements();) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (!name.startsWith(pkg))
continue;
name = name.substring(pkg.length() + 1);
if (name.indexOf('/') < 0 && name.endsWith(".class"))
System.out.println("Found class: "
+ name.substring(0, name.length() - 6));
}
} catch (ZipException e) {
System.out.println("Not a ZIP: " + e.getMessage());
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
ตอนนี้เมื่อคุณมีชื่อคลาสทั้งหมดภายในแพ็คเกจคุณสามารถลองโหลดด้วยการสะท้อนและวิเคราะห์ว่าเป็นคลาสหรืออินเทอร์เฟซเป็นต้น
ฉันพยายามใช้ห้องสมุดรีเฟลคชั่น แต่มีปัญหาในการใช้งานและมีไหจำนวนมากเกินไปที่ฉันควรรวมไว้เพื่อให้ได้คลาสในแพ็คเกจเท่านั้น
ฉันจะโพสต์วิธีแก้ปัญหาที่ฉันพบในคำถามที่ซ้ำกันนี้: จะเรียกชื่อคลาสทั้งหมดในแพ็คเกจได้อย่างไร
คำตอบที่ถูกเขียนโดย sp00m ; ฉันได้เพิ่มการแก้ไขบางอย่างเพื่อให้ทำงานได้:
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
public final class ClassFinder {
private final static char DOT = '.';
private final static char SLASH = '/';
private final static String CLASS_SUFFIX = ".class";
private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";
public final static List<Class<?>> find(final String scannedPackage) {
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final String scannedPath = scannedPackage.replace(DOT, SLASH);
final Enumeration<URL> resources;
try {
resources = classLoader.getResources(scannedPath);
} catch (IOException e) {
throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
}
final List<Class<?>> classes = new LinkedList<Class<?>>();
while (resources.hasMoreElements()) {
final File file = new File(resources.nextElement().getFile());
classes.addAll(find(file, scannedPackage));
}
return classes;
}
private final static List<Class<?>> find(final File file, final String scannedPackage) {
final List<Class<?>> classes = new LinkedList<Class<?>>();
if (file.isDirectory()) {
for (File nestedFile : file.listFiles()) {
classes.addAll(find(nestedFile, scannedPackage));
}
//File names with the $1, $2 holds the anonymous inner classes, we are not interested on them.
} else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {
final int beginIndex = 0;
final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
final String className = file.getName().substring(beginIndex, endIndex);
try {
final String resource = scannedPackage + DOT + className;
classes.add(Class.forName(resource));
} catch (ClassNotFoundException ignore) {
}
}
return classes;
}
}
หากต้องการใช้มันก็แค่เรียกวิธีการค้นหาตามที่ sp00n พูดถึงในตัวอย่างนี้: ฉันได้เพิ่มการสร้างอินสแตนซ์ของคลาสถ้าจำเป็น
List<Class<?>> classes = ClassFinder.find("com.package");
ExcelReporting excelReporting;
for (Class<?> aClass : classes) {
Constructor constructor = aClass.getConstructor();
//Create an object of the class type
constructor.newInstance();
//...
}
ฉันเพิ่งเขียนคลาส util รวมถึงวิธีทดสอบคุณสามารถตรวจสอบได้ ~
IteratePackageUtil.java:
package eric.j2se.reflect;
import java.util.Set;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
/**
* an util to iterate class in a package,
*
* @author eric
* @date Dec 10, 2013 12:36:46 AM
*/
public class IteratePackageUtil {
/**
* <p>
* Get set of all class in a specified package recursively. this only support lib
* </p>
* <p>
* class of sub package will be included, inner class will be included,
* </p>
* <p>
* could load class that use the same classloader of current class, can't load system packages,
* </p>
*
* @param pkg
* path of a package
* @return
*/
public static Set<Class<? extends Object>> getClazzSet(String pkg) {
// prepare reflection, include direct subclass of Object.class
Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
.filterInputsBy(new FilterBuilder().includePackage(pkg)));
return reflections.getSubTypesOf(Object.class);
}
public static void test() {
String pkg = "org.apache.tomcat.util";
Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
for (Class<? extends Object> clazz : clazzSet) {
System.out.println(clazz.getName());
}
}
public static void main(String[] args) {
test();
}
}
โซลูชันของ Aleksander Blomskøldไม่ได้ผลสำหรับฉันสำหรับการทดสอบแบบกำหนดพารามิเตอร์@RunWith(Parameterized.class)
เมื่อใช้ Maven การทดสอบถูกตั้งชื่ออย่างถูกต้องและยังพบ แต่ไม่ดำเนินการ:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec
ปัญหาที่คล้ายกันได้รับรายงานที่นี่
ในกรณีของฉัน@Parameters
คือการสร้างอินสแตนซ์ของแต่ละคลาสในแพ็คเกจ การทดสอบทำงานได้ดีเมื่อทำงานในเครื่องใน IDE อย่างไรก็ตามเมื่อรัน Maven ไม่มีคลาสที่พบกับโซลูชันของ Aleksander Blomskøld
ฉันทำให้มันทำงานกับ snipped ต่อไปนี้ซึ่งเป็นแรงบันดาลใจจากความคิดเห็นของ David Pärssonในคำตอบของ Aleksander Blomskøld:
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
.addUrls(ClasspathHelper.forJavaClassPath())
.filterInputsBy(new FilterBuilder()
.include(FilterBuilder.prefix(basePackage))));
Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);
เกี่ยวกับสิ่งนี้:
public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
final String pkgPath = pkgName.replace('.', '/');
final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
Path root;
if (pkg.toString().startsWith("jar:")) {
try {
root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
} catch (final FileSystemNotFoundException e) {
root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
}
} else {
root = Paths.get(pkg);
}
final String extension = ".class";
try (final Stream<Path> allPaths = Files.walk(root)) {
allPaths.filter(Files::isRegularFile).forEach(file -> {
try {
final String path = file.toString().replace('/', '.');
final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
allClasses.add(Class.forName(name));
} catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
}
});
}
return allClasses;
}
จากนั้นคุณสามารถโอเวอร์โหลดฟังก์ชัน:
public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
return getClassesForPackage(pkg.getName());
}
หากคุณต้องการทดสอบ:
public static void main(final String[] argv) throws IOException, URISyntaxException {
for (final Class<?> cls : getClassesForPackage("my.package")) {
System.out.println(cls);
}
for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
System.out.println(cls);
}
}
หาก IDE ของคุณไม่มีตัวช่วยนำเข้า:
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
มันได้ผล:
จาก IDE ของคุณ
สำหรับไฟล์ JAR
โดยไม่ต้องพึ่งพาภายนอก
คำตอบเกือบทั้งหมดใช้Reflections
หรืออ่านไฟล์คลาสจากระบบไฟล์ หากคุณพยายามอ่านคลาสจากระบบไฟล์คุณอาจได้รับข้อผิดพลาดเมื่อคุณจัดทำแพ็กเกจแอปพลิเคชันของคุณเป็น JAR หรืออื่น ๆ นอกจากนี้คุณอาจไม่ต้องการใช้ห้องสมุดแยกต่างหากสำหรับวัตถุประสงค์นั้น
นี่เป็นอีกวิธีหนึ่งซึ่งเป็นจาวาบริสุทธิ์และไม่ขึ้นอยู่กับระบบไฟล์
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class PackageUtil {
public static Collection<Class> getClasses(final String pack) throws Exception {
final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
.map(javaFileObject -> {
try {
final String[] split = javaFileObject.getName()
.replace(".class", "")
.replace(")", "")
.split(Pattern.quote(File.separator));
final String fullClassName = pack + "." + split[split.length - 1];
return Class.forName(fullClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toCollection(ArrayList::new));
}
}
Java 8 ไม่ได้เป็นต้อง คุณสามารถใช้ลูปแทนสตรีมได้ และคุณสามารถทดสอบได้เช่นนี้
public static void main(String[] args) throws Exception {
final String pack = "java.nio.file"; // Or any other package
PackageUtil.getClasses(pack).stream().forEach(System.out::println);
}
หากคุณไม่ได้ใช้ตัวโหลดคลาสแบบไดนามิกคุณสามารถค้นหา classpath และสำหรับแต่ละรายการค้นหาไดเรกทอรีหรือไฟล์ JAR
มูลค่าการกล่าวขวัญ
ถ้าคุณต้องการมีรายการของคลาสทั้งหมดภายใต้บางแพคเกจคุณสามารถใช้Reflection
วิธีต่อไปนี้:
List<Class> myTypes = new ArrayList<>();
Reflections reflections = new Reflections("com.package");
for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
myTypes.add(Class.forName(s));
}
สิ่งนี้จะสร้างรายการของคลาสที่คุณสามารถใช้ในภายหลังตามที่คุณต้องการ
มันเป็นไปได้มาก แต่ถ้าไม่มีห้องสมุดเพิ่มเติมReflections
มันยาก ...
มันยากเพราะคุณยังไม่ได้รับชื่อเต็มชั้น
และฉันใช้รหัสของClassFinder
ชั้นเรียนของฉัน:
package play.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Created by LINKOR on 26.05.2017 in 15:12.
* Date: 2017.05.26
*/
public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
try {
file = new JarFile(filePath);
} catch (IOException e) {
trouble = true;
}
}
public List<String> findClasses(String pkg) {
ArrayList<String> classes = new ArrayList<>();
Enumeration<JarEntry> entries = file.entries();
while (entries.hasMoreElements()) {
JarEntry cls = entries.nextElement();
if (!cls.isDirectory()) {
String fileName = cls.getName();
String className = fileName.replaceAll("/", ".").replaceAll(File.pathSeparator, ".").substring(0, fileName.lastIndexOf('.'));
if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
}
}
return classes;
}
}
จากคำตอบของ @ Staaleและในความพยายามที่จะไม่พึ่งพาไลบรารีของบุคคลที่สามฉันจะใช้วิธีการของระบบไฟล์โดยตรวจสอบตำแหน่งทางกายภาพของแพ็คเกจแรกด้วย:
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();
new java.io.File(
klass.getResource(
"/" + curPackage.replace( "." , "/")
).getFile()
).listFiles(
new java.io.FileFilter() {
public boolean accept(java.io.File file) {
final String classExtension = ".class";
if ( file.isFile()
&& file.getName().endsWith(classExtension)
// avoid inner classes
&& ! file.getName().contains("$") )
{
try {
String className = file.getName();
className = className.substring(0, className.length() - classExtension.length());
foundClassesDyn.add( Class.forName( curPackage + "." + className ) );
} catch (ClassNotFoundException e) {
e.printStackTrace(System.out);
}
}
return false;
}
}
);
foundClasses = foundClassesDyn.toArray(foundClasses);
หากคุณเพียงแค่ต้องการโหลดกลุ่มของคลาสที่เกี่ยวข้องสปริงสามารถช่วยคุณได้
Spring สามารถสร้างรายการหรือแผนที่ของคลาสทั้งหมดที่ใช้อินเตอร์เฟสที่กำหนดในโค้ดบรรทัดเดียว รายการหรือแผนที่จะมีอินสแตนซ์ของคลาสทั้งหมดที่ใช้อินเทอร์เฟซนั้น
ที่กล่าวไว้ว่าเป็นอีกทางเลือกหนึ่งในการโหลดรายการของคลาสออกจากระบบไฟล์แทนที่จะใช้อินเตอร์เฟสเดียวกันในทุกคลาสที่คุณต้องการโหลดโดยไม่คำนึงถึงแพ็กเกจและใช้ Spring เพื่อให้อินสแตนซ์ของคุณทั้งหมด ด้วยวิธีนี้คุณสามารถโหลด (และยกตัวอย่าง) ชั้นเรียนทั้งหมดที่คุณต้องการโดยไม่คำนึงถึงแพ็คเกจที่อยู่
ในทางกลับกันหากมีทั้งหมดในแพ็คเกจเป็นสิ่งที่คุณต้องการแล้วให้คลาสทั้งหมดในแพ็คเกจนั้นใช้อินเตอร์เฟสที่กำหนด
Java ธรรมดา: FindAllClassesUsingPlainJavaReflectionTest.java
@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {
private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
log.error(throwable.getLocalizedMessage());
return new RuntimeException(throwable);
};
private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {
Locale locale = Locale.getDefault();
Charset charset = StandardCharsets.UTF_8;
val fileManager = ToolProvider.getSystemJavaCompiler()
.getStandardFileManager(/* diagnosticListener */ null, locale, charset);
StandardLocation location = StandardLocation.CLASS_PATH;
JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
.getOrElseThrow(asRuntimeException);
String pathToPackageAndClass = basePackageName.replace(".", File.separator);
Function<String, String> mapToClassName = s -> {
String prefix = Arrays.stream(s.split(pathToPackageAndClass))
.findFirst()
.orElse("");
return s.replaceFirst(prefix, "")
.replaceAll(File.separator, ".");
};
return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
.filter(javaFileObject -> javaFileObject.getKind().equals(kind))
.map(FileObject::getName)
.map(fileObjectName -> fileObjectName.replace(".class", ""))
.map(mapToClassName)
.map(className -> Try.of(() -> Class.forName(className))
.getOrElseThrow(asRuntimeException))
.collect(Collectors.toList());
};
@Test
@DisplayName("should get classes recursively in given package")
void test() {
Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
assertThat(classes).hasSizeGreaterThan(4);
classes.stream().map(String::valueOf).forEach(log::info);
}
}
PS: เพื่อลดความซับซ้อนของแม่แบบสำหรับการจัดการข้อผิดพลาด ฯลฯ ฉันใช้ที่นี่vavr
และlombok
ห้องสมุด
การใช้งานอื่น ๆ สามารถพบได้ในGitHub ของฉัน daggerok / java-reflection-find-annotated-class-or-methods repo
ฉันไม่สามารถหางานสั้น ๆ มาทำอะไรง่ายๆได้ ดังนั้นที่นี่คือฉันทำมันเองหลังจากที่สกรูไปรอบ ๆ ในขณะที่:
Reflections reflections =
new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packagePath))
.setUrls(ClasspathHelper.forPackage(packagePath))
.setScanners(new SubTypesScanner(false)));
Set<String> typeList = reflections.getAllTypes();
ถ้าคุณอยู่ในฤดูใบไม้ผลิที่ดินคุณสามารถใช้PathMatchingResourcePatternResolver
;
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");
Arrays.asList(resources).forEach(r->{
...
});
เป็นไปไม่ได้เนื่องจากคลาสทั้งหมดในแพ็คเกจอาจไม่สามารถโหลดได้ในขณะที่คุณรู้จักแพ็คเกจของคลาสเสมอ