1.ContentProvider 源码深入解析
2.Spring容器刷新—02—obtainFreshBeanFactory
3.webpack5loader和plugin原理解析
4.springMVC的配置问题 求大神来帮帮忙,谢谢啦!
ContentProvider 源码深入解析
ContentProvider作为Android系统中核心组件之一,用于实现应用间数据共享。其工作流程始于ActivityManagerService启动新进程,此过程由startProcessLocked方法调用Process的怎么进源码start方法实现。ActivityThread的main方法作为整个流程的起点,创建ActivityThread实例后,通过attach方法进行一系列数据操作,开启主线程Looper循环。
attach方法内部首先调用ActivityManagerService的attachApplication方法,经过attachApplicationLocked和ApplicationThread的bindApplication方法,实现进程间的调用。接着,通过handler发送消息给ActivityThread的handleBindApplication方法,从而创建ContextImpl与Instrumentation对象。
整个启动过程中,installContentProviders方法起到关键作用,它遍历ProviderInfo列表,通过installProvider进行ContentProvider启动操作,并将启动的ContentProvider发布到AMS中。借助ClassLoader加载ContentProvider,完成对象创建。最终调用localProvider.attachInfo(c, info);方法,实现ContentProvider的onCreate操作,至此,ContentProvider完成启动过程,为其他应用提供访问途径。
随着ContentProvider的启动,ActivityManager能够访问并利用其提供的接口,实现应用间的数据共享。这一机制简化了跨应用数据访问的复杂性,为Android系统的整体架构提供了高效的数据流通渠道。
Spring容器刷新——obtainFreshBeanFactory
本文讨论的是Spring容器中的刷新过程,重点讲解了创建BeanFactory实例的操作。BeanFactory和ApplicationContext在Spring源码中有多种实现,ApplicationContext在BeanFactory基础上增加了额外功能,如管理应用上下文、提供更丰富的依赖注入等。
在实际应用中,选择使用哪个具体实现取决于项目的特定需求。本文列出了两种常见的实现:AbstractApplicationContext和GenericApplicationContext。
AbstractApplicationContext支持多次刷新,内部维护了一个volatile的DefaultListableBeanFactory实例。刷新逻辑分为两步:首先调用refreshBeanFactory()方法,然后返回此实例通过getBeanFactory()方法。
GenericApplicationContext的提前三天预警副图指标源码实现相对简单,对于obtainFreshBeanFactory()方法的调用几乎不做任何操作。
至于应用程序中使用哪个具体的BeanFactory实现,这取决于项目的配置和需求。在传统的Servlet环境下,通常通过ContextLoaderListener加载上下文,而SpringBoot环境中的ApplicationContext创建则通过ApplicationContextFactory完成。
具体实现细节和流程在不同环境下的差异,如Servlet环境中的ContextLoaderListener和ContextLoader的使用,以及SpringBoot环境中的ApplicationContextFactory的实现,将在后续的文章中进行详细阐述。
webpack5loader和plugin原理解析
大家好,今天为大家解析下loader和plugin一、区别loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
plugin赋予了Webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决loader无法实现的其他事从整个运行时机上来看,如下图所示:
可以看到,两者在运行时机上的区别:
loader运行在打包文件之前plugins在整个编译周期都起作用在Webpack运行的生命周期中会广播出许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果
对于loader,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scss或A.less转变为B.css,单纯的文件转换过程
下面我们来看看loader和plugin实现的原理
Loader原理loader概念帮助webpack将不同类型的文件转换为webpack可识别的模块。
loader执行顺序分类
pre:前置loader
normal:普通loader
inline:内联loader
post:后置loader
执行顺序
4类loader的执行优级为:pre>normal>inline>post。
相同优先级的loader执行顺序为:从右到左,从下到上。
例如:
//此时loader执行顺序:loader3-loader2-loader1module:{ rules:[{ test:/\.js$/,loader:"loader1",},{ test:/\.js$/,loader:"loader2",},{ test:/\.js$/,loader:"loader3",},],},//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},使用loader的方式
配置方式:在webpack.config.js文件中指定loader。(pre、normal、postloader)
内联方式:在每个import语句中显式指定loader。(inlineloader)
开发一个loader1.最简单的loader//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};它接受要处理的源码作为参数,输出转换后的js代码。
2.loader接受的参数content源文件的内容
mapSourceMap数据
meta数据,可以是任何内容
loader分类1.同步loadermodule.exports=function(content,map,meta){ returncontent;};this.callback方法则更灵活,因为它允许传递多个参数,而不仅仅是content。
module.exports=function(content,map,meta){ //传递map,让source-map不中断//传递meta,岚府燕窝溯源码代理多少钱让下一个loader接收到其他参数this.callback(null,content,map,meta);return;//当调用callback()函数时,总是返回undefined};2.异步loadermodule.exports=function(content,map,meta){ constcallback=this.async();//进行异步操作setTimeout(()=>{ callback(null,result,map,meta);},);};由于同步计算过于耗时,在Node.js这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的loader异步化。但如果计算量很小,同步loader也是可以的。
3.RawLoader默认情况下,资源文件会被转化为UTF-8字符串,然后传给loader。通过设置raw为true,loader可以接收原始的Buffer。
module.exports=function(content){ //content是一个Buffer数据returncontent;};module.exports.raw=true;//开启RawLoader4.PitchingLoadermodule.exports=function(content){ returncontent;};module.exports.pitch=function(remainingRequest,precedingRequest,data){ console.log("dosomethings");};webpack会先从左到右执行loader链中的每个loader上的pitch方法(如果有),然后再从右到左执行loader链中的每个loader上的普通loader方法。
在这个过程中如果任何pitch有返回值,则loader链被阻断。webpack会跳过后面所有的的pitch和loader,直接进入上一个loader。
loaderAPI方法名含义用法this.async异步回调loader。返回this.callbackconstcallback=this.async()this.callback可以同步或者异步调用的并返回多个结果的函数this.callback(err,content,sourceMap?,meta?)this.getOptions(schema)获取loader的optionsthis.getOptions(schema)this.emitFile产生一个文件this.emitFile(name,content,sourceMap)this.utils.contextify返回一个相对路径this.utils.contextify(context,request)this.utils.absolutify返回一个绝对路径this.utils.absolutify(context,request)更多文档,请查阅webpack官方loaderapi文档
手写clean-log-loader作用:用来清理js代码中的console.log
//loaders/clean-log-loader.jsmodule.exports=functioncleanLogLoader(content){ //将console.log替换为空returncontent.replace(/console\.log\(.*\);?/g,"");};手写banner-loader作用:给js代码添加文本注释
loaders/banner-loader/index.js
constschema=require("./schema.json");module.exports=function(content){ //获取loader的options,同时对options内容进行校验//schema是options的校验规则(符合JSONschema规则)constoptions=this.getOptions(schema);constprefix=`/**Author:${ options.author}*/`;return`${ prefix}\n${ content}`;};loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},0手写babel-loader作用:编译js代码,将ES6+语法编译成ES5-语法。
下载依赖
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},1loaders/babel-loader/index.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},2loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},3手写file-loader作用:将文件原封不动输出出去
下载包
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},4loaders/file-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},5loader配置
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},6手写style-loader作用:动态创建style标签,插入js中的样式代码,使样式生效。
loaders/style-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},7Plugin原理Plugin的作用通过插件我们可以扩展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务,拥有更强的构建能力。
Plugin工作原理webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack通过Tapable来组织这条复杂的生产线。webpack在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。webpack的事件流机制保证了插件的有序性,使得整个系统扩展性很好。——「深入浅出Webpack」
站在代码逻辑的角度就是:webpack在编译代码过程中,会触发一系列Tapable钩子事件,插件所做的怎么查免税店的溯源码真伪,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当webpack构建的时候,插件注册的事件就会随着钩子的触发而执行了。
Webpack内部的钩子什么是钩子钩子的本质就是:事件。为了方便我们直接介入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做:hooks(钩子)。开发插件,离不开这些钩子。
TapableTapable为webpack提供了统一的插件接口(钩子)类型定义,它是webpack的核心功能库。webpack中目前有十种hooks,在Tapable源码中可以看到,他们是:
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},8Tapable还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:
tap:可以注册同步钩子和异步钩子。
tapAsync:回调方式注册异步钩子。
tapPromise:Promise方式注册异步钩子。
Plugin构建对象Compilercompiler对象中保存着完整的Webpack环境配置,每次启动webpack构建时它都是一个独一无二,仅仅会创建一次的对象。
这个对象会在首次启动Webpack时创建,我们可以通过compiler对象上访问到Webapck的主环境配置,比如loader、plugin等等配置信息。
它有以下主要属性:
compiler.options可以访问本次启动webpack时候所有的配置文件,包括但不限于loaders、entry、output、plugin等等完整配置信息。
compiler.inputFileSystem和compiler.outputFileSystem可以进行文件操作,相当于Nodejs中fs。
compiler.hooks可以注册tapable的不同种类Hook,从而可以在compiler生命周期中植入不同的逻辑。
compilerhooks文档
Compilationcompilation对象代表一次资源的构建,compilation实例能够访问所有的模块和它们的依赖。
一个compilation对象会对构建依赖图中所有模块,进行编译。在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。怎么看源码中的域名数据
它有以下主要属性:
compilation.modules可以访问所有模块,打包的每一个文件都是一个模块。
compilation.chunkschunk即是多个modules组成而来的一个代码块。入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk。
compilation.assets可以访问本次打包生成所有文件的结果。
compilation.hooks可以注册tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改。
compilationhooks文档
生命周期简图开发一个插件最简单的插件plugins/test-plugin.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},9注册hook//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};0启动调试通过调试查看compiler和compilation对象数据情况。
package.json配置指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};1运行指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2此时控制台输出以下内容:
PSC:\Users\\Desktop\source>//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2>source@1.0.0debug>node--inspect-brk./node_modules/webpack-cli/bin/cli.jsDebuggerlisteningonws://.0.0.1:/ea-7b--a7-fccForhelp,see:/post/开发思路:
我们需要借助html-webpack-plugin来实现
在html-webpack-plugin输出index.html前将内联runtime注入进去
删除多余的runtime文件
如何操作html-webpack-plugin?官方文档
实现:
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};7springMVC的配置问题 求大神来帮帮忙,谢谢啦!
Spring配置
人们经常会在一台以上的主机上配置一种Web应用程序。例如,在生产中,一个网站可能只有一个实例。除了此实例外,开发人员可以在用于开发的机器上配置其他的(开发)实例。也可以在公司(机构)内部的本地开发服务器上维护其他应用程序装置,这将让您受益匪浅。该实例的目的是使Web设计者可以获得有质量保证的材料,并为需要为应用程序提供文件资料的人提供准入。
大家都知道,即使是最简单的场景,也需要安装、配置和维护三个实例。而对于位于不同地理位置的团队来说,要从事这样的项目便更加困难。对于任何不是特别简单的Web应用程序项目,都需要多名开发人员来安装项目装置和本地设置以及运行单元测试的装置等。
很多组织都将自己开发的产品作为Web应用程序。我们可以在很多产品中发现这种情况,例如电子商务系统、内容管理系统(CMS),以及博客发布平台等。这类产品可在多个服务器中进行部署。对于成功的多用途Web应用程序来说,他们的开发人员必须要保证他们的应用程序便于安装,并且能够与其他Web应用程序完美集成。经过上述讨论之后,我们应该明了,作为本文主题的应用程序配置是通用Web应用程序项目开发人员所需要解决的重要问题之一。
诸如CVS或Subversion之类的版本控制系统是开发组织使用的一种标准工具。这种工具代表了一些组织的中心源代码版本库,它们被用于保持源代码的有序。用户可以跟踪应用程序源代码的变化,显示不同版本的区别,并可以确定项目分支。而且,它们使得在应用程序部署中进行部分更新成为可能。
很明显,版本控制系统软件是跟踪源代码所必需的,它对于解决应用程序配置问题有非常大的帮助。在本文中,我们将不会把重点放在版本控制系统上,因为这方面已经有很多相关的材料了。在此,我们将关注版本控制问题中的一个小话题:如何使Web应用程序的配置更加便捷(尤其是使用Spring
MVC框架编写的Web应用程序)。
问题是:我们在此讨论的是一种什么样的配置?任何Web应用程序都需要一些资源,这些资源通常都是其所运行的服务器所特有的,例如数据库URL、发送电子邮件的SMTP服务器,以及包含专用软件文件的文件夹等。这样的设置应该集中,从而使应用程序配置更加简单。
但是,这只是这个问题最简单的一种版本。有时候,在应用程序开发中需要更加复杂的配置。这意味着,必须将各次部署中的不同Bean连接起来,而这会使问题更加复杂。
这些应用程序配置问题的解决方案有诸多优势,包括:简化应用程序的安装和配置过程,使源代码版本控制更加简便,减少源代码版本库中的冲突现象。下面,我们将通过示例详细讨论这个话题。
问题。
我们首先来演示一下上文所提到的最简单的版本。在这一场景中,我们希望在应用程序部署中改变的是简单的配置参数,例如链接、密码等。如果您曾经使用Spring
MVC框架开发过Web应用程序,那么您应该知道这里将用到的两个配置文件:
/WEB-INF/applicationContext.xml,它让您可以配置Bean,或者显示应用程序上下文。通过这个文件,您可以定义自己的业务逻辑Bean、资源以及其他可以与Web端口相关联的所有Bean。
/WEB-INF/[servlet-name]-servlet.xml,它被用于配置Web层、视图解析器、控制器、校验器以及其他所有MVC框架中必需的Bean。[servlet-name]指的是在web.xml部署描述符中定义的Spring
dispatcher servlet的名称。
那么问题在哪儿呢?问题就出在applicationContext.xml中将包括一些特定于主机的Bean。本文将定义。其中,最明显的一个示例就是包含了JDBC连接信息的bean,但是任何一种稍微复杂些的应用程序都有十几个类似的Bean。看一下下面的示例:
<bean
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property >
<value>org.postgresql.Driver</value>
</property>
<property >
</property>
<property >
<value>postgres</value>
</property>
<property >
<value></value>
</property>
</bean>
这个解决方案的问题在于对applicationContext.xml文件的维护。对于初学者来说,设想一下,项目放在源代码版本控制系统中,例如CVS。下面,假设您希望在网站中添加新的功能,那么就需要在应用程序上下文定义中添加额外的Bean定义。问题是如何在生产服务器上体现这些改变。
通常情况下,应用程序的本地实例不会与活动站点使用同样的数据库,因此applicationContext.xml文件将包括让您能够访问本地数据库的设置。当您想提交在源代码版本库中的改变时,就需要注意这些特定于主机属性的同步性。版本库中的文件最终可能使用本地设置中的配置。如果想在生产服务器上更新配置,就必须手动同步这些属性的值。这是非常枯燥的任务,而且还非常容易出错。
对于应用程序的每个实例来说,这个问题更加重要。假如有三位开发人员正在使用代码段基址,而且他们使用的是本地的数据库。当您提交更改的时候,他们每个人在本地服务器上更新源代码的时候都必须非常谨慎。他们会手动同步这些更改,然后提交他们的工作。这样一来,版本控制系统对于这些配置文件来说已经毫无用处。如果曾经使用过Spring
MVC,那么您应该知道applicationContext.xml是应用程序中的关键组件,因为是它将所有的东西粘合在一起。所以,我们需要一种机制来帮助使应用程序中各项保持有序,这点非常重要。
正如前面所提到的,这是您可能遇到的较简单的配置问题。更难的问题出现在当需要在不同服务器中进行不同的Bean连接的时候。这类问题常会出现在日常软件开发任务中。例如,假如您的产品有一个客户身份验证模块,可以对来自关系数据库或LDAP服务器中的用户进行身份验证。自然,这一身份验证模块可以使用抽象了特定版本库的Bean进行配置。如果您想改变不同应用程序部署中验证用户的方式,就需要在applicationContext.xml文件中进行不同的Bean连接。这种配置问题常见于在部署中有可配置特性的所有应用程序。
在下文中,我们将讨论这两种配置问题。首先我们会关注同步的Bean属性问题及其解决方案,接下来,我们会讨论更加复杂的同步Bean连接问题。
解决方案
同步Bean属性
这个问题的一种可行的解决方案是将所有特定于主机的参数都放到普通的Java属性文件中,使用Spring的PropertyPlaceHolderConfigurer类,将这些参数写入Bean属性中。
使用这一解决方案,我们可以生成如下的属性文件(/WEB-INF/jdbc.properties):
jdbc.driver=org.postgresql.Driver
jdbc.url=jdbc:postgresql://localhost/test
jdbc.user=postgres
jdbc.password=
我们的Bean配置如下:
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property >
<value>/WEB-INF/jdbc.properties</value>
</property>
</bean>
<bean
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property >
<value>${ jdbc.driver}</value>
</property>
<property >
<value>${ jdbc.url}</value>
</property>
<property >
<value>${ jdbc.user}</value>
</property>
<property >
<value>${ jdbc.password}</value>
</property>
</bean>
如上所述,我们定义了一个PropertyPlaceholderConfigurer类的实例,并将其位置属性设置为我们的属性文件。该类被实现为Bean工厂的后处理器,并将使用定义在文件中的属性来代替所有的占位符(${ ...}value)。
利用这种技术,我们可以从applicationContext.xml中移除所有特定于主机的配置属性。通过这种方式,我们可以自由地为该文件添加新的Bean,而不必担心特定于主机属性的同步性。这样可以简化生产部署和维护。
同步性连接
上面的技术解决了第一个问题,可是如果您计划修改不同应用程序部署之间的Bean连接,这一技术便不很适合。针对这一问题的一个解决方案便是额外创建一个名为applicationContext-[hostname].xml
的XML定义文件。其中[hostname]是部署应用程序的主机的名称。例如,在本地的机器上,这个文件通常名为applicationContext-localhost.xml,而在部署时,它可能更名为applicationContext-somehost.com.xml。
可以猜测,这一文件必须包括特定于某一主机的所有配置Bean。在本文中,我们将假设dataSource
bean定义将位于这类文件中,而不是通用的applicationContext.xml定义。当然,这种机制与前者并非冲突,但是为了更加简单明了,我们将只关注这种方法。
既然我们已经有了特定的配置,下面我们就来讨论一下如何将其整合到整个Spring
MVC配置概念中。要达到这一目的,可以有许多方法,我们将详细地一一说明。但首先,我们应该注意到,由于有些Bean可能位于独立的配置文件中,因此在applicationContext.xml中,所有对它们的局部引用都必须更换成全局名称。
例如,如下引用:
<property
>
<ref local="someBean"/>
</property> 应更改为:
<property >
<ref bean="someBean"/>
</property> 在这之后,我们有很多可以添加额外的资源以用于配置的方式。其中最明显的就是使用<import>标签将这一额外资源包含在applicationContext.xml配置文件中。使用时,要将该标签放在applicationContext.xml文件开头。例如:
<import
resource="applicationContext-somehost.com.xml"/>
现在,在独立的XML定义文件和普通的应用程序上下文定义文件中的所有通用Bean定义都有了特定于主机的连接。由于大多数的Bean都不是特定于主机的,因此我们可以像处理Web应用程序中的其他资源一样自由地处理applicationContext.xml文件,并可以通过合适的版本控制系统与其进行同步。
但是,上述方法也有一定的弊端。如果您想保留不同XML文件的不同配置,就仍然必须担心applicationContext.xml的同步性,因为资源的名称必须根据不同服务器进行更改。虽然与原有的解决方案相比有了很大提高,只需更改文件名,但是这还是需要开发人员的手动协助。
由于与applicationContext.xml相比,主机配置不需如此频繁地进行更改,因此下一步便是将主机配置移动到web.xml文件中(如果可能的话)。幸运的是,我们有一个可用的解决方案。看一下下面关于web.xml配置的片断:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/applicationContext-somehost.com.xml
</param-value>
</context-param> 正如您所看到的,除了web.xml文件中常有的ContextLoaderListener之外,我们还添加了contextConfigLocation上下文参数配置。这一参数用于指示框架查找这些配置文件的位置。如果这一参数被省略,则Spring就只能到applicationContext.xml中查找。这里我们也定义了特定于主机的配置文件来使用。
利用这种方法,我们将所有特定于主机的配置从applicationContext.xml文件中移除,这样便减轻了其在不同应用程序部署中的同步性。
如果这种方法成为您的新习惯,您还可以使其更加灵活。通过遵守下列指令,也可以将特定于主机的配置从web.xml文件中移除。
为此,需要创建特定于我们的应用程序上下文的类:
package
net.nighttale.spring.util;
import java.net.InetAddress;
import
org.springframework.web.context.support.XmlWebApplicationContext;
public
class PerHostXmlWebApplicationContext
extends XmlWebApplicationContext {
protected String[] getDefaultConfigLocations() {
String hostname = "localhost";
try {
hostname =
InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
}
String perHostConfiguration =
DEFAULT_CONFIG_LOCATION_PREFIX
+ "applicationContext-"
+ hostname
+ DEFAULT_CONFIG_LOCATION_SUFFIX
logger.debug(
"Adding per host configuration file: "
+ perHostConfiguration
);
if
(getNamespace() != null) {
return new String[] {
DEFAULT_CONFIG_LOCATION_PREFIX
+ getNamespace()
+
DEFAULT_CONFIG_LOCATION_SUFFIX
, perHostConfiguration};
}
else {
return new String[] {
DEFAULT_CONFIG_LOCATION
, perHostConfiguration};
}
}
}
这个类拓展了Spring中常被作为默认值使用的XmlWebApplicationContext。XmlWebApplicationContext类将Web应用程序的配置从XML定义文件中复制过来。默认情况下,它可以配置来自applicationContext.xml和[servlet-name]-servlet.xml文件中的应用程序。这个类执行的惟一一项额外任务便是获取它所在的主机名称,并将applicationContext-[hostname].xml文件添加到配置文件列表中。
为了使用这个类,我们需要对其进行编译,将其包含在类途径中,并指示Spring框架使用它。前两步非常简单,我们就不在此赘述。我们可以指示Sping通过contextClass上下文参数来使用它。除了web.xml文件中的原有配置,我们还可以添加下列内容:
<context-param>
<param-name>contextClass</param-name>
<param-value>
net.nighttale.spring.util.PerHostXmlWebApplicationContext
</param-value>
</context-param>
如果我们使用这一配置片断,将会有三个文件被用于初始化这个框架:[servlet-name]-servlet.xml、applicationContext-[hostname].xml以及applicationContext.xml。
正如您所看到的,applicationContext.xml和web.xml文件已经完全摆脱了任何特定的配置细节,而且您也不必担心会在更新应用程序时破坏配置。
但是,这种方法有一个不足之处。因为,不论是否会使用,都需要在应用程序部署中有第三个配置文件。在这种情况下,便不需要特定于主机的配置。例如:
<?xml
version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD
BEAN//EN"
"Address.getLocalHost().getHostName())
可以将其作为Java代码执行,也可在喜欢使用的脚本语言(如BeanShell或Groovy)中作为一个具有Java风格语法的脚本执行。在获取了主机的名称之后,应该创建一个默认的/WEB-INF/applicationContext-[hostname].xml空文件夹(如我们上面所定义的),然后便可以开始了。