笔者的工作内容主要是研发框架,即提供了一整套平台框架(登陆模块、菜单模块、组织机构模块、权限模块、流程、表单、润乾。。。),其中业务程序开发模块供业务开发者进行二次开发使用。通常,业务开发者只需要在引导页面输入一些表名、字段名等信息,就可以完整地生成一个程序页面(列表、维护、树型页面等)。但是,业务需求总是千差万别的,平台框架不可能满足所有现场的特殊需求,这时候,就需要业务开发者在我们限定的方案内进行二次开发。
举个栗子,下图是平台框架中自动生成的新建维护页面;比如现在有个特殊需求,在打开新建维护页面的时候,根据登陆人的信息,自动给【工程项目】赋值,这就需要业务开发者进行二次开发了。
肯定是不能改平台框架的代码了,我们提供了修改业务细节的方法。他们可以在该程序对应的Java代码beforeInit()方法中,给【工程项目】赋值,如下图。
那么,问题来了,如果要在上图的beforeInit()方法中使用DB连接怎么办?
可以看到 业务类CMCCCC00101.java继承了M.java,而M的除Object.java的顶级父类是Business.java,我们给Business.java添加了属性db,在它构造方法里给db赋值,然后Business.java的子类可以随意使用这些创建好的db。
我们并不知道开发者何时结束使用这个db,所以不能主动去关闭db。那就需要一种特别的方式去关闭由业务类的顶级父类Business.java所创建的db。方法是,将Business.java构造时创建的db加入到本地线程中,当请求结束的时候截取并清空本地线程中的db对象。
//在Business构造处,将创建好的DB加入数据库线程本地map中
public class DatabaseHolder {
/**
* 数据库线程本地map
*/
public static final ThreadLocal<List<Database>> dbThreadLocalMap = new ThreadLocal<>();
/**
* 将db存入数据库本地线程map中
* @author chengmeng
*/
public static void addDB(Database db) {
if(dbThreadLocalMap.get() == null) {
dbThreadLocalMap.set(new ArrayList<Database>());
}
dbThreadLocalMap.get().add(db);
}
}
@Aspect
@Component
public class DbCleanAspect {
/**
* 在ResponseDataHandler中截获响应,并触发清理本地线程中的未关闭连接的db
* @author chengmeng
*/
public static void cleanDbAfterRequest() {
try {
List<Database> list = DatabaseHolder.dbThreadLocalMap.get();
if(CollectionUtils.isEmpty(list)) {
return;
}
for(Database db : list) {
if(db != null) {
db.cleanup();
}
}
DatabaseHolder.dbThreadLocalMap.set(new ArrayList<>());
} catch (Exception e) {
Log.error(">>>> 切面程序清理数据库连接时发生异常:", e);
}
}
}
上面是我们提供给业务程序类的db,使他们不需要关注db的创建与销毁从而专注功能的开发。
但除了业务程序类,还有很多其它非标准程序类,这时怎么规范他们用db呢?采用拉姆达表达式:
//拉姆达表达式,当TODO操作完毕,db会自动释放连接
Object obj = DataBase.execute(db->{
//TODO
});
不仅如此,我们还封闭了他们通过DataBase类创建实例的渠道,我们将DataBase.getInstance()方法修改为私有,并在其中加入了调用方的路径判断,非白名单内的Java类路径调用getInstance()时会直接报错。
public static DataBase getInstance() throws Exception{
if( 调用方法 in 白名单 ){
return DB对象;
}else{
throw new Exception("此方法仅供平台使用,业务程序请勿调用!");
}
}