一般的web开发中,配置文件分为xml和properties两种。其中properties文件可能在java代码中读取使用,也可能在xml中读取使用,比如配置数据源。我们要尽量的把全部的properties和部分的xml配置文件外化出来。
静态工具类:
首先对于java中读取的properties文件我们可以提供一个工具类,供其他同事开发与使用。如果用一个Properties对象存储多个文件时会发生相同key值前者被后者覆盖掉的问题,为了能够更清晰的管理properties文件,这里采用每个文件对应一个Properties对象的方法,该工具类提供两个参数,取值时需要把文件名称和key都传进来。
- 提供获取properties内容的工具类:
**
* @description: 该工具类提供按照指定文件读取properties文件内的信息
* @author: zhangchi
* @email: zc_ciyuan@163.com
* @datetime: 2017/1/14 13:45
* @version: 1.0.0
*/
public final class PropertiesUtil {
private PropertiesUtil() {
}
/**
* 日志引用
*/
private static final LogUtil logger = LogUtil.getInstance();
/**
* 根据properties文件名称和键获取对应值
*
* @param propertiesName 文件名称
* @param key 键
* @return 返回value
*/
public static String getPropertiesByKey(String propertiesName, String key) {
if (PropertiesLoader.propertiesMap != null) {
return PropertiesLoader.propertiesMap.get(propertiesName).getProperty(key);
} else {
try {
PropertiesLoader.loader();
return PropertiesLoader.propertiesMap.get(propertiesName).getProperty(key);
} catch (IOException e) {
logger.error(null, "加载properties文件异常", "初始化配置文件", e);
return null;
}
}
}
}
- 这里加载制定路径下的配置文件:
/**
* @description: 读取制定路径下的properties文件
* @author: zhangchi
* @email: zc_ciyuan@163.com
* @datetime: 2017/1/14 13:49
* @version: 1.0.0
*/
public class PropertiesLoader {
/**
* 日志引用
*/
private static final LogUtil logger = LogUtil.getInstance();
/**
* 定义存入properties属性的map集合
*/
public static ConcurrentMap<String, Properties> propertiesMap = null;
/**
* 加载配置文件
*/
public static void loader() throws IOException {
if (propertiesMap == null){
propertiesMap = new ConcurrentHashMap<>();
ResourcePatternResolver rpr = new PathMatchingResourcePatternResolver();
InputStreamReader inputStreamReader = null;
String path = ".././files/configuration/**/*.properties";
try {
/*
* 读取文件
*/
Resource[] resources = rpr.getResources(ResourceUtils.FILE_URL_PREFIX + path);
for (Resource r : resources) {
Properties properties = new Properties();
logger.info("初始化配置文件:", r.getURL().toString(), null);
inputStreamReader = new InputStreamReader(r.getInputStream(), "UTF-8");
properties.load(inputStreamReader);
propertiesMap.put(r.getFilename(), properties);
}
} catch (IOException e) {
logger.error(null, "获取制定路径下的properties文件异常", "", e);
} finally {
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
logger.error(null, "过去projects文件的InputStreamReader流关闭异常", "", e);
}
}
}
}
}
}
需要注意的是我们使用了spring的PathMatchingResourcePatternResolver类进行读取properties文件,通过使用通配符支持子目录文件查询。path路径采用的是相对路径方式,这里的“.././”就相当于在当前工作路径的上一级目录下要有个files目录来存放properties文件。./即系统参数${user.dir}。如果不考虑运维其实可以通过创建一个环境变量来定义指定路径,这样无论是本地开发(通常是windows)还是生产环境(linux)只要定义了环境变量,那么我们就不需要更改代码中的路径了。在代码中完全可以通过System.getenv("XXX")来获取,个人建议通过环境变量来制定路径是对代码侵入最小的方式,我在这里只是用了相对路径而已。
Java Config配置
以上的工具类只适用于java代码,那么如果xml中该如何读取需要的配置文件呢?spring支持通过file:/xxx/xx/x.properties这种方式制定读取文件。
<!-- <context:property-placeholder location="classpath*:props/*.properties" ignore-unresolvable="true"/>-->
<context:property-placeholder location="file:D:/files/**/*.properties" ignore-unresolvable="true"/>
- 这里我讲通过使用java config的方式尽量的避免使用xml文件,比如通过java配置数据源、SpringMVC属性配置等:
/**
* @description: 配置Druid数据源
* @author: zhangchi
* @email: zc_ciyuan@163.com
* @datetime: 2017/1/16 9:43
* @version: 1.0.0
*/
@Configuration
public class DruidDataSourceConfig {
/**
* 配置数据源
*
* @return dataSource
* @throws SQLException 抛出SQL异常
*/
//@Bean(name = "dataSource" , initMethod = "init",destroyMethod="close")
public DruidDataSource druidDataSource() throws SQLException {
DruidDataSource dds = new DruidDataSource();
/*获取配置属性,初始化连接池*/
String url = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.connectionURL");
if (!Strings.isNullOrEmpty(url)) {
dds.setUrl(url);
}
String userName = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.userId");
if (!Strings.isNullOrEmpty(userName)) {
dds.setUsername(userName);
}
String passWord = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.password");
if (!Strings.isNullOrEmpty(passWord)) {
dds.setPassword(passWord);
}
String initialSize = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.initialSize");
if (!Strings.isNullOrEmpty(initialSize)) {
dds.setInitialSize(Integer.parseInt(initialSize)); //初始化连接大小
}
String maxActive = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.maxActive");
if (!Strings.isNullOrEmpty(maxActive)) {
dds.setMaxActive(Integer.parseInt(maxActive)); //连接池最大使用连接数量
}
String maxIdle = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.maxIdle");
if (!Strings.isNullOrEmpty(maxIdle)) {
dds.setMaxIdle(Integer.parseInt(maxIdle)); //连接池最大空闲
}
String minIdle = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.minIdle");
if (!Strings.isNullOrEmpty(minIdle)) {
dds.setMinIdle(Integer.parseInt(minIdle)); //连接池最小空闲
}
String maxWait = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.maxWait");
if (!Strings.isNullOrEmpty(maxWait)) {
dds.setMaxWait(Long.parseLong(maxWait)); //获取连接最大等待时间
}
String validationQuery = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.validationQuery");
if (!Strings.isNullOrEmpty(validationQuery)) {
dds.setValidationQuery(validationQuery);
}
String testOnBorrow = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.testOnBorrow");
if (!Strings.isNullOrEmpty(testOnBorrow)) {
dds.setTestOnBorrow(Boolean.parseBoolean(testOnBorrow));
}
String testOnReturn = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.testOnReturn");
if (!Strings.isNullOrEmpty(testOnReturn)) {
dds.setTestOnReturn(Boolean.parseBoolean(testOnReturn));
}
String testWhileIdle = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.testWhileIdle");
if (!Strings.isNullOrEmpty(testWhileIdle)) {
dds.setTestWhileIdle(Boolean.parseBoolean(testWhileIdle));
}
String timeBetweenEvictionRunsMillis = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.timeBetweenEvictionRunsMillis");
if (!Strings.isNullOrEmpty(timeBetweenEvictionRunsMillis)) {
dds.setTimeBetweenEvictionRunsMillis(Long.parseLong(timeBetweenEvictionRunsMillis)); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
}
String minEvictableIdleTimeMillis = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.minEvictableIdleTimeMillis");
if (!Strings.isNullOrEmpty(minEvictableIdleTimeMillis)) {
dds.setMinEvictableIdleTimeMillis(Long.parseLong(minEvictableIdleTimeMillis)); //配置一个连接在池中最小生存的时间,单位是毫秒
}
String removeAbandoned = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.removeAbandoned");
if (!Strings.isNullOrEmpty(removeAbandoned)) {
dds.setRemoveAbandoned(Boolean.parseBoolean(removeAbandoned)); //打开removeAbandoned功能
}
String removeAbandonedTimeout = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.removeAbandonedTimeout");
if (!Strings.isNullOrEmpty(removeAbandonedTimeout)) {
dds.setRemoveAbandonedTimeout(Integer.parseInt(removeAbandonedTimeout));
}
String logAbandoned = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.logAbandoned");
if (!Strings.isNullOrEmpty(logAbandoned)) {
dds.setLogAbandoned(Boolean.parseBoolean(logAbandoned)); //关闭abanded连接时输出错误日志
}
String filters = PropertiesUtil.getPropertiesByKey(Constants.PROPERTIES_NAME_JDBC, "jdbc.filters");
if (!Strings.isNullOrEmpty(filters)) {
dds.setFilters(filters); //监控数据库
}
dds.init();
return dds;
}
/**
* 创建SqlSessionFactoryBean工厂实例
*
* @return SqlSessionFactoryBean
* @throws IOException 加载myBatis文件失败时抛出
* @throws SQLException 加载dataSource失败时抛出
*/
@Bean
@Scope(value = "singleton")
public SqlSessionFactoryBean sqlSessionFactory() throws IOException, SQLException {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(druidDataSource());
ResourcePatternResolver rpr = new PathMatchingResourcePatternResolver();
Resource[] resources = rpr.getResources("classpath*:mybatis/mapper/*.xml");
sessionFactoryBean.setMapperLocations(resources);
return sessionFactoryBean;
}
/**
* 创建MapperScannerConfigurer实例
*
* @return MapperScannerConfigurer
*/
@Bean
@Scope(value = "singleton")
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.xxx.xxx.xxx.dal.dao");
msc.setSqlSessionFactoryBeanName("sqlSessionFactory");
return msc;
}
/**
* @description: 配置springMVC
* @author: zhangchi
* @email: zc_ciyuan@163.com
* @datetime: 2017/1/18 16:20
* @version: 1.0.0
*/
@Configuration
@EnableWebMvc
@EnableAspectJAutoProxy(proxyTargetClass=true)//激活切面AOP
public class WebConfig extends WebMvcConfigurerAdapter {
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
* #configureViewResolvers(
* org.springframework.web.servlet.config.annotation.ViewResolverRegistry)
* 配置视图解析翻译从控制器返回到具体的基于字符串的视图名称视图 实现执行与渲染。
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp("/WEB-INF/views/", ".jsp");//注册JSP视图解析器指定的前缀和后缀。
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#addResourceHandlers(
* org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry)
* 添加处理程序服务静态资源,如图片,JS,并从其他受Web应用程序根目录的特定位置,类路径中,cssfiles。
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/styles/**").addResourceLocations("/styles/");
registry.addResourceHandler("/scripts/**").addResourceLocations("/scripts/");
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
/*
* (non-Javadoc)
*
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
* #addViewControllers( org.springframework.web.servlet.config.annotation.ViewControllerRegistry)
*
* 配置视图控制器
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("/login");
registry.addViewController("/login.do").setViewName("/login");
registry.addViewController("/index.do").setViewName("/login/index");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Date to/from json
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
converters.add(converter);
}
}
虽然通过JavaConfig能够避免一些通过xml加载的容器项,但是这并不能避免所有的配置需求,比如使用dubbo时需要注册zookeeper。针对于其他XML中需要读取properties元素的配置我们可以把properties还是加载到Spring的Properties对象里,只不过这一步我们也是通过JavaConfig来实现的。实际上我们是讲所有的properties文件存了两份,一份在Spring中一份在我们的Map<Properties>集合中。
/**
* @description: 加载properties文件
* @author: zhangchi
* @email: zc_ciyuan@163.com
* @datetime: 2017/1/18 20:31
* @version: 1.0.0
*/
@Configuration
@PropertySources({
@PropertySource(value = "file:.././files/configuration/common.properties",ignoreResourceNotFound = true,encoding = "UTF-8"),
@PropertySource(value = "file:.././files/configuration/constant.properties",encoding = "UTF-8"),
@PropertySource(value = "file:.././files/configuration/httpclient.properties"),
@PropertySource(value = "file:.././files/configuration/jdbc.properties"),
@PropertySource(value = "file:.././files/configuration/zookeeper.properties")
})
public class PropertiesConfig {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
}
XML中正常注册zook
<dubbo:application name="dubbo-channel-service"/>
<dubbo:registry address="${zookeeper.url}"/>
<dubbo:protocol name="dubbo" port="${dubbo.port}"/>
web.xml外化配置
部分配置文件是需要通过web.xml管理的例如logback.xml,web.xml中也可以指定加载路径。
<!--定义logback加载配置-->
<context-param>
<param-name>logbackConfigLocation</param-name>
<param-value>file:.././files/configuration/logback.xml</param-value>
</context-param>
<!--配置logback监听-->
<listener>
<listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
</listener>
总结:
以上内容将web开发中常遇到的配置进行了外化,主要就是通过JavaConfig、工具类、file:指定路径等方式把配置文件放到我们指定的路径中,避免了打包时配置文件出现在我们的war包或jar包里。从而避免了每次修改配置文件都需要重新打包编译一次再发版的烦人问题。这里没有介绍具体的分布式配置中心实现方式,因为那将是一个大话题需要根据具体项目的业务场景决定,所以以上内容只适用于学习参考。