框架扫描并加载类的原理

框架扫描并加载类的原理

在spring配置文件中,有个component-scan标签,可以扫描带有@Controller,@Service,@Component,@Repository等注解,
并且把带着些注解的类注册为一个bean。配置截图
这里的原理就是用当前线程的ClassLoader来获取classpath的位置,然后对classpath中的字节码文件,把类进行加载,加载为Class类型的反射对象,接着判断是否含有那些注解,如果有就把他们进行不同的处理,注册为一个个不同的bean。最后当检查到有@Resource或者@Autowired时,把这些获取到并且已经处理好的bean进行注入。下面我来分享一下我写的一个简易的扫描class和jar并且获取到这些文件中全部类的方法。
下面这个代码可以扫描classpath和jar中全部的类,并且获取到反射对象:

package com.rabit.classes.scanner;

import org.apache.log4j.helpers.LogLog;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 扫描指定包下的全部类
 */
public class PackageScanner {
    private List<String> classPaths = new ArrayList<String>();
    private String basePack;
    public PackageScanner(String basePack){
        LogLog.setInternalDebugging(true);
        this.basePack=basePack;
    }
    public List<Class> scan() throws IOException {
        List<Class> list=new ArrayList<>();
        System.out.println("==============开始扫描啦====================");
        System.out.println(basePack);
        Enumeration<URL> urlEnumeration = Thread.currentThread().getContextClassLoader().getResources(basePack.replace(".", "/"));
        System.out.println(urlEnumeration);
        while (urlEnumeration.hasMoreElements()) {
            URL url = urlEnumeration.nextElement();//如果是jar得到的结果大概是:jar:file:/D:maven/.m2/xxx.jar反之,则是file:classes/xxx.class
            System.out.println(url);
            String protocol = url.getProtocol();//获取标识,后面通过判断是否是jar:file来进行不同的处理
            System.out.println(protocol);
            if ("jar".equalsIgnoreCase(protocol)) {
                //转换为JarURLConnection
                JarURLConnection connection = (JarURLConnection) url.openConnection();
                if (connection != null) {
                    JarFile jarFile = connection.getJarFile();
                    if (jarFile != null) {
                        //得到该jar文件下面的类实体
                        Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
                        while (jarEntryEnumeration.hasMoreElements()) {
                            //获取到的class文件在jar中的路径:xx/xxx/xxx
                            JarEntry entry = jarEntryEnumeration.nextElement();
                            String jarEntryName = entry.getName();
                            //这里我们需要过滤不是class文件和不在basePack包名下的类
                            if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/",".").startsWith(basePack)) {
                                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
                                System.out.println(className);
                                try{
                                    Class cls = Class.forName(className);
                                    list.add(cls);
                                }catch (Exception|Error ex){//报错我们直接记录,然后不直接抛异常
                                    LogLog.error(className+" is not found");
                                    LogLog.error(ex.getMessage());
                                }
                            }
                        }
                    }
                }
            }else {
                //然后把我们的包名basPach转换为路径名
                //然后把classpath和basePack合并
                String searchPath = URLDecoder.decode(url.getPath(),"UTF-8");//注意,直接获取的path中间会有乱码,需要转为utf-8
                doPath(new File(searchPath));
                //这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象
                for (String s : classPaths) {
                    LogLog.debug(s);
                    //s是获取到这个class文件的路径:d:ideal/project/test/classes/com/rabit/classes/scanner/PackageScanner.class
                    s = s.substring(s.indexOf(File.separator+"classes") + 9, s.lastIndexOf(".")).replace(File.separator, ".").replace("/",".").replace("\\",".");
                    //我们利用这两步把路径通过replace和截取子串的办法把路径转化为类路径:com.rabit.classes.scanner.PackageScanner
                    LogLog.debug(s);
                    try{
                        Class cls = Class.forName(s);
                        list.add(cls);
                    }catch(Exception|Error ex){//报错我们直接记录,然后不直接抛异常
                        LogLog.error(s+" is not found");
                        LogLog.error(ex.getMessage());
                    }
                }
            }
        }
        return list;
    }
    /**
     * 该方法会得到所有的类,将类的绝对路径写入到classPaths中
     * @param file
     */
    private void doPath(File file) {
        if (file.isDirectory()) {//文件夹
            //文件夹我们就递归
            File[] files = file.listFiles();
            for (File f1 : files) {
                doPath(f1);
            }
        } else {//标准文件
            //标准文件我们就判断是否是class文件
            if (file.getName().endsWith(".class")) {
                //如果是class文件我们就放入我们的集合中。
                classPaths.add(file.getPath());
            }
        }
    }
}

通过上面的方法,我们可以获取到类的反射对象,接下来的是筛选这些类对象的过程:

package com.rabit.classes.scanner;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * 将包含xxx注解的类过滤出来
 */
public class ClassGetter {
    private List<Class> classList=new ArrayList<>();//扫描到的class反射类
    private List<Class> fClasses=new ArrayList<>();//需要过滤的注解,只有带这些注解的类才会被加载
     /**
     * 加载过滤器
     * @param fClasses 扫描到的class反射类
     * @param annotationClasses 需要过滤的注解,只有带这些注解的类才会被加载
     */
    public ClassGetter(List<Class> fClasses, Class... annotationClasses){
        this.classList= Arrays.asList(annotationClasses);
        this.fClasses=fClasses;
    }
    public List<Class> get(){
        List<Class> a=fClasses.stream().map((x)->{
            AtomicBoolean b= new AtomicBoolean(false);
            Arrays.stream(x.getAnnotations()).map(Annotation::annotationType).forEach((z)->{
                if(classList.contains(z))
                 b.set(true);return;
            });
            //这里是对被扫描到的反射类进行遍历,获取到反射类所包含的注解列表然后遍历注解列表,使用contains判断需要被过滤的注解是否在这里面,在就设置boolean原子为true,然后直接退出函数(注意,退出的是在里面的(z)->的lambda函数)
            if(b.get()){
                return x;//结果为true,则把这个class反射类装入stream
            }
            return null;//反之则返回null
        }).filter(Objects::nonNull).collect(Collectors.toList());//最后过滤掉为null的值,然后转换stream为list
        return a;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值