001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.utils;
017
018import io.jboot.app.config.JbootConfigManager;
019
020import java.io.File;
021import java.io.IOException;
022import java.lang.annotation.Annotation;
023import java.lang.reflect.Modifier;
024import java.net.URL;
025import java.net.URLClassLoader;
026import java.net.URLDecoder;
027import java.util.*;
028import java.util.function.Predicate;
029import java.util.jar.JarEntry;
030import java.util.jar.JarFile;
031import java.util.jar.JarInputStream;
032import java.util.stream.Collectors;
033
034public class ClassScanner {
035
036    private static final Set<Class<?>> appClassesCache = new HashSet<>();
037
038    public static final Set<String> scanJars = new HashSet<>();
039    public static final Set<String> excludeJars = new HashSet<>();
040
041    public static final Set<String> scanClasses = new HashSet<>();
042    public static final Set<String> excludeClasses = new HashSet<>();
043
044    // dev模式打开扫描信息打印
045    private static boolean printScannerInfoEnable = false;
046
047    public static boolean isPrintScannerInfoEnable() {
048        return printScannerInfoEnable;
049    }
050
051    public static void setPrintScannerInfoEnable(boolean printScannerInfoEnable) {
052        ClassScanner.printScannerInfoEnable = printScannerInfoEnable;
053    }
054
055
056    public static void addScanJarPrefix(String prefix) {
057        scanJars.add(prefix.toLowerCase().trim());
058    }
059
060    static {
061        scanJars.add("jboot");
062    }
063
064
065    public static void addUnscanJarPrefix(String prefix) {
066        excludeJars.add(prefix.toLowerCase().trim());
067    }
068
069    static {
070        excludeJars.add("jfinal-");
071        excludeJars.add("cos-");
072        excludeJars.add("cglib-");
073        excludeJars.add("undertow-");
074        excludeJars.add("xnio-");
075        excludeJars.add("javax.");
076        excludeJars.add("hikaricp-");
077        excludeJars.add("druid-");
078        excludeJars.add("mysql-");
079        excludeJars.add("db2jcc-");
080        excludeJars.add("db2jcc4-");
081        excludeJars.add("ojdbc");
082        excludeJars.add("junit-");
083        excludeJars.add("junit5-");
084        excludeJars.add("org.junit");
085        excludeJars.add("hamcrest-");
086        excludeJars.add("jboss-");
087        excludeJars.add("motan-");
088        excludeJars.add("commons-pool");
089        excludeJars.add("commons-beanutils");
090        excludeJars.add("commons-codec");
091        excludeJars.add("commons-collections");
092        excludeJars.add("commons-configuration");
093        excludeJars.add("commons-lang");
094        excludeJars.add("commons-logging");
095        excludeJars.add("commons-io");
096        excludeJars.add("commons-httpclient");
097        excludeJars.add("commons-fileupload");
098        excludeJars.add("commons-validator");
099        excludeJars.add("commons-email");
100        excludeJars.add("commons-text");
101        excludeJars.add("commons-cli");
102        excludeJars.add("commons-math");
103        excludeJars.add("commons-jxpath");
104        excludeJars.add("commons-compress");
105        excludeJars.add("audience-");
106        excludeJars.add("hessian-");
107        excludeJars.add("metrics-");
108        excludeJars.add("javapoet-");
109        excludeJars.add("netty-");
110        excludeJars.add("consul-");
111        excludeJars.add("gson-");
112        excludeJars.add("zookeeper-");
113        excludeJars.add("slf4j-");
114        excludeJars.add("fastjson-");
115        excludeJars.add("guava-");
116        excludeJars.add("failureaccess-");
117        excludeJars.add("listenablefuture-");
118        excludeJars.add("jsr305-");
119        excludeJars.add("checker-qual-");
120        excludeJars.add("error_prone_annotations-");
121        excludeJars.add("j2objc-");
122        excludeJars.add("animal-sniffer-");
123        excludeJars.add("cron4j-");
124        excludeJars.add("jedis-");
125        excludeJars.add("lettuce-");
126        excludeJars.add("reactor-");
127        excludeJars.add("fst-");
128        excludeJars.add("kryo-");
129        excludeJars.add("jackson-");
130        excludeJars.add("javassist-");
131        excludeJars.add("objenesis-");
132        excludeJars.add("reflectasm-");
133        excludeJars.add("asm-");
134        excludeJars.add("minlog-");
135        excludeJars.add("jsoup-");
136        excludeJars.add("ons-client-");
137        excludeJars.add("amqp-client-");
138        excludeJars.add("ehcache-");
139        excludeJars.add("sharding-");
140        excludeJars.add("snakeyaml-");
141        excludeJars.add("groovy-");
142        excludeJars.add("profiler-");
143        excludeJars.add("joda-time-");
144        excludeJars.add("shiro-");
145        excludeJars.add("dubbo-");
146        excludeJars.add("curator-");
147        excludeJars.add("resteasy-");
148        excludeJars.add("reactive-");
149        excludeJars.add("validation-");
150        excludeJars.add("httpclient-");
151        excludeJars.add("httpcore-");
152        excludeJars.add("httpmime-");
153        excludeJars.add("jcip-");
154        excludeJars.add("jcl-");
155        excludeJars.add("microprofile-");
156        excludeJars.add("org.osgi");
157        excludeJars.add("zkclient-");
158        excludeJars.add("jjwt-");
159        excludeJars.add("okhttp-");
160        excludeJars.add("okio-");
161        excludeJars.add("zbus-");
162        excludeJars.add("swagger-");
163        excludeJars.add("j2cache-");
164        excludeJars.add("caffeine-");
165        excludeJars.add("jline-");
166        excludeJars.add("qpid-");
167        excludeJars.add("geronimo-");
168        excludeJars.add("activation-");
169        excludeJars.add("org.abego");
170        excludeJars.add("antlr-");
171        excludeJars.add("antlr4-");
172        excludeJars.add("st4-");
173        excludeJars.add("icu4j-");
174        excludeJars.add("idea_rt");
175        excludeJars.add("mrjtoolkit");
176        excludeJars.add("logback-");
177        excludeJars.add("log4j-");
178        excludeJars.add("log4j2-");
179        excludeJars.add("aliyun-java-sdk-");
180        excludeJars.add("aliyun-sdk-");
181        excludeJars.add("archaius-");
182        excludeJars.add("aopalliance-");
183        excludeJars.add("hdrhistogram-");
184        excludeJars.add("jdom-");
185        excludeJars.add("rxjava-");
186        excludeJars.add("jersey-");
187        excludeJars.add("stax-");
188        excludeJars.add("stax2-");
189        excludeJars.add("jettison-");
190        excludeJars.add("commonmark-");
191        excludeJars.add("jaxb-");
192        excludeJars.add("json-20");
193        excludeJars.add("jcseg-");
194        excludeJars.add("lucene-");
195        excludeJars.add("elasticsearch-");
196        excludeJars.add("jopt-");
197        excludeJars.add("httpasyncclient-");
198        excludeJars.add("jna-");
199        excludeJars.add("lang-mustache-client-");
200        excludeJars.add("parent-join-client-");
201        excludeJars.add("rank-eval-client-");
202        excludeJars.add("aggs-matrix-stats-client-");
203        excludeJars.add("t-digest-");
204        excludeJars.add("compiler-");
205        excludeJars.add("hppc-");
206        excludeJars.add("libthrift-");
207        excludeJars.add("seata-");
208        excludeJars.add("eureka-");
209        excludeJars.add("netflix-");
210        excludeJars.add("nacos-");
211        excludeJars.add("apollo-");
212        excludeJars.add("guice-");
213        excludeJars.add("servlet-");
214        excludeJars.add("debugger-agent.jar");
215        excludeJars.add("xpp3_min-");
216        excludeJars.add("latency");
217        excludeJars.add("micrometer-");
218        excludeJars.add("xstream-");
219        excludeJars.add("jsr311-");
220        excludeJars.add("servo-");
221        excludeJars.add("compactmap-");
222        excludeJars.add("dexx-");
223        excludeJars.add("spotbugs-");
224        excludeJars.add("xmlpull-");
225        excludeJars.add("shardingsphere-");
226        excludeJars.add("sentinel-");
227        excludeJars.add("spring-");
228        excludeJars.add("simpleclient-");
229        excludeJars.add("breeze-");
230        excludeJars.add("config-");
231        excludeJars.add("encrypt-core-");
232        excludeJars.add("lombok-");
233        excludeJars.add("hutool-");
234        excludeJars.add("jakarta.");
235        excludeJars.add("protostuff-");
236        excludeJars.add("poi-");
237        excludeJars.add("easypoi-");
238        excludeJars.add("ognl-");
239        excludeJars.add("xmlbeans-");
240        excludeJars.add("master-slave-core-");
241        excludeJars.add("shadow-core-rewrite-");
242        excludeJars.add("apiguardian-api-");
243        excludeJars.add("opentest4j-");
244        excludeJars.add("opentracing-");
245        excludeJars.add("freemarker-");
246        excludeJars.add("protobuf-");
247        excludeJars.add("jdom2-");
248        excludeJars.add("useragentutils-");
249        excludeJars.add("common-io-");
250        excludeJars.add("common-image-");
251        excludeJars.add("common-lang-");
252        excludeJars.add("imageio-");
253        excludeJars.add("curvesapi-");
254        excludeJars.add("myexcel-");
255        excludeJars.add("oshi-");
256        excludeJars.add("classmate-");
257        excludeJars.add("hibernate-");
258        excludeJars.add("aspectjweaver-");
259        excludeJars.add("aspectjrt-");
260        excludeJars.add("simpleclient_");
261        excludeJars.add("rocketmq-");
262        excludeJars.add("clickhouse-");
263        excludeJars.add("lz4-");
264        excludeJars.add("commons-digester-");
265        excludeJars.add("opencc4j-");
266        excludeJars.add("heaven-");
267        excludeJars.add("tinypinyin-");
268        excludeJars.add("jieba-");
269        excludeJars.add("ahocorasick-");
270        excludeJars.add("kotlin-");
271        excludeJars.add("xml-apis-");
272        excludeJars.add("dom4j-");
273        excludeJars.add("ini4j-");
274        excludeJars.add("cache-api-");
275        excludeJars.add("byte-buddy-");
276        excludeJars.add("jodd-");
277        excludeJars.add("redisson-");
278        excludeJars.add("bcprov-");
279        excludeJars.add("pay-java-");
280        excludeJars.add("alipay-sdk-");
281        excludeJars.add("mapper-extras-");
282        excludeJars.add("org.jacoco");
283        excludeJars.add("jxl-");
284        excludeJars.add("jxls-");
285        excludeJars.add("jstl-");
286        excludeJars.add("batik-");
287        excludeJars.add("xmlsec-");
288        excludeJars.add("pdfbox-");
289        excludeJars.add("fontbox-");
290        excludeJars.add("xk-time-");
291        excludeJars.add("geohash-");
292        excludeJars.add("ezmorph-");
293        excludeJars.add("async-http-");
294        excludeJars.add("jsr-");
295        excludeJars.add("jsr250");
296        excludeJars.add("pinyin4j");
297        excludeJars.add("ijpay-");
298        excludeJars.add("wildfly-");
299        excludeJars.add("liquibase-");
300        excludeJars.add("flowable-");
301        excludeJars.add("mybatis-");
302        excludeJars.add("ip2region-");
303        excludeJars.add("java-uuid-generator-");
304        excludeJars.add("quartz-");
305        excludeJars.add("elasticjob-");
306        excludeJars.add("reflections-");
307        excludeJars.add("jts-");
308        excludeJars.add("json-");
309        excludeJars.add("httpclient5-");
310        excludeJars.add("httpcore5-");
311        excludeJars.add("jul-to-");
312        excludeJars.add("calcite-");
313        excludeJars.add("avatica-");
314        excludeJars.add("encoder-");
315        excludeJars.add("aggdesigner-");
316        excludeJars.add("uzaygezen-");
317        excludeJars.add("memory-");
318        excludeJars.add("commons-");
319        excludeJars.add("accessors-");
320        excludeJars.add("sketches-");
321        excludeJars.add("h2-");
322        excludeJars.add("cosid-");
323        excludeJars.add("mchange-");
324        excludeJars.add("janino-");
325        excludeJars.add("jnanoid-");
326        excludeJars.add("proj4j-");
327        excludeJars.add("sparsebitset-");
328        excludeJars.add("captcha-");
329        excludeJars.add("cryptokit");
330        excludeJars.add("isec-");
331        excludeJars.add("jurt-");
332        excludeJars.add("minio-");
333        excludeJars.add("logging-");
334        excludeJars.add("simple-xml-");
335        excludeJars.add("jodconverter-");
336        excludeJars.add("credentials-");
337        excludeJars.add("unoil-");
338        excludeJars.add("endpoint-");
339        excludeJars.add("ridl-");
340        excludeJars.add("tencentcloud-");
341        excludeJars.add("yauaa-");
342        excludeJars.add("tea-");
343        excludeJars.add("fr.");
344        excludeJars.add("vod20");
345        excludeJars.add("juh-");
346        excludeJars.add("prefixmap-");
347        excludeJars.add("dmjdbcdriver");
348    }
349
350
351    public static void addUnscanClassPrefix(String prefix) {
352        excludeClasses.add(prefix.trim());
353    }
354
355    static {
356        excludeClasses.add("java.");
357        excludeClasses.add("javax.");
358        excludeClasses.add("junit.");
359        excludeClasses.add("jline.");
360        excludeClasses.add("redis.");
361        excludeClasses.add("lombok.");
362        excludeClasses.add("oshi.");
363        excludeClasses.add("jodd.");
364        excludeClasses.add("javassist.");
365        excludeClasses.add("google.");
366        excludeClasses.add("com.jfinal.");
367        excludeClasses.add("com.aliyuncs.");
368        excludeClasses.add("com.carrotsearch.");
369        excludeClasses.add("org.aopalliance.");
370        excludeClasses.add("org.apache.");
371        excludeClasses.add("org.nustaq.");
372        excludeClasses.add("net.sf.");
373        excludeClasses.add("org.slf4j.");
374        excludeClasses.add("org.antlr.");
375        excludeClasses.add("org.jboss.");
376        excludeClasses.add("org.checkerframework.");
377        excludeClasses.add("org.jsoup.");
378        excludeClasses.add("org.objenesis.");
379        excludeClasses.add("org.ow2.");
380        excludeClasses.add("org.reactivest.");
381        excludeClasses.add("org.yaml.");
382        excludeClasses.add("org.checker");
383        excludeClasses.add("org.codehaus");
384        excludeClasses.add("org.commonmark");
385        excludeClasses.add("org.jdom2.");
386        excludeClasses.add("org.aspectj.");
387        excludeClasses.add("org.hibernate.");
388        excludeClasses.add("org.ahocorasick.");
389        excludeClasses.add("org.lionsoul.jcseg.");
390        excludeClasses.add("org.ini4j.");
391        excludeClasses.add("org.jetbrains.");
392        excludeClasses.add("org.jacoco.");
393        excludeClasses.add("org.xnio.");
394        excludeClasses.add("org.bouncycastle.");
395        excludeClasses.add("org.elasticsearch.");
396        excludeClasses.add("org.hamcrest.");
397        excludeClasses.add("org.objectweb.");
398        excludeClasses.add("org.joda.");
399        excludeClasses.add("org.wildfly.");
400        excludeClasses.add("org.owasp.");
401        excludeClasses.add("aj.org.");
402        excludeClasses.add("ch.qos.");
403        excludeClasses.add("joptsimple.");
404        excludeClasses.add("com.alibaba.csp.");
405        excludeClasses.add("com.alibaba.nacos.");
406        excludeClasses.add("com.alibaba.druid.");
407        excludeClasses.add("com.alibaba.fastjson.");
408        excludeClasses.add("com.aliyun.open");
409        excludeClasses.add("com.caucho");
410        excludeClasses.add("com.codahale");
411        excludeClasses.add("com.ctrip.framework.apollo");
412        excludeClasses.add("com.ecwid.");
413        excludeClasses.add("com.esotericsoftware.");
414        excludeClasses.add("com.fasterxml.");
415        excludeClasses.add("com.github.");
416        excludeClasses.add("io.github.");
417        excludeClasses.add("com.google.");
418        excludeClasses.add("metrics_influxdb.");
419        excludeClasses.add("com.rabbitmq.");
420        excludeClasses.add("com.squareup.");
421        excludeClasses.add("com.sun.");
422        excludeClasses.add("com.typesafe.");
423        excludeClasses.add("com.weibo.api.motan.");
424        excludeClasses.add("com.zaxxer.");
425        excludeClasses.add("com.mysql.");
426        excludeClasses.add("com.papertrail.");
427        excludeClasses.add("com.egzosn.");
428        excludeClasses.add("com.alipay.api");
429        excludeClasses.add("org.gjt.");
430        excludeClasses.add("org.fusesource.");
431        excludeClasses.add("org.redisson.");
432        excludeClasses.add("io.dropwizard");
433        excludeClasses.add("io.prometheus");
434        excludeClasses.add("io.jsonwebtoken");
435        excludeClasses.add("io.lettuce");
436        excludeClasses.add("reactor.adapter");
437        excludeClasses.add("io.seata.");
438        excludeClasses.add("io.swagger.");
439        excludeClasses.add("io.undertow.");
440        excludeClasses.add("io.netty.");
441        excludeClasses.add("io.opentracing.");
442        excludeClasses.add("it.sauronsoftware");
443        excludeClasses.add("net.oschina.j2cache");
444        excludeClasses.add("net.bytebuddy");
445        excludeClasses.add("cn.hutool.");
446        excludeClasses.add("com.dyuproject.");
447        excludeClasses.add("io.protostuff.");
448        excludeClasses.add("io.reactivex.");
449        excludeClasses.add("freemarker.");
450        excludeClasses.add("com.twelvemonkeys.");
451        excludeClasses.add("eu.bitwalker.");
452        excludeClasses.add("jstl.");
453        excludeClasses.add("jxl.");
454        excludeClasses.add("org.jxls");
455        excludeClasses.add("org.kordamp");
456        excludeClasses.add("org.mybatis");
457        excludeClasses.add("org.lisonsoul");
458        excludeClasses.add("org.flowable");
459    }
460
461
462    public static void addScanClassPrefix(String prefix) {
463        scanClasses.add(prefix.toLowerCase().trim());
464    }
465
466    static {
467        scanClasses.add("io.jboot.support.shiro.directives");
468    }
469
470    static {
471        String scanJarPrefix = JbootConfigManager.me().getConfigValue("jboot.app.scanner.scanJarPrefix");
472        if (scanJarPrefix != null) {
473            String[] prefixes = scanJarPrefix.split(",");
474            for (String prefix : prefixes) {
475                if (prefix != null && prefix.trim().length() > 0) {
476                    addScanJarPrefix(prefix.trim());
477                }
478            }
479        }
480
481        String unScanJarPrefix = JbootConfigManager.me().getConfigValue("jboot.app.scanner.unScanJarPrefix");
482        if (unScanJarPrefix != null) {
483            String[] prefixes = unScanJarPrefix.split(",");
484            for (String prefix : prefixes) {
485                if (prefix != null && prefix.trim().length() > 0) {
486                    addUnscanJarPrefix(prefix.trim());
487                }
488            }
489        }
490
491        String unScanClassPrefix = JbootConfigManager.me().getConfigValue("jboot.app.scanner.unScanClassPrefix");
492        if (unScanClassPrefix != null) {
493            String[] prefixes = unScanClassPrefix.split(",");
494            for (String prefix : prefixes) {
495                if (prefix != null && prefix.trim().length() > 0) {
496                    addUnscanClassPrefix(prefix.trim());
497                }
498            }
499        }
500
501        String scanClassPrefix = JbootConfigManager.me().getConfigValue("jboot.app.scanner.scanClassPrefix");
502        if (scanClassPrefix != null) {
503            String[] prefixes = scanClassPrefix.split(",");
504            for (String prefix : prefixes) {
505                if (prefix != null && prefix.trim().length() > 0) {
506                    addScanClassPrefix(prefix.trim());
507                }
508            }
509        }
510
511    }
512
513    public static <T> List<Class<T>> scanSubClass(Class<T> pclazz) {
514        return scanSubClass(pclazz, false);
515    }
516
517
518    public static <T> List<Class<T>> scanSubClass(Class<T> pclazz, boolean instantiable) {
519        initIfNecessary();
520        List<Class<T>> classes = new ArrayList<>();
521        findChildClasses(classes, pclazz, instantiable);
522        return classes;
523    }
524
525    public static List<Class> scanClass() {
526        return scanClass(false);
527    }
528
529    public static List<Class> scanClass(boolean isInstantiable) {
530
531        initIfNecessary();
532
533        if (!isInstantiable) {
534            return new ArrayList<>(appClassesCache);
535        }
536
537        return scanClass(ClassScanner::isInstantiable);
538
539    }
540
541    public static List<Class> scanClass(Predicate<Class> filter) {
542
543        initIfNecessary();
544
545        return appClassesCache.stream()
546                .filter(filter)
547                .collect(Collectors.toList());
548
549    }
550
551    public static void clearAppClassesCache() {
552        appClassesCache.clear();
553    }
554
555
556    private static boolean isInstantiable(Class clazz) {
557        return !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers());
558    }
559
560
561    public static List<Class> scanClassByAnnotation(Class annotationClass, boolean instantiable) {
562        initIfNecessary();
563
564        List<Class> list = new ArrayList<>();
565        for (Class clazz : appClassesCache) {
566            Annotation annotation = clazz.getAnnotation(annotationClass);
567            if (annotation == null) {
568                continue;
569            }
570
571            if (instantiable && !isInstantiable(clazz)) {
572                continue;
573            }
574
575            list.add(clazz);
576        }
577
578        return list;
579    }
580
581    private static void initIfNecessary() {
582        if (appClassesCache.isEmpty()) {
583            initAppClasses();
584        }
585    }
586
587
588    private static <T> void findChildClasses(List<Class<T>> classes, Class<T> parent, boolean instantiable) {
589        for (Class clazz : appClassesCache) {
590
591            if (!parent.isAssignableFrom(clazz)) {
592                continue;
593            }
594
595            if (instantiable && !isInstantiable(clazz)) {
596                continue;
597            }
598
599            classes.add(clazz);
600        }
601    }
602
603
604    private static void initAppClasses() {
605
606        Set<String> jarPaths = new HashSet<>();
607        Set<String> classPaths = new HashSet<>();
608
609
610        // jdk8 及以下、
611        // tomcat 容器、
612        // jfinal-undertow、
613        // 以上三种加载模式通过 classloader 获取
614        findClassPathsAndJarsByClassloader(jarPaths, classPaths, ClassScanner.class.getClassLoader());
615
616        //jdk9+ 等其他方式通过 classpath 获取
617        findClassPathsAndJarsByClassPath(jarPaths, classPaths);
618
619
620        String tomcatClassPath = null;
621
622        for (String classPath : classPaths) {
623            //过滤tomcat自身的lib 以及 bin 下的jar
624            File tomcatApiJarFile = new File(classPath, "tomcat-api.jar");
625            File tomcatJuliJarFile = new File(classPath, "tomcat-juli.jar");
626            if (tomcatApiJarFile.exists() || tomcatJuliJarFile.exists()) {
627                tomcatClassPath = tomcatApiJarFile
628                        .getParentFile()
629                        .getParentFile().getAbsolutePath().replace('\\', '/');
630                continue;
631            }
632
633            if (isPrintScannerInfoEnable()) {
634                System.out.println("Jboot Scan ClassPath: " + classPath);
635            }
636
637            addClassesFromClassPath(classPath);
638        }
639
640        for (String jarPath : jarPaths) {
641
642            //过滤 tomcat 的 jar,但是不能过滤 webapps 目录下的
643            if (tomcatClassPath != null
644                    && jarPath.startsWith(tomcatClassPath)
645                    && !jarPath.contains("webapps")) {
646                continue;
647            }
648
649            if (!isIncludeJar(jarPath)) {
650                continue;
651            }
652
653            if (isPrintScannerInfoEnable()) {
654                System.out.println("Jboot Scan Jar: " + jarPath);
655            }
656
657            addClassesFromJar(jarPath);
658        }
659
660
661    }
662
663    private static void addClassesFromJar(String jarPath) {
664        JarFile jarFile = null;
665        try {
666            jarFile = new JarFile(jarPath);
667            Enumeration<JarEntry> entries = jarFile.entries();
668            while (entries.hasMoreElements()) {
669                JarEntry jarEntry = entries.nextElement();
670                String entryName = jarEntry.getName();
671                if (jarEntry.isDirectory() && entryName.startsWith("BOOT-INF/classes/")) {
672
673                    if (isPrintScannerInfoEnable()) {
674                        System.out.println("Jboot Scan entryName: " + entryName);
675                    }
676
677                    if (entryName.endsWith(".class")) {
678                        String className = entryName.replace("/", ".").substring(0, entryName.length() - 6);
679                        addClass(classForName(className));
680                    }
681                } else {
682                    if (entryName.endsWith(".class")) {
683                        String className = entryName.replace("/", ".").substring(0, entryName.length() - 6);
684                        addClass(classForName(className));
685                    } else if (entryName.startsWith("BOOT-INF/lib/") && entryName.endsWith(".jar")) {
686                        if (!isIncludeJar(entryName)) {
687                            continue;
688                        }
689
690                        if (isPrintScannerInfoEnable()) {
691                            System.out.println("Jboot Scan Jar: " + entryName);
692                        }
693
694                        try (JarInputStream jarStream = new JarInputStream(jarFile
695                                .getInputStream(jarEntry));) {
696                            JarEntry innerEntry = jarStream.getNextJarEntry();
697                            while (innerEntry != null) {
698                                if (!innerEntry.isDirectory()) {
699                                    String nestedEntryName = innerEntry.getName();
700                                    if (nestedEntryName.endsWith(".class")) {
701                                        String className = nestedEntryName.replace("/", ".").substring(0, nestedEntryName.length() - 6);
702                                        addClass(classForName(className));
703                                    }
704                                }
705                                innerEntry = jarStream.getNextJarEntry();
706                            }
707                        }
708                    }
709                }
710            }
711        } catch (IOException e1) {
712        } finally {
713            if (jarFile != null) {
714                try {
715                    jarFile.close();
716                } catch (IOException e) {
717                }
718            }
719        }
720    }
721
722
723    private static void addClassesFromClassPath(String classPath) {
724
725        List<File> classFileList = new ArrayList<>();
726        scanClassFile(classFileList, classPath);
727
728        for (File file : classFileList) {
729
730            int start = classPath.length();
731            int end = file.toString().length() - ".class".length();
732
733            String classFile = file.toString().substring(start + 1, end);
734            String className = classFile.replace(File.separator, ".");
735
736            addClass(classForName(className));
737        }
738    }
739
740    private static void addClass(Class clazz) {
741        if (clazz != null && isNotExcludeClass(clazz.getName())) {
742            appClassesCache.add(clazz);
743        }
744    }
745
746    //用于在进行 fatjar 打包时,提高性能
747    private static boolean isNotExcludeClass(String clazzName) {
748        for (String prefix : scanClasses) {
749            if (clazzName.startsWith(prefix)) {
750                return true;
751            }
752        }
753        for (String prefix : excludeClasses) {
754            if (clazzName.startsWith(prefix)) {
755                return false;
756            }
757        }
758        return true;
759    }
760
761
762    private static void findClassPathsAndJarsByClassloader(Set<String> jarPaths, Set<String> classPaths, ClassLoader classLoader) {
763        try {
764            URL[] urls = null;
765            if (classLoader instanceof URLClassLoader) {
766                URLClassLoader ucl = (URLClassLoader) classLoader;
767                urls = ucl.getURLs();
768            }
769            if (urls != null) {
770                for (URL url : urls) {
771                    String path = url.getPath();
772                    path = URLDecoder.decode(path, "UTF-8");
773
774                    // path : /d:/xxx
775                    if (path.startsWith("/") && path.indexOf(":") == 2) {
776                        path = path.substring(1);
777                    }
778
779                    if (!path.toLowerCase().endsWith(".jar")) {
780                        if (path.toLowerCase().endsWith("!/") || path.toLowerCase().endsWith("!")) {
781                        } else {
782                            classPaths.add(new File(path).getCanonicalPath().replace('\\', '/'));
783                        }
784                    } else {
785                        jarPaths.add(new File(path).getCanonicalPath().replace('\\', '/'));
786                    }
787                }
788            }
789        } catch (Exception ex) {
790            ex.printStackTrace();
791        }
792
793        ClassLoader parent = classLoader.getParent();
794        if (parent != null) {
795            findClassPathsAndJarsByClassloader(jarPaths, classPaths, parent);
796        }
797    }
798
799    private static void findClassPathsAndJarsByClassPath(Set<String> jarPaths, Set<String> classPaths) {
800        String classPath = System.getProperty("java.class.path");
801        if (classPath == null || classPath.trim().length() == 0) {
802            return;
803        }
804        String[] classPathArray = classPath.split(File.pathSeparator);
805        if (classPathArray == null || classPathArray.length == 0) {
806            return;
807        }
808        for (String path : classPathArray) {
809            path = path.trim();
810
811            if (path.startsWith("./")) {
812                path = path.substring(2);
813            }
814
815            if (path.startsWith("/") && path.indexOf(":") == 2) {
816                path = path.substring(1);
817            }
818            try {
819                if (!path.toLowerCase().endsWith(".jar") && !jarPaths.contains(path)) {
820                    if (path.toLowerCase().endsWith("!/") || path.toLowerCase().endsWith("!")) {
821                    } else {
822                        classPaths.add(new File(path).getCanonicalPath().replace('\\', '/'));
823                    }
824                } else {
825                    jarPaths.add(new File(path).getCanonicalPath().replace('\\', '/'));
826                }
827            } catch (IOException e) {
828            }
829        }
830    }
831
832
833    private static boolean isIncludeJar(String path) {
834
835        String jarName = new File(path).getName().toLowerCase();
836
837        for (String include : scanJars) {
838            if (jarName.startsWith(include)) {
839                return true;
840            }
841        }
842
843        for (String exclude : excludeJars) {
844            if (jarName.startsWith(exclude)) {
845                return false;
846            }
847        }
848
849        //from jre lib
850        if (path.contains("/jre/lib")) {
851            return false;
852        }
853
854        //from java home
855        if (getJavaHome() != null
856                && path.startsWith(getJavaHome())) {
857            return false;
858        }
859
860        return true;
861    }
862
863
864    @SuppressWarnings("unchecked")
865    private static Class classForName(String className) {
866        try {
867            ClassLoader cl = Thread.currentThread().getContextClassLoader();
868            return Class.forName(className, false, cl);
869        } catch (Throwable ex) {
870            //ignore
871        }
872        return null;
873    }
874
875
876    private static void scanClassFile(List<File> fileList, String path) {
877        File[] files = new File(path).listFiles();
878        if (null == files || files.length == 0) {
879            return;
880        }
881        for (File file : files) {
882            if (file.isDirectory()) {
883                scanClassFile(fileList, file.getAbsolutePath());
884            } else if (file.getName().endsWith(".class")) {
885                fileList.add(file);
886            }
887        }
888    }
889
890
891    private static String javaHome;
892
893    private static String getJavaHome() {
894        if (javaHome == null) {
895            try {
896                String javaHomeString = System.getProperty("java.home");
897                if (javaHomeString != null && javaHomeString.trim().length() > 0) {
898                    javaHome = new File(javaHomeString, "..").getCanonicalPath().replace('\\', '/');
899                }
900            } catch (Exception e) {
901                e.printStackTrace();
902            }
903        }
904        return javaHome;
905    }
906
907}