Spring如何扫描class和配置文件

来源:互联网 发布:php从入门到精通下载 编辑:程序博客网 时间:2024/05/22 05:16

       前几天由于公司项目架构调整,想将以前代码开发为主改成配置文件配置为主,即所有的外部服务调用由配置文件组织,因为必须高效,所以涉及包括调用顺序,并发调用等,但配置文件的缺陷是只能实现简单的业务逻辑,所以我们还用了jeval表达式Jar包。

       废话不多说,由于服务配置文件是放在Maven项目下的一个子模块的classpath下,该子模块在eclipse下运行是以用文件系统路径来扫描到并解析的,但在线上环境,该子模块是会被打成Jar包,就是说线上环境是需要解析该子模块的Jar包才能取到配置文件的。

       Jar包本质上是压缩文件,以前也做个在压缩文件中解析配置文件,但感觉不太专业,由于时间赶,不想在网上捞资料,而且靠不靠谱也不一定,于是想到了借鉴Spring中的扫描和解析配置文件的功能代码

     (转载请注明出处:http://manzhizhen.iteye.com/blog/2244806)

       我们经常用如下Spring配置来解析资源文件和扫描class:

       <context:component-scan        base-package="com.manzhizhen.server.service,com.manzhizhen.server.aop" />

       <bean
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:conf/resource1.properties</value>
            </list>
        </property>
    </bean>

        我本地已经有Spring4的源码,于是我直接在源码中搜索base-package关键字,于是定位到ComponentScanBeanDefinitionParser类:

 

public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {    private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
 然后我搜索哪些类用到了BASE_PACKAGE_ATTRIBUTE,于是找到了ComponentScanBeanDefinitionParser类:

 

 

public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {     ... ...@Overridepublic BeanDefinition parse(Element element, ParserContext parserContext) {String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(<strong>BASE_PACKAGE_ATTRIBUTE</strong>),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);// Actually scan for bean definitions and register them.ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);registerComponents(parserContext.getReaderContext(), beanDefinitions, element);return null;}
 ComponentScanBeanDefinitionParser类的作用就是将解析来的xml元素转换成Bean定义,并将他们注册到上下文中,所以我可以从这里开始追踪Spring是如何根据我们定义的class路径去扫描class文件的。
       其中,element.getAttribute(BASE_PACKAGE_ATTRIBUTE)的值就是我们配置的"com.manzhizhen.server.service,com.manzhizhen.server.aop",而ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS只是在Spring中预定义的配置路径分隔符而已,比如“.;\t\n”,最后经过分隔,得到的String[] basePackages就是com.manzhizhen.server.service和com.manzhizhen.server.aop组成的字符串列表了。

 

        我们发现,代码Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);就已经把我们配置的两个package下的所有class解析出来了,所以我决定看看scanner.doScan(basePackages)里面到底做了什么,于是我们来到了ClassPathBeanDefinitionScanner#doScan

 

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();for (String basePackage : basePackages) {<strong>Set<BeanDefinition> candidates = findCandidateComponents(basePackage);</strong>for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}
 由于上面加黑的代码就已经将class扫描出来了,于是去看看findCandidateComponents方法是怎么实现的:

 

/** * Scan the class path for candidate components. * @param basePackage the package to check for annotated classes * @return a corresponding Set of autodetected bean definitions */public Set<BeanDefinition> findCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + "/" + this.resourcePattern;Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}candidates.add(sbd);}else {if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}}else {if (traceEnabled) {logger.trace("Ignored because not matching any filter: " + resource);}}}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}}else {if (traceEnabled) {logger.trace("Ignored because not readable: " + resource);}}}}catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;}

 代码String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + "/" + this.resourcePattern;
将我们的包路径组装成Spring中能识别的格式,如把 “com.manzhizhen.server.service” 变成 "classpath*:com.manzhizhen.server.service/**/*.class",对,就是对前后做了补充,给后面的统一解析操作提供必要的指引。我们发现代码Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);就已经将所有的class扫描出来了,于是我们看看里面做了些什么,于是追踪到了GenericApplicationContext#getResources

/** * This implementation delegates to this context's ResourceLoader if it * implements the ResourcePatternResolver interface, falling back to the * default superclass behavior else. * @see #setResourceLoader */@Overridepublic Resource[] getResources(String locationPattern) throws IOException {if (this.resourceLoader instanceof ResourcePatternResolver) {return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern);}return <strong>super.getResources(locationPattern);</strong>}
 加黑部分,发现它是调了父类的方法AbstractApplicationContext#getResources

 

 

public Resource[] getResources(String locationPattern) throws IOException {return <strong>this.resourcePatternResolver.getResources(locationPattern);</strong>}
 this.resourcePatternResolver PathMatchingResourcePatternResolver类的对象,我们看看它的getResources 方法:
public Resource[] getResources(String locationPattern) throws IOException {Assert.notNull(locationPattern, "Location pattern must not be null");if (<strong>locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)</strong>) {// a class path resource (multiple resources for same name possible)if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {// a class path resource patternreturn findPathMatchingResources(locationPattern);}else {// all class path resources with the given namereturn findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));}}else {// Only look for a pattern after a prefix here// (to not get fooled by a pattern symbol in a strange prefix).int prefixEnd = locationPattern.indexOf(":") + 1;if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {// a file patternreturn findPathMatchingResources(locationPattern);}else {// a single resource with the given namereturn new Resource[] {getResourceLoader().getResource(locationPattern)};}}}
 CLASSPATH_ALL_URL_PREFIX是 PathMatchingResourcePatternResolver 的实现接口 ResourcePatternResolver 中定义的常量:
/** * Pseudo URL prefix for all matching resources from the class path: "classpath*:" * This differs from ResourceLoader's classpath URL prefix in that it * retrieves all matching resources for a given name (e.g. "/beans.xml"), * for example in the root of all deployed JAR files. * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX */String <strong>CLASSPATH_ALL_URL_PREFIX</strong> = "classpath*:";
 其值就是前面Spring给包路径加的前缀。
        我们回到 PathMatchingResourcePatternResolver#getResources的那段代码,继续往下看,getPathMatcher() 返回的是 AntPathMatcher 类的对象,咱们看看它的 isPattern 方法:
public boolean isPattern(String path) {return (path.indexOf('*') != -1 || path.indexOf('?') != -1);}
 由于前面Spring对包路径的加工,我们很幸运的就匹配上了,于是我们进入了下面的findPathMatchingResources(locationPattern); 方法,我们看看实现:
/** * Find all resources that match the given location pattern via the * Ant-style PathMatcher. Supports resources in jar files and zip files * and in the file system. * @param locationPattern the location pattern to match * @return the result as Resource array * @throws IOException in case of I/O errors * @see #doFindPathMatchingJarResources * @see #doFindPathMatchingFileResources * @see org.springframework.util.PathMatcher */protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {String rootDirPath = determineRootDir(locationPattern);String subPattern = locationPattern.substring(rootDirPath.length());Resource[] rootDirResources = getResources(rootDirPath);Set<Resource> result = new LinkedHashSet<Resource>(16);for (Resource rootDirResource : rootDirResources) {rootDirResource = resolveRootDirResource(rootDirResource);if (isJarResource(rootDirResource)) {result.addAll(<strong>doFindPathMatchingJarResources</strong>(rootDirResource, subPattern));}else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));}else {result.addAll(<strong>doFindPathMatchingFileResources</strong>(rootDirResource, subPattern));}}if (logger.isDebugEnabled()) {logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);}return result.toArray(new Resource[result.size()]);}
 第一行String rootDirPath = determineRootDir(locationPattern);  得到的 rootDirPath值为“classpath*:com/kuaidadi/liangjian/allconfig/server/service/”, 即资源文件根目录。第二行String subPattern = locationPattern.substring(rootDirPath.length()); 得到的subPattern “**/*.class”,即需要扫描的资源文件类型。接下来的 Resource[] rootDirResources = getResources(rootDirPath);将该资源根路径解析成Spring中的资源对象。 其实 getResources findPathMatchingResources 之间会相互调用。请看上面代码我对两个方法进行了加黑:doFindPathMatchingJarResourcesdoFindPathMatchingFileResources,这两个方法分别完成Jar包和文件系统资源的扫描工作,doFindPathMatchingFileResources方法实现比较简单,文件系统的读取大家都会,咱们看看Spring是如何解析Jar包中的资源的,doFindPathMatchingJarResources 方法源码如下:
/** * Find all resources in jar files that match the given location pattern * via the Ant-style PathMatcher. * @param rootDirResource the root directory as Resource * @param subPattern the sub pattern to match (below the root directory) * @return the Set of matching Resource instances * @throws IOException in case of I/O errors * @see java.net.JarURLConnection * @see org.springframework.util.PathMatcher */protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)throws IOException {URLConnection con = rootDirResource.getURL().openConnection();JarFile jarFile;String jarFileUrl;String rootEntryPath;boolean newJarFile = false;if (con instanceof JarURLConnection) {// Should usually be the case for traditional JAR files.JarURLConnection jarCon = (JarURLConnection) con;jarCon.setUseCaches(false);jarFile = jarCon.getJarFile();jarFileUrl = jarCon.getJarFileURL().toExternalForm();JarEntry jarEntry = jarCon.getJarEntry();rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");}else {// No JarURLConnection -> need to resort to URL file parsing.// We'll assume URLs of the format "jar:path!/entry", with the protocol// being arbitrary as long as following the entry format.// We'll also handle paths with and without leading "file:" prefix.String urlFile = rootDirResource.getURL().getFile();int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);if (separatorIndex != -1) {jarFileUrl = urlFile.substring(0, separatorIndex);rootEntryPath = urlFile.substring(separatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());jarFile = getJarFile(jarFileUrl);}else {jarFile = new JarFile(urlFile);jarFileUrl = urlFile;rootEntryPath = "";}newJarFile = true;}try {if (logger.isDebugEnabled()) {logger.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");}if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {// Root entry path must end with slash to allow for proper matching.// The Sun JRE does not return a slash here, but BEA JRockit does.rootEntryPath = rootEntryPath + "/";}Set<Resource> result = new LinkedHashSet<Resource>(8);for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {JarEntry entry = entries.nextElement();String entryPath = entry.getName();if (entryPath.startsWith(rootEntryPath)) {String relativePath = entryPath.substring(rootEntryPath.length());if (getPathMatcher().match(subPattern, relativePath)) {result.add(rootDirResource.createRelative(relativePath));}}}return result;}finally {// Close jar file, but only if freshly obtained -// not from JarURLConnection, which might cache the file reference.if (newJarFile) {jarFile.close();}}}
 这就拿到了我想要的代码的,我 定义了一个ResourceTool  类,其中做了简化处理:
   public class ResourceTool {    /**     * 获取默认的类加载器     *      * @return     */    public static ClassLoader getDefaultClassLoader() {        ClassLoader cl = null;        try {            cl = Thread.currentThread().getContextClassLoader();        } catch (Throwable ex) {        }        if (cl == null) {            // No thread context class loader -> use class loader of this class.            cl = ClassUtils.class.getClassLoader();        }        return cl;    }    /**     * 获取配置文件资源对象     *      * @param location     * @return     * @throws IOException     */    public static List<URL> findAllClassPathResources(String location) throws IOException {        String path = location;        if (path.startsWith("/")) {            path = path.substring(1);        }        Enumeration<URL> resourceUrls = getDefaultClassLoader().getResources(location);        List<URL> result = Lists.newArrayList();        while (resourceUrls.hasMoreElements()) {            result.add(resourceUrls.nextElement());        }        return result;    }    /**     * 获取指定路径下的指定文件列表     *      * @param rootFile           文件路径     * @param extensionName 文件扩展名     * @return     */    public static List<File> getFiles(File rootFile, String extensionName) {        List<File> fileList = Lists.newArrayList();        String tail = null;        if (extensionName == null) {            tail = "";        } else {            tail = "." + extensionName;        }        if (rootFile == null) {            return fileList;        } else if (rootFile.isFile() && rootFile.getName().endsWith(tail)) {            fileList.add(rootFile);            return fileList;        } else if (rootFile.isDirectory()) {            File[] files = rootFile.listFiles();            for (File file : files) {                if (file.isFile() && file.getName().endsWith(tail)) {                    fileList.add(file);                } else if (file.isDirectory()) {                    fileList.addAll(getFiles(file, extensionName));                }            }        }        return fileList;    }    public static List<URL> <strong>getJarUrl</strong>(URL rootUrl, String extensionName) throws IOException {        List<URL> result = Lists.newArrayList();        if (rootUrl == null || !"jar".equals(rootUrl.getProtocol())) {            return result;        }        if (StringUtils.isNotBlank(extensionName)) {            extensionName = "." + extensionName;        }        if (extensionName == null) {            extensionName = "";        }        URLConnection con = rootUrl.openConnection();        JarURLConnection jarCon = (JarURLConnection) con;        jarCon.setUseCaches(false);        JarFile jarFile = jarCon.getJarFile();        JarEntry jarEntry = jarCon.getJarEntry();        String rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");        if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {            rootEntryPath = rootEntryPath + "/";        }        for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {            JarEntry entry = entries.nextElement();            String entryPath = entry.getName();            if (entryPath.startsWith(rootEntryPath)) {                String relativePath = entryPath.substring(rootEntryPath.length());                if (relativePath.endsWith(".service")) {                    result.add(createRelative(rootUrl, relativePath));                }            }        }        return result;    }    private static URL createRelative(URL url, String relativePath) throws MalformedURLException {        if (relativePath.startsWith("/")) {            relativePath = relativePath.substring(1);        }        return new URL(url, relativePath);    }}
 使用举例:
    /**     * 将配置内容转换成内置对象     *      * @return     */    private Map<String, ServiceSetting> getServiceSettingList(String path) {        Map<String, ServiceSetting> map = Maps.newHashMap();        try {            List<URL> urlList = ResourceTool.findAllClassPathResources(path);            for (URL url : urlList) {                String protocol = url.getProtocol();                // org.springframework.util.ResourceUtils                if (ResourceUtils.URL_PROTOCOL_JAR.equals(protocol)) {                    // 资源文件扩展名为"service"                    List<URL> result = ResourceTool.getJarUrl(url, "service");                    for (URL jarUrl : result) {                        URLConnection connection = jarUrl.openConnection();                        try {                            /**                             * 得到InputStream,即可解析配置文件                             */                            ServiceSetting serviceSetting = reloadServiceSetting(connection.getInputStream());                            /**                             * 检查服务配置正确性                             */                            boolean check = checkServiceSetting(serviceSetting);                            if (check) {                                map.put(serviceSetting.getName(), serviceSetting);                                logger.info("成功加载文件:" + jarUrl.getFile() + ", serviceSetting:"                                            + JsonUtil.toJson(serviceSetting));                            }                        } catch (Exception e) {                           // TODO:                        }                    }                } else if (ResourceUtils.URL_PROTOCOL_FILE.endsWith(protocol)) {                    // <a target=_blank class="header">org</a>.<a target=_blank class="header">springframework</a>.<a target=_blank class="header">util</a>.StringUtils                    File file = new File(                        new URI(StringUtils.replace(url.toString(), " ", "%20")).getSchemeSpecificPart());  ////  资源文件扩展名为"service"                    List<File> fileList = ResourceTool.getFiles(file, "service");                    for (File serviceFile : fileList) {                        ServiceSetting serviceSetting = reloadServiceSetting(new FileInputStream(serviceFile));                        /**                         * 检查服务配置正确性                         */                        boolean check = checkServiceSetting(serviceSetting);                        if (check) {                            map.put(serviceSetting.getName(), serviceSetting);                            logger.info("成功加载文件:" + serviceFile.getPath() + ", serviceSetting:"                                        + JsonUtil.toJson(serviceSetting));                        }                    }                }            }            return map;        } catch (Exception e) {           // TODO:        }    }
 
是不是相当简单?
 
 
 

 

 

 

 

 

 

 

 

 

 
 
0 0
原创粉丝点击