在项目开发过程中,无论是使用当前市面上流行的各种框架,还是自己公司内部开发的业务针对性框架,配置是一个永远绕不过的坎。当然,不少框架为了减少配置,引入了各种类型诸如类名、包名潜规则,不使用配置而改用注解,抑或是通过接口来进行指定等方案。今天我们要谈的就是,tiny中如何来思考、解决这方面的问题。
本文不会对具体的细节进行过多的描述,这些在tiny文档中都有会专门的讲解,对具体实现感兴趣的同学,可以去下载tiny源码,阅读tiny文档。通过接口、命名、注解等方式进行配置,不属于本文所谈及范围,对于此类问题,后续会专门再开文章进行讲解。本次主要解决,在项目中更方便的对引入的配置文件进行管理的处理思路,后面提到的配置信息统一指此类情况。
首先,我们把配置信息分为两种:
一种是全局性配置,这种配置信息,整个项目只需要在一处进行同一配置即可,例如项目的数据库相关信息,缓存的redis服务器地址等。一般情况下,项目增加新模块,全局性配置不需要进行新增,最多进行一些参数性的调整。这种配置信息,在项目实际部署时,往往会根据不同的环境,需要部署或则运维人员对配置的值进行调整。因此,这种配置信息往往直接放置于项目的web根目录下。比如一般项目中的db.xml、db.properties。
另一种是不同的业务模块都会产生的,往往跟业务的关系较为紧密,依托于不同的业务模块而产生的配置,比如spring beans配置文件,tiny框架中的服务配置信息。这种配置信息,与业务模块的联系紧密,对于模块化的项目,一个模块的引入或者剥离,都需要对模块相应的配置信息进行引入或者剥离。这种配置文件依附于实践中具体的业务单元,因此,从模块化的角度来说,这种配置信息在项目运维时往往是放置于jar包之中。
首先,针对第二种配置进行处理。
spring的做法,是指定需要引入的beans文件所在路径。
<import resource="com/demo/res/bean.xml"/>
<import resource="com/demo/res2/bean2.xml"/>
<import resource="com/demo/res3/bean3.xml"/>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/spring/applicationContext.xml,
classpath*:conf/spring/applicationContext_core*.xml
</param-value>
</context-param>
当引入或者移除新的业务单元,可能需要对配置的路径进行变更。当业务单元模块越来越多,项目人员的不断变化,后期需要专门维护一个路径与业务单元的关系表。当然,技术经理可以约定beans文件的路径放置规则,这样似乎解决了对路径进行变更的问题。但这种方式,只能解决该技术经理自有项目中的配置信息。当进行跨部门合作,引入其他部门已有项目时,还是无法避开这个问题。
在Tiny中,我们通过配置自发现来解决这个问题。在此种方式下,开发者并不需要配置资源路径,只要改资源存在于整个应用之中,框架都会将其找出,最后统一送到开发者定义的处理器之中。这样,开发者就不需要为引入或者移除业务单元而频繁的修改配置信息,不需要关注配置信息的路径,只需要在工作重点放在配置信息的处理上。例,在tiny框架中,应用中只需要将springbeans文件配置为*.beans.xml并配置相应的处理器(框架已提供),所有存在于整个项目下的所有的beans配置文件都会被加载起来,而开发者无需再逐一指定资源路径,或者定义各种潜规则。
自发现配置的实现基于tiny的文件扫描体系(org.tinygroup.fileresolver),该扫描体系可以将应用中所有资源开发者需要的资源根据其自定义的规则筛选出来,推送给开发者定义的处理器。其核心接口如下。
开发者通过isMatch指定文件匹配规则,传入的FileObject对象,即文件扫描体系在应用中扫到的实际文件对象。process方法,则用来对收集到的配置文件进行处理。
/**
* 文件处理器
*
* @author luoguo
*
*/
public interface FileProcessor extends Configuration,Ordered {
/**
* 定义文件匹配规则
*
* @return
*/
boolean isMatch(FileObject fileObject);
/**
* 是否支持动态刷新
*
* @return
*/
boolean supportRefresh();
/**
* 设置文件查找器
*
* @param fileResolver
*/
void setFileResolver(FileResolver fileResolver);
/**
* 新增文件
*
* @param fileObject
*/
void add(FileObject fileObject);
/**
* 没有变化
*
* @param fileObject
*/
void noChange(FileObject fileObject);
/**
* 修改文件时的处理
*
* @param fileObject
*/
void modify(FileObject fileObject);
/**
* 删除文件时的处理
*
* @param fileObject
*/
void delete(FileObject fileObject);
/**
* 对文件进行处理
*/
void process(); /**
* 处理完成后执行
*/
void clean();
}
这个问题解决了,再回过头来,我们还有一种全局性配置要处理。在传统模式下,不同的功能模块会定义不同类型的配置文件,进行读取处理。比如为了配置数据库引入db.xml,为了配置缓存引入memorycache.xml,为了配置xxxx,引入xxxx.xml,每个模块定义自己的配置文件,读取自己的配置文件。随着应用的功能越来越多,配置文件的数量也越来越多,这也是很多项目的实际情况,运维人员不得不面对大量的配置文件,为了查找某个配置项,逐一翻阅配置文件查找。
因此,tiny考虑首先从配置文件数量上下手。在tiny应用中,大部分的配置信息都被定义到了Application.xml之中,以期达到配置同一管理的目的。开发者可以通过实现指定的接口,将自己的配置信息定义到Application.xml,同时框架会在启动时,自动将配置信息推送给开发者,省去配置文件读取这一步骤。开发者只需要关注如何处理获取到的配置信息即可。该功能的核心接口Configuration(org.tinygroup.config)。
/**
* 所有需要进行应用配置统一管理的类,都推荐实现此接口。
* 通过此接口,可以由框架自动注入配置信息
*
* @author luoguo
*/
public interface Configuration {
/**
* 获取对应的节点名,指在父节点下的子节点
*
* @return
*/
String getApplicationNodePath();
/**
* 返回模块配置路径不用带扩展名
*
* @return
*/
String getComponentConfigPath();
/**
* 设置配置信息
*
* @param applicationConfig
* @param componentConfig
*/
void config(XmlNode applicationConfig, XmlNode componentConfig);
/**
* 获取组件配置信息
*
* @return
*/
XmlNode getComponentConfig();
/**
* 获取应用配置信息
*
* @return
*/
XmlNode getApplicationConfig();
}
如下是框架内部一个实现了该接口的类。
public class CepCoreAopConfig extends AbstractConfiguration {
private static final String CEPCORE_AOP_CONFIG_PATH="/application/cepcore-aop-config";
private CEPCoreAopManager manager;
public CEPCoreAopManager getManager() {
return manager;
}
public void setManager(CEPCoreAopManager manager) {
this.manager = manager;
}
public String getApplicationNodePath() {
return CEPCORE_AOP_CONFIG_PATH;
}
public String getComponentConfigPath() {
return "/cepcoreaop.config.xml";
}
public void config(XmlNode applicationConfig, XmlNode componentConfig) {
super.config(applicationConfig, componentConfig);
XmlNode combineNode= ConfigurationUtil.combineXmlNode(applicationConfig, componentConfig);
manager.init(combineNode);
}
}
该实现类,对应在Application.xml中的配置如下。
<application>
<cepcore-aop-config>
<!-- 配置体,由实现类自己定义结构,框架会将整个cepcore-aop-config节点推送给实现类 -->
</cepcore-aop-config>
</application>
到这一步,所做的还只是将多个配置文件合并到一个配置文件中,并省去不同功能模块自行读取文件这一步骤。为了配置信息方面管理,还需要定义一个变量池,运维人员只需要关注对变量池中的变量进行调整,而不需要关注这个变量对应到配置文件的哪个节点的哪个属性,那玩意应该是开发人员关心的。因此,在Application.xml,tiny又定义了一个统一变量节点。
开发者可以在统一变量节点中自定义各种变量,然后在配置节点中以{变量名}的方式引用改变量,最终配置加载时,框架会自动将该引用替换为变量真正的值。
<application>
<application-properties>
<property name="BASE_PACKAGE" value="org.tinygroup"/>
<property name="DEBUG_MODE" value="false"/>
<property name="TINY_THEME" value="default"/>
<property name="fieldWidth" value="120pt"/>
</application-properties>
<custom-node>
<node1 mode="{DEBUG_MODE}" width="{fieldWidth}">
</custom-node>
<application>
好了,看起来似乎可以一用了。不过,细心的读者可能发现新的问题了,万一我的变量很多呢?本来这个Application.xml已经聚合了很多配置信息进来,现在里面搞这么一大块变量域,岂不是,到时候变量域都长的够人吃一壶了。嗯,悠然也是这么想的。于是拍一拍脑袋,好吧,那么变量域是不是考虑下可以引入外部properties/ini文件。于是,又对application-properties节点进行加工。
<application>
<application-properties>
<file path="db.ini"></file>
<file path="rpcip.properties"></file>
<property name="DEBUG_MODE" value="false"/>
<property name="TINY_THEME" value="default"/>
<property name="TINY_DEFAULT_LOCALE" value="zh_CN"/>
</application-properties>
<rpc-node>
<node host="{rpc_ip}" port="{rpc_port}">
</rpc-node>
<application>
其中,变量rpc_ip、rpc_port被定义到了rpcip.properties文件之中。
到这一步,好像用起来有那么点味道了。
由于这块的内容比较多,这次先写到这里,更多的内容请关注本人后续博客。
如果你对我的博客感兴趣,请关注我以便及时收到我的相关动态,谢谢!