天天看点

利用Spring实现自己的类扫描机制

    需求是这样的,要做一个类似前置交易规则的东西。就是在交易执行前,添加一些前置处理逻辑。这些处理逻辑由代码实现。可以随意添加也可以移除。

    设计的方案就是使用注解+包扫描。也就是指定一个包名,然后对包下面类扫描,看看是否带上指定的注解。条件都满足,那么将这个类构造(new)一个对象放入前置处理规则集中。在这个机制下,规则类可以根据需要不断的增加,存放地点可以靠包名来指定,如果要废弃一个规则,也可以直接去掉注解的声明,无论怎么做,处理权都在编码手中,比较灵活。

    这个方案有两个关键点:1、包扫描,2、构造规则对象。

    1、包扫描要考虑两种可能,一个是在classes目录下,一个是在jar文件中。

   方案一、直接借鉴网上的包扫描比较底层的代码,全手工做文件夹目录扫描,和解析jar文件,和一层层扫描下去。

       String packageDirName = packageName.replace('.', '/');  

       Enumeration<URL> dirs=Thread.currentThread().getContextClassLoader().getResources(packageDirName);  

   上面2行代码就可以完成给定包名,获取包所在路径的结果集。这些都是JDK的JVM规范就搞定的。

   在这个地方我一直就以为URL就两种形式:file:/ 和jar!/,一个文件目录,一个jar包。剩下就是递归扫描文件目录和JarEntry。

       URL url = dirs.nextElement();

       String protocol = url.getProtocol();  

       if ("file".equals(protocol)){//文件扫描

            String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); 

           File[] dirfiles = filePath .listFiles();//获取目录下的文件,需要递归处理文件夹

       }  

      if ("jar".equals(protocol)) {  

          JarFile jar= ((JarURLConnection) url.openConnection()).getJarFile(); 

          Enumeration<JarEntry> entries = jar.entries();//获取文件,需要递归处理文件夹

      }

    这部分代码我在Windows环境下的tomcat测试通过了,正确的添加了class文件。但是在linux weblogic环境下测试失败,无法添加jar中的文件。因为发现protocol竟然是zip格式的,而且url解析出来了weblogic自己封装的ZipURLConnection,完全没法搞。

    这个时候只有祭出Spring这个强大的武器。因为spring肯定对所有环境都能做到class扫描,现在的问题是如何分析强大component-scan标签,到底是哪些类在为这个标签服务。

    方案二,使用Spring的component-scan代码完成。幸好网上有很多帖子分析了spring的包扫描源码。

    Spring实现<context:component-scanbase-package="com.xxx" />这个功能有几个关键的代码接口:

      1. XmlBeanDefinitionReader.loadBeanDefinitions(Resource ) // 解析xml文件,将compoent-scan也作为一个bean注册到容器中,并初始化这个Bean

      2. ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext)//将compoent-scan标签中的配置进行分析

      3. ClassPathBeanDefinitionScanner.doScan(String... basePackages)//解析包路径(可能有多个),扫描包下所有的符合条件的class(声明了@Component注解)

      4. ClassPathBeanDefinitionScanner.findCandidateComponents(String basePackage)//将包名转换为class路径表达式,查找改该包下所有class

      5. PathMatchingResourcePatternResolver.getResources(String locationPattern)//在指定路径下,找到所有classes

    这几个关键类,串起了整个Spring包扫描结构。关于protocol的判定,在PathMatchingResourcePatternResolver实现了:

利用Spring实现自己的类扫描机制

   ResourceUtils.isJarURL(URL url)中明确:

利用Spring实现自己的类扫描机制

  从这部分代码可以看到,jar 文件的处理分为了很多种,有weblogic和websphere,还有oralce方式的,哎,还真的孤陋寡闻啊。既然Spring已经完成的可考虑,我们就直接使用好了: 

利用Spring实现自己的类扫描机制

 从Spring源码中copy一些代码出来,然后直接使用PathMatchingResourcePatternResolver完成路径解析,然后自己写代码添加class文件,就大功告成。

2、构造规则对象

   包下所有的class文件已经找到,如何过滤出自己需要的class,又如何保证能完成初始化。我定义了一个规则注解,和一个规则抽象类,这样可以保证既扫描出自己需要的类,也能保证类的行为受到约束: 

利用Spring实现自己的类扫描机制

首先需要判断class是否是声明了注解,然后判断是否extends规则抽象类,这样就可以得到自己所需要的类集合

然后构造这些类对象:

利用Spring实现自己的类扫描机制

OK,整个设计实现就已经完成了,有了这个机制,关于如何动态灵活加载,就可以有了更多的遐想了,可以在很多地方进行扩展。

Spring的整个类加载就是兼容了很多种protocol,关键的还是Thread.currentThread().getContextClassLoader().getResources(packageDirName),其他就是在这个上面搭积木了,当然肯定不是每个人都能搭建的和Spring一样完善,所以以后做东西的时候,一定要想想有没有可以从Spring项目上挖掘的。

继续阅读