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文件的。
我们发现,代码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给包路径加的前缀。
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 之间会相互调用。请看上面代码我对两个方法进行了加黑:doFindPathMatchingJarResources 和doFindPathMatchingFileResources,这两个方法分别完成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: } }
- Spring如何扫描class和配置文件
- SpringMVC和Spring的配置文件扫描包详解
- SpringMVC和Spring的配置文件扫描包详解
- SpringMVC和Spring的配置文件扫描包详解
- spring 扫描不到jar中class文件的原因和解决方法
- maven dao和service工程,spring 扫描jar中配置文件nullpointer的问题
- spring配置mybatis自动扫描*mapper.java和*mapper.xml配置文件
- Spring配置文件里如何同时配置Spel表达和AOP
- 如何配置myeclipse10,Spring配置文件中输入class时的自动提示,spring本身的xml可以提示
- spring自动扫描和管理
- spring-扫描service和controller
- Spring、Spring自动扫描和管理Bean
- spring 和 spring mvc 扫描包问题
- 解决在spring配置文件中包扫描无效问题
- 监听器如何获取Spring配置文件
- 监听器如何获取Spring配置文件
- 如何理解Spring 的配置文件
- springmvc配置文件,扫描组件分开扫描和直接全扫描的区别
- ActiveMQ中的NetworkConnector(网络连接器)详解
- 【转】JDK1.5-1.9新特性
- Spring MVC 创建自定义转换器
- JVM常用调优参数
- 如何根据历史数据监控当前数据是否异常
- Spring如何扫描class和配置文件
- HttpClient入门示例
- Dubbo源代码实现一:切入Spring
- Flume快速入门(五):File Channel之重播(replay)
- 常用限流方案的设计和实现
- RocketMQ初探一:NameServer的作用
- Flume快速入门(一):背景简介
- 基础算法(排序)
- JNI综合实验一:LED点亮+IO电平读取