01.实现ResourceResolver

本文介绍了如何使用注解在Spring框架中实现对指定包内Class文件的扫描,通过ResourceResolver类配合Java类加载器,以支持通过Function接口映射资源。展示了如何通过扫描找到.class文件并转换为类名,以便于后续的依赖注入和管理。
摘要由CSDN通过智能技术生成

title: 01.实现ResourceResolver
tag: 笔记 手写SSM IOC

扫描指定包下的所有Class文件

IOC管理对象

我们知道IOC容器最主要的内容是管理对象的创建和依赖关系的维护,因此我们在实现IOC容器之前我们需要知道哪些类的对象是需要IOC容器管理的。我们学过spring知道将对象交由IOC容器管理通常有两种方式:

  • 通过XML配置文件,使用< beans > 等标签将对象注册到IOC容器中进行管理。

这样的方式我们可以通过将XML文件传入ApplicationContext核心类(也可以是BeanFactory)中,即可通过getBean方法拿到对应创建完成的类:

ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
Object bean = ctx.getBean("beanName");
  • Java引入注解后,我们可以完全依赖注解来实现Bean的注册:
@Configuration
@ComponentScan("com.duan")
public class SpringConfig {

}

我们可以创建一个spring的配置类来配置扫描Bean的包,spring就会把包下带有特定注解的类加入到IOC容器中管理:

@Component("BookService")
public class BookServiceImpl implements BookService {
}

这样我们就可以在将spring的配置类传入ApplicationContext中,同样可以实现IOCbean的管理。

目前注解开发是最常用的方式,因此我们选择注解来实现注册bean到容器,因此我们首先需要实现扫描包下的所有类文件

扫描包下的类文件

Java的类加载器ClassLoader可以通过指定类名来加载指定的Class文件,但不能根据包名来加载包下的所有Class文件。

我们知道Class文件是Java源代码编译后的文件,经过maven编译后存在target目录中,这个路径我们叫做classpath,也就是存放class文件的路径。因此我们需要的就是去classpath中去扫描符合扫描包路径的class文件。

例如,Classpath中搜索的文件org/example/Hello.class就符合包名org.example,我们需要根据文件路径把它变为org.example.Hello,就相当于获得了类名。因此,搜索Class变成了搜索文件。

实现ResourceResolver

Resource类

我们先定义一个Resource类型表示文件:

public record Resource(String path, String name) {
}

ResourceResolver类

再定义一个ResourceResolver 来对传入的路径进行扫描。

public class ResourceResolver {
    String basePackage;

    public ResourceResolver(String basePackage) {
        this.basePackage = basePackage;
    }
	//扫描Resourse
    public <R> List<R> scan(Function<Resource, R> mapper) {
        String basePackagePath = this.basePackage.replace(".", "/");
        try {
            List<R> collector = new ArrayList<>();
            scan0(basePackagePath, collector, mapper);
            return collector;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 我们先将传入的包路径中的".“替换为”/"。
  • 我们通过传入的Function<Resource, R> mapper函数式接口来对扫描到的Resource进行映射。这样,ResourceResolver只负责扫描并列出所有文件,由客户端决定是找出.class文件,还是找出.properties文件。
  • 我们使用collector集合来收集扫描出的资源。

下面我们看scan0函数的实现:

<R> void scan0(String basePackagePath,List<R> collector, Function<Resource, R> mapper)
        throws IOException, URISyntaxException {
    logger.info("scan path:{}" , basePackagePath);
    //获取
    Enumeration<URL> resources = getContextClassLoader().getResources(basePackagePath);
    while (resources.hasMoreElements()){
        URI uri = resources.nextElement().toURI();
        //处理uri为字符串
        String uriStr = removeTrailingSlash(uriToString(uri));
        //拿到存放class文件的目录的路径
        String classPath = uriStr.substring(0, uriStr.length() - basePackagePath.length());
        //将“file:删除”
        if (classPath.startsWith("file:")) {
            classPath = classPath.substring(5);
        }
        //jar和file做不同处理
        if (uriStr.startsWith("jar:")) {
            scanFile(true, classPath, jarUriToPath(basePackagePath, uri), collector, mapper);
        } else {
            scanFile(false, classPath, Paths.get(uri), collector, mapper);
        }
    }
}

ClassLoader getContextClassLoader() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if(classLoader == null) classLoader = getClass().getClassLoader();
        return classLoader;
}
  • ClassLoader首先从Thread.getContextClassLoader()获取,如果获取不到,再从当前Class获取,因为Web应用的ClassLoader不是JVM提供的基于Classpath的ClassLoader,而是Servlet容器提供的ClassLoader,它不在默认的Classpath搜索,而是在/WEB-INF/classes目录和/WEB-INF/lib的所有jar包搜索,从Thread.getContextClassLoader()可以获取到Servlet容器专属的ClassLoader;
  • 拿到ClassLoader后我们就可以通过ClassLoader调用getResources(basePackagePath),就可以获取到存放basePackagePathClass文件的URI
  • 我们再将URI转换为字符串后将basePackagePath截去之后就可以得到classpath
  • 接下来我们就可以根据文件是file或者jar来进行文件扫描scanFile

下面是scanFile方法的实现,该类用于遍历指定包root下的所有class文件:

<R> void scanFile(boolean isJar, String classPath, Path root, List<R> collector,
                       Function<Resource, R> mapper) throws IOException {
    logger.info("classPath:{},root:{}", classPath, root);
    //去除最后的斜杠
    classPath = removeTrailingSlash(classPath);
    //try-with-resource保证pathStream资源释放
    try(Stream<Path> pathStream = Files.walk(root)) {
        String finalClassPath = classPath;
        pathStream.filter(Files::isRegularFile)
                .forEach(file -> {
                    Resource resource;
                    //实例化Resource
                    if(isJar){
                        resource = new Resource(finalClassPath, removeTrailingSlash(file.toString()));
                    }else {
                        String path = file.toString();
                        String name = removeLeadingSlash(path.substring(finalClassPath.length()));
                        resource = new Resource("file:" + path, name);
                    }
                    logger.atDebug().log("found resource: {}", resource);
                    //通过传入的函数式表达式来传入想要获取的文件
                    R r = mapper.apply(resource);
                    if (r != null) {
                        collector.add(r);
                    }
                });
    }
}
  • Files.walk(root):会生成一个file的stream流,来对路径下的全部文件进行遍历。

测试

编写测试就对刚才编写的两个类的包进行扫描

public static void main(String[] args) {
    ResourceResolver rr = new ResourceResolver("com.duan.framework.io");
    List<String> classList = rr.scan(res -> {
        String name = res.name(); // 资源名称"com/duan/framework/io/Resource.class"
        if (name.endsWith(".class")) { // 如果以.class结尾
            // 把"org/example/Hello.class"变为"org.example.Hello":
            return name.substring(0, name.length() - 6).replace("/", ".").replace("\\", ".");
        }
        // 否则返回null表示不是有效的Class Name:
        return null;
    });
}

输出:

22:31:59.000 [main] INFO com.duan.framework.io.ResourceResolver -- scan path:com/duan/framework/io
22:31:59.004 [main] INFO com.duan.framework.io.ResourceResolver -- classPath:/E:/JAVACODE/Spring-Simple/target/classes/,root:E:\JAVACODE\Spring-Simple\target\classes\com\duan\framework\io
22:31:59.009 [main] DEBUG com.duan.framework.io.ResourceResolver -- found resource: Resource[path=file:E:\JAVACODE\Spring-Simple\target\classes\com\duan\framework\io\Resource.class, name=com\duan\framework\io\Resource.class]
22:31:59.022 [main] DEBUG com.duan.framework.io.ResourceResolver -- found resource: Resource[path=file:E:\JAVACODE\Spring-Simple\target\classes\com\duan\framework\io\ResourceResolver.class, name=com\duan\framework\io\ResourceResolver.class]
[com.duan.framework.io.Resource, com.duan.framework.io.ResourceResolver]

从日志的最后一行来看,我们确实获取到了该包下的类的包路径。这样我们就可以通过反射forName来获取该类的Class对象,就可以进行下面的操作了。

an\framework\io\ResourceResolver.class]
[com.duan.framework.io.Resource, com.duan.framework.io.ResourceResolver]


从日志的最后一行来看,我们确实获取到了该包下的类的包路径。这样我们就可以通过**反射**的`forName`来获取该类的Class对象,就可以进行下面的操作了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值