batis-rest是一个简单而优雅的组件,可以将mybatis的mapper操作方法暴露为rest接口,从而减少service和controller的代码开发。
使用方法和源码详见https://gitee.com/wangbaishi_libi/data-rest。
本组件实现过程:
- 组件初始化(BatisRestAutoConfig);
- 扫描mybatis mapper方法(MapperComponentScanAndRegister);
- 将mapper方法代理对象注册为rest接口。
SpringCloud的data-rest组件功能令我印象深刻,于是打算参考该组件给mybatis做一个自动rest化的组件,毕竟国内用mybatis还是蛮多的。
先扫描mapper。mybatis内部mapper的管理类是MapperRegistry,它可以通过mybatis的configuration对象取到。为了方便,本组件内只引用了SqlSessionTemplate,通过这个bean对象获取mapper。注册Spring的ContextRefreshedEvent事件,待mybatis初始化之后就可以获取到mapper了。
sqlSessionTemplate.getConfiguration().getMapperRegistry().getMappers().forEach(mapperCls -> {
if(mapperCls.getAnnotation(EnableRestMapper.class) != null){
restInterfaceRegister.register(mapperCls);
}
});
构建代理mapper代理。一般情况下,接口(Interface)方法是不能执行的,为了能调用mapper方法,需要用到代理对象Proxy(实际上,mybatis也是给mapper方法构建了代理对象),用于处理mapper方法对应的实际逻辑。
执行操作。Mybatis中每个mapper方法会有一个对应的MapperMethod对象来执行,这里我们可以通过构造MapperMethod来实现mapper的具体逻辑。
public class RestMapperProxy implements InvocationHandler, Serializable {
Class<?> mapperInterface;
SqlSessionTemplate sqlSessionTemplate;
public RestMapperProxy(Class<?> mapperInterface, SqlSessionTemplate sqlSessionTemplate) {
this.mapperInterface = mapperInterface;
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
MapperMethod mapperMethod = new MapperMethod(mapperInterface, method, sqlSessionTemplate.getConfiguration());
return mapperMethod.execute(sqlSessionTemplate, args);
}
}
rest接口注册。Spring中接口映射管理对象为requestMappingHandlerMapping,注册方法参数中,mapper方法的代理对象作为handler参数,mapper方法名称作为RequestMappingInfo的映射地址,sql类型转化为请求类型。
public Object register(Class<?> mapperInterface){
EnableRestMapper enableRestMapper = mapperInterface.getAnnotation(EnableRestMapper.class);
RestMapperProxy restMapperProxy = new RestMapperProxy(mapperInterface, sqlSessionTemplate);
//注册代理
Object proxy = Proxy.newProxyInstance(RestInterfaceRegister.class.getClassLoader(), new Class[]{mapperInterface}, restMapperProxy);
//注册接口
for(Method method : mapperInterface.getMethods()){
String uri = enableRestMapper.value() + "/" + method.getName();
log.info("mapper rest [{} - {}]", uri, method.getDeclaringClass());
RequestMappingInfo mappingInfo = new RequestMappingInfo(
new PatternsRequestCondition(uri),
new RequestMethodsRequestCondition(commandTypeToRequestMethod(method)),
null, null, null, null, null);
requestMappingHandlerMapping.registerMapping(mappingInfo, proxy, method);
}
return proxy;
}
private RequestMethod commandTypeToRequestMethod(Method method){
MappedStatement statement = sqlSessionTemplate.getConfiguration().getMappedStatement(method.getName());
SqlCommandType commandType = statement.getSqlCommandType();
if(SqlCommandType.SELECT == commandType){
return RequestMethod.GET;
}
return RequestMethod.POST;
}
参数匹配。正常编译情况下,Spring的几个ParameterNameDiscoverer是无法获取Interface方法参数名称的。mybatis为确保获取参数名称,增加了@Param注解。于是我自定义了BatisParameterNameDiscoverer,用于适配mybatis的ParamNameResolver。虽然没有经过完整测试,但我试了多参数、对象参数等情况,都是完美支持的。
结果装配。@EnableRestMapper继承了@ResponseBody,意味着加了EnableRestMapper注解的mapper返回结果会使用ResponseBody组装。
后续。本来想多花点时间好好做一下,为此专门研究了data-rest和mybatis的源码,实际却很快搞定了上述核心功能,这或许就是Spring强大的框架集成和扩展能力吧。当然,还有诸如事务、异常处理、结果封装等场景尚未经过测试和处理,敬请期待!