大学毕业之后到公司,就是velocity+springMVC+srping+ibatis,所以一直在用ibatis做持久层,其他的几个框架也都是稍有了解。
好了屁话少说进入正题:之前有写一篇文章 《java webapp嵌入jetty》 为的就是能快速开发,直接在eclipse做debug很是方便。但是呢,用了ibatis,在sqlmap中写了sql,如果每次修改了sqlmap,那么就要每次都重启应用才行,使用起来很是蛋疼,如果项目小,也就是分分钟的事,如果工程足够大,那么重启一次就够受了!
于是就在考虑,能否每次手动来控制ibatis重新加载已经修改好的sqlmap呢?这就可以不用重启了。
答案肯定是可以的~ 毕竟在spring做bean初始化的时候就会加载ibatis的sqlmap,所以只要我们找到对应的代码,然后做出一些调整就可以实现重新加载了。
OK,看代码~~
首先SqlmapClientFactoryBean是spring给ibatis做的适配,那么我们就要从这个类看起。
可以看到ibatis的sqlmapClient是通过configParser返回的。
在new SqlMapConfigParser()的时候,由于以下的引用关系。
SqlMapConfigParser--XmlParserState--SqlMapConfiguration---SqlMapExecutorDelegate
SqlMapConfigParser会 将上面的类初始化,并且在SqlMapConfiguration初始化的时候,将new的SqlMapExecutorDelegate赋值给SqlMapClientImpl而SqlMapExecutorDelegate才是真正的执行代理类,并且所有的sqlmap解析都被它保存着。
解析过程不详细表述了,感兴趣的同学可以看源码。
最终通过SqlMapExecutorDelegate.addMappedStatement方法,把解析出来的sqlmapstatement保存到map中sqlmap的id为key值
代码如上,可以看到每次添加会验证id是否重复。这也就是为啥一个sql中如果有两个相同id就会报错的原因。
好了!代码看清楚了,接下来就看我们怎么来修改代码了。
主要就是SqlmapClientFactoryBean、SqlMapExecutorDelegate、SqlMapClientImpl三个类
我们想重新刷新,那么就要操作SqlMapExecutorDelegate中的map,然后ibatis和spring的接口只有SqlMapClientImpl这个类,并且spring的适配是SqlmapClientFactoryBean
于是乎,思路就来了,只要我们重写SqlmapClientFactoryBean让它返回我们重写过的SqlMapClientImpl,在SqlMapClientImpl添加刷新的方法,然后通过SqlMapClientImpl来调用我们代理的SqlMapExecutorDelegate就可以实现重新加载了~
代码如下:
SqlmapClientFactoryBean
在添加返回前,声明出我们重写的对sqlmapClientImpl的代理
package com.h2o3.right.dal.platform; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.util.Properties; import org.springframework.core.NestedIOException; import org.springframework.core.io.Resource; import com.ibatis.sqlmap.client.SqlMapClient; import com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser; import com.ibatis.sqlmap.engine.builder.xml.XmlParserState; import com.ibatis.sqlmap.engine.config.SqlMapConfiguration; import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient; import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl; /** * sqlMapClientImpl的代理类。 * * <pre> * 这个类是所有sql操作的入口,并且存放了SqlMapExecutorDelegate这个sql执行器的代理类。 * 并且这个类是在spring中的入口。 * * 所以代理这个类,将自己的delegate设置进去,并且反射自己的delegate到SqlMapConfiguration中 * 保证重新加载sqlmap的时候,操作的是自己的delegate,这样就不会触发原生delegate中的重复判断。 * </pre> * * @author yuezhen * */ public class H2o3SqlMapClientImpl extends SqlMapClientImpl { /** * Delegate for SQL execution */ public H2o3SqlMapExecutorDelegate h2o3Delegate; /** * sqlmap的路径 */ private Resource[] configLocations; /** * config转换器 */ private SqlMapConfigParser configParser; /** * sqlmapclient配置的properties,spring传入 */ private Properties properties; /** * 构造方法。 * * @param client * @param configLocations * @param configParser * @param properties */ public H2o3SqlMapClientImpl(SqlMapClient client, Resource[] configLocations, SqlMapConfigParser configParser, Properties properties) { super(new H2o3SqlMapExecutorDelegate(((ExtendedSqlMapClient) client) .getDelegate())); this.h2o3Delegate = (H2o3SqlMapExecutorDelegate) this.delegate; this.configLocations = configLocations; this.configParser = configParser; this.properties = properties; relfectDelegate(); } /** * 重新刷新。 * * @throws IOException */ public void fresh() throws IOException { // 调用configParser来重新加载 for (Resource configLocation : configLocations) { InputStream is = configLocation.getInputStream(); try { configParser.parse(is, properties); } catch (RuntimeException ex) { throw new NestedIOException("Failed to parse config resource: " + configLocation, ex.getCause()); } } } /** * 反射将自己的delegate,反射到SqlMapConfiguration中。 */ public void relfectDelegate() { try { Field stateField = this.configParser.getClass().getDeclaredField( "state"); stateField.setAccessible(true); XmlParserState state = (XmlParserState) stateField .get(this.configParser); Field configFiled = state.getClass().getDeclaredField("config"); configFiled.setAccessible(true); SqlMapConfiguration configField = (SqlMapConfiguration) configFiled .get(state); Field clientField = configField.getClass().getDeclaredField( "client"); clientField.setAccessible(true); clientField.set(configField, this); Field delegateField = configField.getClass().getDeclaredField( "delegate"); delegateField.setAccessible(true); delegateField.set(configField, this.delegate); } catch (Exception e) { e.printStackTrace(); } } public H2o3SqlMapExecutorDelegate getMydelegate() { return h2o3Delegate; } }
代理中做保存自己所写的SqlMapExecutorDelegate代理,并且把自己的SqlMapExecutorDelegate通过反射的方式放到SqlMapConfiguration中,以保证从新加载的时候用的是我们的SqlMapExecutorDelegate。
并且实现刷新的方法:将传入的sqlmap路径,在调用一次parser的解析。
在SqlMapExecutorDelegate方法中,由于这个类太多不可见,没办法,只能很土的方法,一是写个代理,代理他所有的方法,并且重写addmapping的方法。二是通过反射把所有的属性反射进去。然后重写addmapping。
这里采用第一种方案
将以前的重复判断给去掉。
最后,web层做一个刷新的controller,将sqlmapclient注入进去,然后通过refesh方法来进行刷新即可。
这样,每次修改了sql之后,访问一下refersh.htm就重新加载了sqlmap!!
这里只是实现通过url手动刷新,如果大家感兴趣,还可以设置,没法访问db都去刷新,方法类似,都是修改SqlMapExecutorDelegate这个类的不同调用方法即可。
已经测试通过,ibatis基于org/apache/ibatis/ibatis-sqlmap/2.3.4.726/ibatis-sqlmap-2.3.4.726-sources.jar
附上四个文件下载链接 http://download.csdn.net/detail/lywybo/5613303
H2o3SqlMapClientFactoryBean.java
H2o3SqlMapClientImpl.java
H2o3SqlMapExecutorDelegate.java
RefershController.java
使用的时候将文件放到代码中,然后配置sqlmapclient为H2o3SqlMapClientFactoryBean即可。
然后配合上篇文章 《java webapp嵌入jetty》 来启动,一个很方便的开发环境就做好了。
等过几天有空,会把这个空的框架搭起来,方便大家使用。