×
文章路径: Java

网站配置的一种设计实现方法

发表于3年前(Jan 7, 2015 11:22:17 AM)  阅读 816  评论 0

分类: Java 案例

标签: 网站配置 annotation

1、网站配置:

一个网站都有很多配置选项,如笔者的博客里面就有基本配置、阅读配置、评论配置等:

有了这些配置管理功能,我们在开发时就不需要硬编码,可以实现在线修改配置选项,不需要重启服务。这是一个网站很常见的做法。

2、简单实现:

一般来说,实现这个配置管理很简单,建立一个配置实体类,映射数据库中的一个表,无非就是对这个实体实现增删改查。后期如果要修改,比如增加了一个配置选项,那就修改实体类,增加属性,增加数据库字段,修改页面,可能要修改对应的合法性验证。虽然修改的地方较多,但也没那么麻烦,都是简单的体力劳动,也没什么费劲的。

3、多个配置怎么办:

如果系统里面就一两个这样的配置,采用上面的方法很快就完成了,但如果系统中有很多这样的配置,那工作量就有点大了。这时我们就会偷下懒了,就要想到继承了,看看配置是不是基本都是相似的。而且,难道我们给每一个配置建立一个对应的映射表,每个表里n多字段只有一条数据,这样好像有点浪费。

回过头来看上面的截图,笔者的两个配置,怎么抽象?笔者看了半天没看明白,只发现每个配置项基本都类似,但不知道如何抽象,能使增删改查的代码变得通用。

如果把所有配置都合并成一个配置,多个配置建立多个实体,不同配置管理页面展示不同的配置项,每个配置管理自己的配置项,这样数据库的增删改查应该可以通过不同的实体来控制实现通用,这就部分回到了第二节我们说到的做法,有了些许改进。不过这种做法可能会造成出现一个“大”表,这个大并不是指数据量大(相反我们数据才一条),而是字段非常多,非常难看。

4、“大”表变“小”表:

上面说到,每个配置项基本都是类似的,可以说都是对应一个字符串,其实所有配置项就是一个map。有没有办法把大表变小表?那我们把列转成行是不是就行了。考虑如下表结构,两个字段,一个是key,一个是value,是不是就是可以把所有配置保存进去了。确实,这样每次操作的表结构固定了,应该可以减少大量工作了。

5、笔者的实现:

笔者当初实现这个功能,基本上按照上面的思路过来的。这样实现到底好不好,仁者见仁智者见智。

首先实现Options实体类,每个Options就代表一个配置项,一对key/value值,数据库里的一行记录,所以他是真正跟数据库打交道的实体,只要他不改变,数据库dao是不需要修改的:

package com.cangzhitao.blog.entities;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

import com.cangzhitao.framework.basic.util.IdGenerator;

/**
 * 配置字典
 * @author cangzhitao
 * @date Aug 18, 2013 1:09:50 PM
 */
@Entity
@Table(name="options")
public class Options implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3810672183231180121L;

	/**
	 */
	@Id
	@Column(name="option_id")
	@GeneratedValue(strategy=GenerationType.TABLE,generator=IdGenerator.OPTIONS_ID_GENERATOR)
	@TableGenerator(
			name = IdGenerator.OPTIONS_ID_GENERATOR,
			table = IdGenerator.GENERATOR_TABLE,
			pkColumnName = IdGenerator.GEN_NAME,
			pkColumnValue = IdGenerator.OPTIONS_ID_GENERATOR,
			valueColumnName = IdGenerator.GEN_VALUE,
			initialValue = 1,
			allocationSize = 1
	)
	private long option_id ;
	
	@Column(name="option_type")
	private String option_type = "";
	
	@Column(name="option_name")
	private String option_name = "";
	
	@Column(name="option_desc")
	private String option_desc = "";
	
	@Column(name="option_value")
	private String option_value = "";

	/**
	 * @return the option_type
	 */
	public String getOption_type() {
		return option_type;
	}

	/**
	 * @param option_type the option_type to set
	 */
	public void setOption_type(String option_type) {
		this.option_type = option_type;
	}

	/**
	 * @return the option_name
	 */
	public String getOption_name() {
		return option_name;
	}

	/**
	 * @param option_name the option_name to set
	 */
	public void setOption_name(String option_name) {
		this.option_name = option_name;
	}

	/**
	 * @return the option_desc
	 */
	public String getOption_desc() {
		return option_desc;
	}

	/**
	 * @param option_desc the option_desc to set
	 */
	public void setOption_desc(String option_desc) {
		this.option_desc = option_desc;
	}

	/**
	 * @return the option_value
	 */
	public String getOption_value() {
		return option_value;
	}

	/**
	 * @param option_value the option_value to set
	 */
	public void setOption_value(String option_value) {
		this.option_value = option_value;
	}
	
	public String getInsertSql() {
		return "insert into options(option_name, option_desc, option_value, option_type) values('"+option_name+"', '"+option_desc+"', '"+option_value+"', '"+option_type+"')";
	}

	public long getOption_id() {
		return option_id;
	}

	public void setOption_id(long option_id) {
		this.option_id = option_id;
	}
}

Options里增加了option_type属性,为了便于区分不同配置,也可以使不同配置下面运行存在相同的配置项名。这样,每个配置其实就是一个Options的集合。现在我们来建立这么一个基类,BaseOption:

package com.cangzhitao.blog.config;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.codehaus.jackson.annotate.JsonIgnore;

import com.cangzhitao.blog.entities.Options;
import com.cangzhitao.framework.basic.util.StringUtil;

public class BaseOption {
	

	private Map<String, String> annotationMap = null;
	
	private static BaseOption baseOption = null;
	
	@JsonIgnore
	public static BaseOption getInstance() {
		if(baseOption==null) {
			baseOption = new BaseOption();
		}
		return baseOption;
	}
	
	/**
	 * 将属性组装成实例对象
	 * @param classs
	 * @param optionList
	 * @return
	 */
	@JsonIgnore
	public static BaseOption optionListPackage(Class<?> classs, List<Options> optionList) {
		try {
			Object baseOption = classs.newInstance();
			for(int i=0;i<optionList.size();i++) {
				Options o = optionList.get(i);
				String optionName = o.getOption_name();
				Method method = classs.getMethod("set"+StringUtil.initialString(optionName), String.class);
				method.invoke(baseOption, new Object[]{o.getOption_value()});
			}
			return (BaseOption) baseOption;
		} catch(Exception e) {
			
		}
		return null;
	}
	
	/**
	 * 将属性转换为Option
	 * @return
	 */
	@JsonIgnore
	public List<Options> getOptionList() {
		List<Options> list = new ArrayList<Options>();
		try {
			Class<? extends BaseOption> c = this.getClass();
			String option_type = c.getSimpleName();
			for(Field field : c.getDeclaredFields()) {
				ChangeToOption changeToOption = field.getAnnotation(ChangeToOption.class);
				if(changeToOption!=null) {
					Options option = new Options();
					option.setOption_type(option_type);
					option.setOption_name(field.getName());
					Method method = c.getMethod("get"+StringUtil.initialString(field.getName()));
					option.setOption_value(method.invoke(this).toString());
					option.setOption_desc(changeToOption.option_desc());
					list.add(option);
				}
			}
			
		} catch(Exception e) {
			e.printStackTrace();
			return new ArrayList<Options>();
		}
		return list;
	}
	
	@JsonIgnore
	public List<String> getInsertSql() {
		List<Options> list = this.getOptionList();
		List<String> sqlList = new ArrayList<String>();
		for(int i=0;i<list.size();i++) {
			Options option = list.get(i);
			String sql = option.getInsertSql();
			sqlList.add(sql);
		}
		return sqlList;
	}
	
	
	public String getOptionType() {
		Class<? extends BaseOption> c = this.getClass();
		String option_type = c.getSimpleName();
		return option_type;
	}

	@JsonIgnore
	public Map<String, String> getAnnotationMap() {
		if(annotationMap==null) {
			annotationMap = new HashMap<String, String>();
			Class<? extends BaseOption> c = this.getClass();
			for(Field field : c.getDeclaredFields()) {
				ChangeToOption changeToOption = field.getAnnotation(ChangeToOption.class);
				if(changeToOption!=null) {
					annotationMap.put(field.getName(), changeToOption.option_desc());
				}
			}
		}
		return annotationMap;
	}

	
	
}
我们先只看里面的getOptionList与getOptionType方法,getOptionList就是返回该配置的所有配置项,修改配置就是针对这个集合了,getOptionType就是返回配置类型了,来区分不同的配置。

我们再来看BaseOption的一个实现,BlogConfig:

按理BlogConfig应该提供一个自己的Options集合,笔者稍微变化了一下,直接提供的配置项属性名,这样看起来更加明了,通过调用父类的getOptionList来把属性转化为需要的集合。这里使用了自定义的annotation ChangeToOption:

package com.cangzhitao.blog.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 标志是否需要转成option配置
 * @author cangzhitao
 * @date Aug 18, 2013 1:19:49 PM
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ChangeToOption {
	
	public String option_desc();

}

使用annotation有两个用意,一是用来标志哪些属性是要转成集合的,二是用来定于属性的描述信息。

再来看看dao如何操作配置:

package com.cangzhitao.blog.dao.impl;

import java.util.List;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.cangzhitao.blog.config.BaseOption;
import com.cangzhitao.blog.dao.IOptionsDao;
import com.cangzhitao.blog.entities.Options;
import com.cangzhitao.framework.basic.dao.impl.BaseDao;

@Repository(value="optionsDao")
public class OptionsDao extends BaseDao<BaseOption, Long> implements IOptionsDao {

	/**
	 * 更新指定配置
	 */
	@Transactional
	@Override
	public void updateBaseOption(BaseOption baseOption) {
		this.deleteBaseOption(baseOption);
		this.getHibernateTemplate().saveOrUpdateAll(baseOption.getOptionList());
		//this.getJdbcTemplate().batchUpdate(baseOption.getInsertSql().toArray(new String[]{}));
	}

	@Transactional
	@Override
	public void deleteBaseOption(BaseOption baseOption) {
		String sql = "delete from options where option_type='"+baseOption.getOptionType()+"'";
		this.getJdbcTemplate().execute(sql);
	}
	
	@Transactional
	@SuppressWarnings("unchecked")
	public BaseOption findBaseOption(Class<?> classs) {
		String hql = "from Options o where o.option_type=?";
		List<Options> optionList = this.getHibernateTemplate().find(hql, classs.getSimpleName());
		return BaseOption.optionListPackage(classs, optionList);
	}

}
可以看到,有这三个方法足以。需要注意的是,每次更新配置其实是把之前的配置删除,然后插入新的配置项。

页面上可以利用annotation里面的描述来展现,这样保持一致,是一种比较好的习惯:

最后我们来看下如果修改一个配置需要做些什么操作。首先要修改相应的配置实体,然后修改配置页面,这样就够了。如果属性都是由用户手动输的话,还可以页面代码都自动生成,那样页面都不需要我们来管了。

<tr>
	<td width="30%"><span class="fl ml10">${blogConfig.annotationMap["webName"] }:</span></td>
  <td><span class="fl ml10"><input type="text" name="webName" value="${blogConfig.webName}" /></span></td>
</tr>
<tr>
	<td width="30%"><span class="fl ml10">${blogConfig.annotationMap["secondName"] }:</span></td>
	<td><span class="fl ml10"><input type="text" name="secondName" value="${blogConfig.secondName}" /></span></td>
</tr>
<tr>
	<td width="30%"><span class="fl ml10">${blogConfig.annotationMap["url"] }:</span></td>
	<td><span class="fl ml10"><input type="text" name="url" value="${blogConfig.url}" /></span></td>
</tr>
<tr>
	<td width="30%"><span class="fl ml10">${blogConfig.annotationMap["contextPath"] }:</span></td>
	<td><span class="fl ml10"><input type="text" name="contextPath" value="${blogConfig.contextPath}" /></span></td>
</tr>
<tr>
	<td width="30%"><span class="fl ml10">${blogConfig.annotationMap["author"] }:</span></td>
	<td><span class="fl ml10"><input type="text" name="author" value="${blogConfig.author}" /></span></td>
</tr>
<tr>
	<td width="30%"><span class="fl ml10">${blogConfig.annotationMap["email"] }:</span></td>
	<td><span class="fl ml10"><input type="text" name="email" value="${blogConfig.email}" /></span></td>
</tr>
<tr>
	<td width="30%"><span class="fl ml10">${blogConfig.annotationMap["keywords"] }:</span></td>
	<td><span class="fl ml10"><input type="text" name="keywords" value="${blogConfig.keywords}" /></span></td>
</tr>
6、后记:

讲了一下大概思路,代码里面有些方法没有具体解释,需要读者自己研究了。还有有些代码已经没有在使用,但笔者还没删除,不用理会。

发表评论