Java: how to scan all classes in a specified package
To write an MVC framework, you need to scan components from the package and register them in the container. JDK does not provide a ready-made slave method and can only be implemented by yourself.
Function:
Given a package name, you can program all the class files under the package (and all its sub-packages. For example, enter the package namecom.myapp.util
, Output the full qualified name of the class under this packagecom.myapp.util.StringUtils
,com.app.util.ImageUtils
.
Ideas:
Some web servers decompress the jar package during deployment and running, so the class file will be in the normal file directory. If the web server does not support the jar package, the class file will directly exist in the Jar package. For the former, you only need to locate the directory where the class file is located, and then read the class file name. For the latter, You need to first locate the directory where the jar package is located, and then useJarInputStream
Read the Jar package and get the class name.
Implementation:
This is directly copied from the written project code. To run this code, you needLogger.debug
ChangeSystem.out.println()
/*** This operation is used to find out all classes in a package. * Created by whf on 15-2-26. */public class classpathpackage‑implements package‑{ private Logger logger = LoggerFactory. getLogger (classpathpackage.pdf. class); private String basePackage; private ClassLoader cl;/*** Construct an instance and specify the base package it shocould scan. * @ param basePackage The base package to scan. */public classpathpackage1_( String basePackage) {this. basePackage = basePackage; this. cl = getClass (). getClassLoader ();}/*** Construct an instance with base package and class loader. * @ param basePackage The base package to scan. * @ param cl Use this class load to locate the package. */public classpathpackageage (String basePackage, ClassLoader cl) {this. basePackage = basePackage; this. cl = cl;}/*** Get all fully qualified names located in the specified package * and its sub-package. ** @ return A list of fully qualified names. * @ throws IOException */@ Override public List
GetFullyQualifiedClassNameList () throws IOException {logger.info ("start scanning all classes under package {}", basePackage); return doScan (basePackage, new ArrayList <> ());} /*** Actually perform the scanning procedure. ** @ param basePackage * @ param nameList A list to contain the result. * @ return A list of fully qualified names. ** @ throws IOException */private List
DoScan (String basePackage, List
NameList) throws IOException {// replace dots with splashes String splashPath = StringUtil. dotToSplash (basePackage); // get file path URL = cl. getResource (splashPath); String filePath = StringUtil. getRootPath (url); // Get classes in that package. // If the web server unzips the jar file, then the classes will exist in the form of // normal file in the directory. // If the web server does not unzip the jar file, then classes will exist in jar file. list
Names = null; // contains the name of the class file. e.g ., apple. class will be stored as "Apple" if (isJarFile (filePath) {// jar file if (logger. isDebugEnabled () {logger. debug ("{} is a JAR package", filePath);} names = readFromJarFile (filePath, splashPath);} else {// directory if (logger. isDebugEnabled () {logger. debug ("{} is a directory", filePath);} names = readFromDirectory (filePath);} for (String name: names) {if (isClassFile (name )) {// nameList. add (basePackage + ". "+ StringUtil. trimExtension (name); nameList. add (toFullyQualifiedName (name, basePackage);} else {// this is a directory // check this directory for more classes // do recursive invocation doScan (basePackage + ". "+ name, nameList) ;}} if (logger. isDebugEnabled () {for (String n: nameList) {logger. debug ("find {}", n) ;}return nameList;}/*** Convert short class name to fully qualified name. * e.g ., string-> java. lang. string */private String toFullyQualifiedName (String shortName, String basePackage) {StringBuilder sb = new StringBuilder (basePackage); sb. append ('. '); sb. append (StringUtil. trimExtension (shortName); return sb. toString ();} private List
ReadFromJarFile (String jarPath, String splashedPackageName) throws IOException {if (logger. isDebugEnabled () {logger. debug ("read class from JAR package: {}", jarPath);} JarInputStream jarIn = new JarInputStream (new FileInputStream (jarPath); JarEntry entry = jarIn. getNextJarEntry (); List
NameList = new ArrayList <> (); while (null! = Entry) {String name = entry. getName (); if (name. startsWith (splashedPackageName) & isClassFile (name) {nameList. add (name);} entry = jarIn. getNextJarEntry ();} return nameList;} private List
ReadFromDirectory (String path) {File file = new File (path); String [] names = file. list (); if (null = names) {return null;} return Arrays. asList (names);} private boolean isClassFile (String name) {return name. endsWith (". class ");} private boolean isJarFile (String name) {return name. endsWith (". jar ");}/*** For test purpose. */public static void main (String [] args) throws Exception {package1_scan = new classpathpackage1_( "cn. fh. lightning. bean "); scan. getFullyQualifiedClassNameList ();}}
The above code is usedStringUtils
Class:
public class StringUtil { private StringUtil() { } /** * "file:/home/whf/cn/fh" -> "/home/whf/cn/fh" * "jar:file:/home/whf/foo.jar!cn/fh" -> "/home/whf/foo.jar" */ public static String getRootPath(URL url) { String fileUrl = url.getFile(); int pos = fileUrl.indexOf('!'); if (-1 == pos) { return fileUrl; } return fileUrl.substring(5, pos); } /** * "cn.fh.lightning" -> "cn/fh/lightning" * @param name * @return */ public static String dotToSplash(String name) { return name.replaceAll("\\.", "/"); } /** * "Apple.class" -> "Apple" */ public static String trimExtension(String name) { int pos = name.indexOf('.'); if (-1 != pos) { return name.substring(0, pos); } return name; } /** * /application/home -> /home * @param uri * @return */ public static String trimURI(String uri) { String trimmed = uri.substring(1); int splashIndex = trimmed.indexOf('/'); return trimmed.substring(splashIndex); }}
Execution result: