Có thể tìm thấy tất cả các lớp hoặc giao diện trong một gói nhất định? (Nhanh chóng nhìn vào Package
, ví dụ , có vẻ như không.)
Có thể tìm thấy tất cả các lớp hoặc giao diện trong một gói nhất định? (Nhanh chóng nhìn vào Package
, ví dụ , có vẻ như không.)
Câu trả lời:
Do tính chất năng động của trình nạp lớp, điều này là không thể. Trình nạp lớp không bắt buộc phải báo cho VM biết lớp nào nó có thể cung cấp, thay vào đó chúng chỉ là các yêu cầu được trao cho các lớp và phải trả về một lớp hoặc ném ngoại lệ.
Tuy nhiên, nếu bạn viết trình nạp lớp của riêng mình hoặc kiểm tra các đường dẫn lớp và nó là bình, bạn có thể tìm thấy thông tin này. Điều này sẽ thông qua các hoạt động hệ thống tập tin, và không phản ánh. Thậm chí có thể có các thư viện có thể giúp bạn làm điều này.
Nếu có các lớp được tạo hoặc phân phối từ xa, bạn sẽ không thể khám phá các lớp đó.
Thay vào đó, phương thức bình thường là ở đâu đó đăng ký các lớp bạn cần truy cập trong một tệp hoặc tham chiếu chúng trong một lớp khác. Hoặc chỉ sử dụng quy ước khi đặt tên.
Phụ lục: Thư viện Reflection sẽ cho phép bạn tra cứu các lớp trong đường dẫn hiện tại. Nó có thể được sử dụng để có được tất cả các lớp trong một gói:
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends Object>> allClasses =
reflections.getSubTypesOf(Object.class);
Có lẽ bạn nên xem thư viện Reflection mã nguồn mở . Với nó, bạn có thể dễ dàng đạt được những gì bạn muốn.
Đầu tiên, thiết lập chỉ mục phản xạ (hơi lộn xộn vì việc tìm kiếm tất cả các lớp bị tắt theo mặc định):
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"))));
Sau đó, bạn có thể truy vấn tất cả các đối tượng trong một gói nhất định:
Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
.addUrls(ClasspathHelper.forJavaClassPath())
thay vì giải quyết chúng ở trên đã giải quyết chúng cho tôi. Ít mã là tốt!
Google Guava 14 bao gồm một lớp mới ClassPath
với ba phương pháp để quét các lớp cấp cao nhất:
getTopLevelClasses()
getTopLevelClasses(String packageName)
getTopLevelClassesRecursive(String packageName)
Xem ClassPath
javadocs để biết thêm.
ClassPath
được gắn thẻ @Beta
, vì vậy có thể không phải là ý tưởng hay cho một số ...
Bạn có thể sử dụng phương pháp 1 này sử dụng ClassLoader
.
/**
* 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 Phương pháp này đã được thực hiện ban đầu từ http://snippets.dzone.com/posts/show/4831 , được lưu trữ bởi Internet Archive, như liên quan đến bây giờ. Đoạn mã cũng có sẵn tại https://dzone.com/articles/get-all-groupes-within-package .
%20
, nhưng hàm new File()
tạo đã coi đó là phần trăm theo nghĩa đen ký hai số không. Tôi đã sửa nó bằng cách thay đổi dirs.add(...)
dòng này: dirs.add(new File(resource.toURI()));
Điều này cũng có nghĩa là tôi phải thêm URISyntaxException
vào mệnh đề ném củagetClasses
Ví dụ này dành cho Spring 4, nhưng bạn cũng có thể tìm thấy trình quét classpath trong các phiên bản trước.
// 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 ...
}
Lưu ý: Trong phiên bản 14, API vẫn được đánh dấu là @Beta , vì vậy hãy cẩn thận trong mã sản xuất.
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
lớp trong Guava cũng được đánh dấu bằng @Beta
: "API được đánh dấu bằng chú thích @Beta ở cấp độ lớp hoặc phương thức có thể thay đổi. Chúng có thể được sửa đổi theo bất kỳ cách nào, hoặc thậm chí bị xóa, trong bất kỳ bản phát hành chính nào. Nếu mã của bạn là một thư viện (nghĩa là nó được sử dụng trên CLASSPATH của người dùng ngoài tầm kiểm soát của bạn), bạn không nên sử dụng API beta, trừ khi bạn đóng gói lại chúng ... " code.google.com/p/guava-lologists/#Important_Warnings
getAllClasses()
phương pháp có thể được sử dụng.
Xin chào. Tôi luôn có một số vấn đề với các giải pháp trên (và trên các trang web khác).
Tôi, với tư cách là một nhà phát triển, đang lập trình một addon cho API. API ngăn chặn việc sử dụng bất kỳ thư viện bên ngoài hoặc công cụ của bên thứ 3. Thiết lập cũng bao gồm một hỗn hợp mã trong các tệp jar hoặc zip và các tệp lớp nằm trực tiếp trong một số thư mục. Vì vậy, mã của tôi phải có khả năng làm việc trên mọi thiết lập. Sau rất nhiều nghiên cứu, tôi đã tìm ra một phương pháp sẽ hoạt động trong ít nhất 95% tất cả các thiết lập có thể.
Các mã sau đây về cơ bản là phương pháp quá mức sẽ luôn hoạt động.
Mã này quét một gói nhất định cho tất cả các lớp được bao gồm trong nó. Nó sẽ chỉ hoạt động cho tất cả các lớp trong hiện tại 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;
}
Ba phương thức này cung cấp cho bạn khả năng tìm thấy tất cả các lớp trong một gói nhất định.
Bạn sử dụng nó như thế này:
getClassesForPackage("package.your.classes.are.in");
Phương pháp đầu tiên có được hiện tại ClassLoader
. Sau đó, nó tìm nạp tất cả các tài nguyên có chứa gói nói và lặp lại của các URL
s này . Sau đó, nó tạo ra một URLConnection
và xác định loại URl chúng ta có. Nó có thể là một thư mục ( FileURLConnection
) hoặc một thư mục bên trong tệp jar hoặc tệp zip ( JarURLConnection
). Tùy thuộc vào loại kết nối, chúng tôi có hai phương thức khác nhau sẽ được gọi.
Đầu tiên hãy xem điều gì xảy ra nếu nó là a FileURLConnection
.
Đầu tiên, nó kiểm tra nếu File đã qua tồn tại và là một thư mục. Nếu đó là trường hợp nó kiểm tra nếu nó là một tập tin lớp. Nếu vậy một Class
đối tượng sẽ được tạo và đưa vàoArrayList
. Nếu nó không phải là một tập tin lớp mà là một thư mục, chúng ta chỉ cần lặp lại vào nó và làm điều tương tự. Tất cả các trường hợp / tập tin khác sẽ bị bỏ qua.
Nếu URLConnection
là một JarURLConnection
phương thức trợ giúp riêng tư khác sẽ được gọi. Phương pháp này lặp lại trên tất cả các mục trong kho lưu trữ zip / jar. Nếu một mục nhập là một tệp lớp và nằm trong gói, một Class
đối tượng sẽ được tạo và lưu trữ trong ArrayList
.
Sau khi tất cả các tài nguyên đã được phân tích cú pháp, nó (phương thức chính) trả về hàm ArrayList
chứa tất cả các lớp trong gói đã cho, đó là hiện tạiClassLoader
biết về.
Nếu quá trình không thành công tại bất kỳ thời điểm nào, a ClassNotFoundException
sẽ bị ném thông tin chi tiết về nguyên nhân chính xác.
sun.net.www.protocol.file.FileURLConnection
, tạo cảnh báo vào thời gian biên dịch ("cảnh báo: sun.net.www.protatio.file.FileURLCconnectection là API độc quyền của Sun và có thể bị xóa trong bản phát hành trong tương lai"). Có cách nào khác để sử dụng lớp đó không, hoặc cảnh báo có thể được loại bỏ bằng cách sử dụng các chú thích không?
if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }
Đó là những gì tôi đã làm trên mã của mình để tìm kiếm các lớp chú thích JPA
Không sử dụng bất kỳ thư viện bổ sung:
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
là null
... :(
String pack = getPackage().getName();
đó, thì bạn phải thêmpack = pack.replaceAll("[.]", "/");
Trong các trình nạp lớp chung không cho phép quét qua tất cả các lớp trên đường dẫn lớp. Nhưng thường thì trình tải lớp duy nhất được sử dụng là UrlClassLoader từ đó chúng ta có thể truy xuất danh sách các thư mục và tệp jar (xem getURL ) và mở từng cái một để liệt kê các lớp có sẵn. Cách tiếp cận này, được gọi là quét đường dẫn lớp, được thực hiện trong Scannotation và Reflection .
Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);
Một cách tiếp cận khác là sử dụng API xử lý chú thích có thể cắm của Java để viết bộ xử lý chú thích sẽ thu thập tất cả các lớp chú thích vào thời gian biên dịch và xây dựng tệp chỉ mục để sử dụng thời gian chạy. Cơ chế này được triển khai trong thư viện Class Index :
// package-info.java
@IndexSubclasses
package my.package;
// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Lưu ý rằng không cần thiết lập bổ sung vì quá trình quét hoàn toàn tự động nhờ trình biên dịch Java tự động phát hiện bất kỳ bộ xử lý nào được tìm thấy trên đường dẫn lớp.
Cơ chế mạnh mẽ nhất để liệt kê tất cả các lớp trong một gói nhất định hiện tại là ClassGraph , vì nó xử lý mảng cơ chế đặc tả đường dẫn rộng nhất có thể , bao gồm cả hệ thống mô-đun JPMS mới. (Tôi là tác giả.)
List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
.enableClassInfo().scan()) {
classNames.addAll(scanResult.getAllClasses().getNames());
}
Đây là cách tôi làm điều đó. Tôi quét tất cả các thư mục con (gói phụ) và tôi không thử tải các lớp ẩn danh:
/**
* 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;
}
Tôi kết hợp một dự án github đơn giản để giải quyết vấn đề này:
https://github.com/ddopson/java- class-enumerator
Nó nên hoạt động đối với các đường dẫn lớp dựa trên tệp VÀ cho các tệp jar.
Nếu bạn chạy 'make' sau khi kiểm tra dự án, nó sẽ in ra:
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.
Xem thêm câu trả lời khác của tôi
Vâng, sử dụng một vài API bạn có thể, đây là cách tôi thích thực hiện, đã đối mặt với vấn đề này mà tôi đang sử dụng lõi ngủ đông & phải tìm các lớp có chú thích với một chú thích nhất định.
Đặt các chú thích tùy chỉnh này bằng cách sử dụng mà bạn sẽ đánh dấu các lớp bạn muốn được chọn.
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 {
}
Sau đó đánh dấu lớp của bạn với nó như thế nào
@EntityToBeScanned
public MyClass{
}
Tạo lớp tiện ích này có phương thức sau
public class ClassScanner {
public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
Reflections reflections = new Reflections(".*");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
return annotated;
}
}
Gọi phương thức allFoundClassAnnotatedWithEntityToBeScned () để tìm Tập hợp các lớp được tìm thấy.
Bạn sẽ cần libs được đưa ra dưới đây
<!-- 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>
Bạn cần tra cứu mọi mục nhập trình nạp lớp trong đường dẫn lớp:
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());
// ....
}
Nếu mục nhập là thư mục, chỉ cần tra cứu trong thư mục con bên phải:
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));
}
}
Nếu mục nhập là tệp và đó là tệp jar, hãy kiểm tra các mục ZIP của tệp đó:
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());
}
}
Bây giờ khi bạn có tất cả các tên lớp đóng gói, bạn có thể thử tải chúng với sự phản chiếu và phân tích nếu chúng là các lớp hoặc giao diện, v.v.
Tôi đã cố gắng sử dụng thư viện Reflection, nhưng đã gặp một số vấn đề khi sử dụng nó và có quá nhiều lọ tôi nên đưa vào chỉ để lấy các lớp trên một gói.
Tôi sẽ đăng một giải pháp tôi đã tìm thấy trong câu hỏi trùng lặp này: Làm thế nào để có được tất cả các tên lớp trong một gói?
Câu trả lời được viết bởi sp00m ; Tôi đã thêm một số chỉnh sửa để làm cho nó hoạt động:
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;
}
}
Để sử dụng nó, chỉ cần gọi phương thức find là sp00n được đề cập trong ví dụ này: Tôi đã thêm việc tạo các thể hiện của các lớp nếu cần.
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();
//...
}
Tôi vừa viết một lớp tiện dụng, nó bao gồm các phương thức kiểm tra, bạn có thể kiểm tra ~
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();
}
}
Giải pháp của Aleksander Blomskøld không hiệu quả đối với tôi đối với các thử nghiệm được tham số hóa @RunWith(Parameterized.class)
khi sử dụng Maven. Các thử nghiệm được đặt tên chính xác và cũng được tìm thấy nhưng không được thực hiện:
-------------------------------------------------------
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
Một vấn đề tương tự đã được báo cáo ở đây .
Trong trường hợp của tôi @Parameters
là tạo các thể hiện của mỗi lớp trong một gói. Các thử nghiệm hoạt động tốt khi chạy cục bộ trong IDE. Tuy nhiên, khi chạy Maven, không có lớp nào được tìm thấy với giải pháp của Aleksander Blomskøld.
Tôi đã làm cho nó hoạt động với đoạn trích sau được lấy cảm hứng từ nhận xét của David Pärsson về câu trả lời của 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);
Cái này thì sao:
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;
}
Sau đó, bạn có thể quá tải chức năng:
public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
return getClassesForPackage(pkg.getName());
}
Nếu bạn cần kiểm tra nó:
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);
}
}
Nếu IDE của bạn không có trình trợ giúp nhập:
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;
Nó hoạt động:
từ IDE của bạn
cho một tệp JAR
không phụ thuộc bên ngoài
Hầu như tất cả các câu trả lời đều sử dụng Reflections
hoặc đọc các tệp lớp từ hệ thống tệp. Nếu bạn cố đọc các lớp từ hệ thống tệp, bạn có thể gặp lỗi khi đóng gói ứng dụng của mình dưới dạng JAR hoặc khác. Ngoài ra, bạn có thể không muốn sử dụng một thư viện riêng cho mục đích đó.
Đây là một cách tiếp cận khác là java thuần túy và không phụ thuộc vào hệ thống tệp.
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 không bắt buộc . Bạn có thể sử dụng cho các vòng lặp thay vì các luồng. Và bạn có thể kiểm tra nó như thế này
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);
}
ToolProvider.getSystemJavaCompiler()
, mã này không quét các gói lồng nhau.
Với điều kiện bạn không sử dụng bất kỳ trình nạp lớp động nào, bạn có thể tìm kiếm đường dẫn lớp và cho mỗi mục nhập tìm kiếm thư mục hoặc tệp JAR.
Đáng nói
Nếu bạn muốn có một danh sách tất cả các lớp theo gói nào đó, bạn có thể sử dụng Reflection
cách sau:
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));
}
Điều này sẽ tạo một danh sách các lớp mà sau này bạn có thể sử dụng chúng theo ý muốn.
Điều đó là rất có thể, nhưng không có các thư viện bổ sung như Reflections
nó rất khó ...
Thật khó vì bạn không có đầy đủ công cụ để lấy tên lớp.
Và, tôi lấy mã của ClassFinder
lớp mình:
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;
}
}
Dựa trên câu trả lời của @ Staale và trong nỗ lực không phụ thuộc vào thư viện của bên thứ ba, tôi sẽ thực hiện phương pháp Hệ thống tệp bằng cách kiểm tra vị trí thực tế của gói đầu tiên với:
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);
Nếu bạn chỉ đang tìm cách tải một nhóm các lớp liên quan, thì Spring có thể giúp bạn.
Spring có thể khởi tạo một danh sách hoặc bản đồ của tất cả các lớp thực hiện một giao diện đã cho trong một dòng mã. Danh sách hoặc bản đồ sẽ chứa các thể hiện của tất cả các lớp thực hiện giao diện đó.
Điều đó đang được nói, như là một cách thay thế để tải danh sách các lớp ra khỏi hệ thống tệp, thay vào đó chỉ thực hiện cùng một giao diện trong tất cả các lớp bạn muốn tải, bất kể gói và sử dụng Spring để cung cấp cho bạn các phiên bản của tất cả chúng. Bằng cách đó, bạn có thể tải (và khởi tạo) tất cả các lớp bạn muốn bất kể chúng nằm trong gói nào.
Mặt khác, nếu có tất cả chúng trong một gói là những gì bạn muốn, thì đơn giản là có tất cả các lớp trong gói đó thực hiện một giao diện nhất định.
java đơn giản: Find ALLClassUsingPlainJavaReflectionTest.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: để đơn giản hóa các mẫu nồi hơi để xử lý lỗi, v.v., tôi đang sử dụng ở đây vavr
vàlombok
các thư viện
các triển khai khác có thể được tìm thấy trong repo GitHub daggerok / java-Reflection-find-annotated-class-or-method của tôi
Tôi không thể tìm thấy một snipped làm việc ngắn cho một cái gì đó rất đơn giản. Vì vậy, đây là, tôi đã tự làm nó sau khi vặn vẹo một lúc:
Reflections reflections =
new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packagePath))
.setUrls(ClasspathHelper.forPackage(packagePath))
.setScanners(new SubTypesScanner(false)));
Set<String> typeList = reflections.getAllTypes();
Nếu bạn ở vùng đất mùa xuân, bạn có thể sử dụng PathMatchingResourcePatternResolver
;
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");
Arrays.asList(resources).forEach(r->{
...
});