面临问题
游戏开发中我们常常会遇到一个问题就是测试环境下配置文件与生产环境配置文件不一致,当然最常用的就是在maven中指定不同的profiles然后分别打包,但是在游戏上线后这种方式就不太灵活,例如游戏上线后需要在特殊节日增加了一些新活动,但这种游戏逻辑已经内嵌至代码中,不必修改,往往只需要改几个配置就够了,同时本地开发是windows,osx,生产环境又是linux,加载路径也是千奇百怪,那么对于这种需求我们应当如何解决呢?
功能设计
-
区分开发,测试,生产环境
这一点很容易理解,即划分好各环境沙箱,使其相互之间没有干扰,最简单易行就是使用spring中PropertyPlaceholderConfigurer来解决各配置的差异,同样你可以使用当然你也可以使用Spring Bean definition profiles,但这个功能支持需要使用spring 3.1以上版本. -
提供统一的文件访问接口
文件的使用者不应该把重点放在如何加载配置文件上,而是直接将文件封装成他需要的对象,这样我们就需要一个能解析各种文件的工具类 -
基于事件机制的加载触发
配置文件加载不应该是显示的调用,而应该是基于事件机制,当在消息总线中抛出一个事件,那么各模块的配置文件加载器需要知道此时是否需要加载配置,而不用关心当前游戏服务器的状态.
功能实现
理想状态是游戏逻辑与配置文件存放完全分享,我们可以写一个单独的游戏配置服务,提供http接口,让游戏需要更新配置文件的时候从接口中抓取即可,但这听起来似乎比较麻烦,有没有更简单易行的方式?答案是肯定的,我们从下面几个方面进行解决.
-
区分开发,测试,生产环境
这里配置包括两个方面,第一个是系统运行环境,第二个是游戏配置,我们使用PropertyPlaceholderConfigurer来解决我们的第一问题
1234567891011121314151617<!-- 定义受环境影响易变的变量 --><bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" /><property name="ignoreResourceNotFound" value="true" /><property name="locations"><list><!-- 本地开发环境配置 --><value>classpath*:/application.properties</value><!-- 测试环境配置 --><value>file:/your_test_server_path/application.server.properties</value><!-- 服务器生产环境配置 --><value>file:/your_produce_server_path/application.server.properties</value></list></property></bean>
这段配置很好理解,我们定义了三个配置,如何当前环境中只有一个配置生效那么就使用这个配置,如果有多个那么最后一个配置将生效,properties看起来类似这样
12345678910111213141516171819#jdbc settings...#hibernate settings...#memcached setting...#socket server setting...#server config#指定各环境server.config.profile = dev|test|produce#指定加载路径server.config.path = your_config_path|classpath -
提供统一的文件访问接口
文件的使用者不应该把重点放在如何加载配置文件上,而是直接将文件封装成他需要的对象,这样我们就需要一个能解析各种文件的工具类
123456789public class FileUtils {// 将文件转化成你需要的对象public static <T> List<T> readFile(String file, Charset charset, Class<T> clazz){...}}
这里文件有很多类型,但常用的还是txt,xml,json,那么我们可以针对这三种文件类型给出实现,这里我就不再列举毕竟文件读取实现很多,txt可以用org.apache.commons.io.IOUtils读取,xml可以使用xstream,json使用jackson,都能很方便解决.
解决了读取问题,那么第一个参数中的file如何解决呢?不同环境可是要加载不同的file,不同操作系统中文件路径也有差异,这时我们需要借助一个ConfigService来做这件事情,而此前我们也说到我们需要一个事件来触发加载,那么我们能不能将这两个功能合并在一起解决呢?我们来看下面的代码
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172import com.google.common.eventbus.EventBus;import yourpackage.Constant;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import org.springframework.util.ResourceUtils;import java.io.File;@Componentpublic class ConfigService {private final static String ROOT_URI = ResourceUtils.FILE_URL_PREFIX + System.getProperty("user.dir");@Value("${server.config.path}")private String configPath;@Value("${server.config.profile}")private String production;@Autowiredprivate EventBus eventBus;public void load() {if (Constant.PRODUCTION.equals(production)) {String path = ROOT_URI + File.separator + configPath + File.separator;eventBus.post(new LoadConfigEvent(path.replaceAll("\\\\", "/")));} else {eventBus.post(new LoadConfigEvent(ResourceUtils.CLASSPATH_URL_PREFIX));}}}public class LoadConfigEvent {/*** 加载路径*/private String configPath;/*** 需要加载的文件,如果为空,则全部加载**/private String file;public LoadConfigEvent(String configPath) {this.configPath = configPath;}public String getConfigPath() {return configPath;}public void setConfigPath(String configPath) {this.configPath = configPath;}public String getFile() {return file;}public void setFile(String file) {this.file = file;}}
这样看起来是不是比较容易理解了,因为需要兼顾操作系统与各种环境,那么我们必须使用uri来对各文件进行定位,而spring又恰好提供了这样的功能,当服务器配置为生产环境时我们从指定目录加载,否则我们从本地classpath中加载,这样就避免了加载目录千奇百怪的问题.
总结
对于配置文件使用好了可以减少很多负担,但如果滥用后期维护也会花费不少精力,当前最好的解决方案仍是开头提到的提供http api,这可算是一劳永逸,但只是游戏服务器开发不是一蹴而就,而是不停迭代,到什么样的规模做什么样的事即可,而本文的中心也是想体不要重复造轮子,现很多问题其实已经有人遇过到了,你只需要借用前人的经验加上一点想法即可.