1. 说明
- 接口注册,使用RequestMappingHandlerMapping来实现
- mybatis中动态执行sql使用github上的SqlMapper工具类实现
2. 核心代码片段
以下代码为spring动态注册接口代码示例
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
public boolean register2Spring(String path) {
RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(path)
.methods(RequestMethod.POST)
.produces(MediaType.APPLICATION_JSON_VALUE)
.options(requestMappingHandlerMapping.getBuilderConfiguration())
.build();
Method method = ReflectionUtils.findMethod(getClass(), "handler",
HttpServletRequest.class, HttpServletResponse.class,
Map.class, Map.class, Map.class);
boolean status = true;
try {
requestMappingHandlerMapping.registerMapping(requestMappingInfo, this, method);
LOGGER.info("【接口注册成功】{}", path);
} catch (Exception e) {
status = false;
LOGGER.error("【注册接口异常】动态映射失败", e.getMessage());
}
return status;
}
3. 源码
3.1 核心代码
3.1.1 ApiServiceHandler
handler中register职责如下:
- 注册到数据库中
- 注册接口到spring容器中
import com.alibaba.fastjson2.JSONObject;
import com.google.common.net.HttpHeaders;
import com.hz.pro.artifact.bean.CommonException;
import com.hz.pro.artifact.bean.Response;
import com.hz.pro.artifact.dynamic.bean.ServiceDto;
import com.hz.pro.artifact.dynamic.mapper.main.ApiServiceMapper;
import com.hz.pro.artifact.utils.SqlMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
/**
* @author pp_lan
* @date 2024/1/4
*/
@Service
public class ApiServiceHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ApiServiceHandler.class);
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Autowired
@Qualifier("sqlSessionFactory")
private SqlSessionFactory sqlSessionFactory;
@Autowired
private ApiServiceMapper apiServiceMapper;
public void initialRegister() {
List<ServiceDto> apis = findApis();
for (ServiceDto api : apis) {
try {
register2Spring(api.getPath());
} catch (Exception e) {
LOGGER.error("[接口注册失败]{}", api.getPath(), e.getMessage());
}
}
}
/**
* 注册到spring,并添加到数据库中
*
* @param path
* @param sql
* @return
*/
public boolean register(String path, String sql) {
boolean status = this.registerApiOfSql(path, sql);
if (status) {
status = register2Spring(path);
}
return status;
}
/**
* 注册到容器
*
* @param path
* @return
*/
public boolean register2Spring(String path) {
RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(path)
.methods(RequestMethod.POST)
.produces(MediaType.APPLICATION_JSON_VALUE)
.options(requestMappingHandlerMapping.getBuilderConfiguration())
.build();
Method method = ReflectionUtils.findMethod(getClass(), "handler",
HttpServletRequest.class, HttpServletResponse.class,
Map.class, Map.class, Map.class);
boolean status = true;
try {
requestMappingHandlerMapping.registerMapping(requestMappingInfo, this, method);
LOGGER.info("【接口注册成功】{}", path);
} catch (Exception e) {
status = false;
LOGGER.error("【注册接口异常】动态映射失败", e.getMessage());
}
return status;
}
@ResponseBody
public Response handler(HttpServletRequest request, HttpServletResponse response,
@PathVariable(required = false) Map<String, Object> pathVariable,
@RequestParam(required = false) Map<String, Object> requestParam,
@RequestBody(required = false) Map<String, Object> requestBody) {
String header = request.getHeader(HttpHeaders.CONTENT_TYPE);
// 参数处理
JSONObject params;
if (header != null && header.contains(MediaType.APPLICATION_JSON_VALUE)) {
params = new JSONObject(requestBody);
} else {
params = new JSONObject(requestParam);
}
// 执行查询
try (SqlMapper sqlMapper = new SqlMapper(sqlSessionFactory)) {
String path = request.getRequestURI();
String sql = apiServiceMapper.findSqlByPath(path);
List<Map<String, Object>> result = sqlMapper.selectList(sql, params);
return Response.ok(result);
} catch (Exception e) {
throw new CommonException("【公共查询异常】", e);
}
}
/**
* 查询所有在用接口
*
* @return
*/
public List<ServiceDto> findApis() {
return apiServiceMapper.findApis();
}
/**
* 注册接口
*
* @param path
* @param sql
* @return
*/
public boolean registerApiOfSql(String path, String sql) {
try {
return apiServiceMapper.insertApiSql(path, sql) > 0;
} catch (Exception e) {
throw new CommonException("【注册接口异常】插入sql配置失败", e);
}
}
}
3.1.2 DynamicController
手动注册接口执行/dynamic/register方法,便可以完成接口注册。
import com.hz.pro.artifact.bean.Response;
import com.hz.pro.artifact.dynamic.bean.ApiReq;
import com.hz.pro.artifact.dynamic.service.ApiServiceHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author pp_lan
* @date 2024/1/2
*/
@RestController
@RequestMapping("/dynamic")
public class DynamicController {
@Autowired
private ApiServiceHandler apiServiceHandler;
/**
* 注册接口
*
* @param apiReq
* @return
*/
@PostMapping("register")
public Response register(@RequestBody @Validated ApiReq apiReq) {
boolean registerStatus = apiServiceHandler.register(apiReq.getPath(), apiReq.getSql());
return registerStatus ? Response.ok("接口注册成功") : Response.error("接口注册失败");
}
}
3.2 依赖类
3.2.1 SqlMapper
此为github上有开源工具类,最新代码请移步github。以下为其源码:
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* MyBatis执行sql工具,在写SQL的时候建议使用参数形式的可以是${}或#{}
*
* 不建议将参数直接拼到字符串中,当大量这么使用的时候由于缓存MappedStatement而占用更多的内存
*
* @author liuzh
* @since 2015-03-10
*/
public class SqlMapper implements AutoCloseable {
private final MSUtils msUtils;
private final SqlSession sqlSession;
/**
* 构造方法,默认缓存MappedStatement
*
* @param sqlSession
*/
public SqlMapper(SqlSession sqlSession) {
this.sqlSession = sqlSession;
this.msUtils = new MSUtils(sqlSession.getConfiguration());
}
public SqlMapper(SqlSessionFactory sqlSessionFactory) {
this.sqlSession = sqlSessionFactory.openSession();
this.msUtils = new MSUtils(sqlSession.getConfiguration());
}
/**
* 获取List中最多只有一个的数据
*
* @param list List结果
* @param <T> 泛型类型
* @return
*/
private <T> T getOne(List<T> list) {
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
/**
* 查询返回一个结果,多个结果时抛出异常
*
* @param sql 执行的sql
* @return
*/
public Map<String, Object> selectOne(String sql) {
List<Map<String, Object>> list = selectList(sql);
return getOne(list);
}
/**
* 查询返回一个结果,多个结果时抛出异常
*
* @param sql 执行的sql
* @param value 参数
* @return
*/
public Map<String, Object> selectOne(String sql, Object value) {
List<Map<String, Object>> list = selectList(sql, value);
return getOne(list);
}
/**
* 查询返回一个结果,多个结果时抛出异常
*
* @param sql 执行的sql
* @param resultType 返回的结果类型
* @param <T> 泛型类型
* @return
*/
public <T> T selectOne(String sql, Class<T> resultType) {
List<T> list = selectList(sql, resultType);
return getOne(list);
}
/**
* 查询返回一个结果,多个结果时抛出异常
*
* @param sql 执行的sql
* @param value 参数
* @param resultType 返回的结果类型
* @param <T> 泛型类型
* @return
*/
public <T> T selectOne(String sql, Object value, Class<T> resultType) {
List<T> list = selectList(sql, value, resultType);
return getOne(list);
}
/**
* 查询返回List<Map<String, Object>>
*
* @param sql 执行的sql
* @return
*/
public List<Map<String, Object>> selectList(String sql) {
String msId = msUtils.select(sql);
return sqlSession.selectList(msId);
}
/**
* 查询返回List<Map<String, Object>>
*
* @param sql 执行的sql
* @param value 参数
* @return
*/
public List<Map<String, Object>> selectList(String sql, Object value) {
Class<?> parameterType = value != null ? value.getClass() : null;
String msId = msUtils.selectDynamic(sql, parameterType);
return sqlSession.selectList(msId, value);
}
/**
* 查询返回指定的结果类型
*
* @param sql 执行的sql
* @param resultType 返回的结果类型
* @param <T> 泛型类型
* @return
*/
public <T> List<T> selectList(String sql, Class<T> resultType) {
String msId;
if (resultType == null) {
msId = msUtils.select(sql);
} else {
msId = msUtils.select(sql, resultType);
}
return sqlSession.selectList(msId);
}
/**
* 查询返回指定的结果类型
*
* @param sql 执行的sql
* @param value 参数
* @param resultType 返回的结果类型
* @param <T> 泛型类型
* @return
*/
public <T> List<T> selectList(String sql, Object value, Class<T> resultType) {
String msId;
Class<?> parameterType = value != null ? value.getClass() : null;
if (resultType == null) {
msId = msUtils.selectDynamic(sql, parameterType);
} else {
msId = msUtils.selectDynamic(sql, parameterType, resultType);
}
return sqlSession.selectList(msId, value);
}
/**
* 插入数据
*
* @param sql 执行的sql
* @return
*/
public int insert(String sql) {
String msId = msUtils.insert(sql);
return sqlSession.insert(msId);
}
/**
* 插入数据
*
* @param sql 执行的sql
* @param value 参数
* @return
*/
public int insert(String sql, Object value) {
Class<?> parameterType = value != null ? value.getClass() : null;
String msId = msUtils.insertDynamic(sql, parameterType);
return sqlSession.insert(msId, value);
}
/**
* 更新数据
*
* @param sql 执行的sql
* @return
*/
public int update(String sql) {
String msId = msUtils.update(sql);
return sqlSession.update(msId);
}
/**
* 更新数据
*
* @param sql 执行的sql
* @param value 参数
* @return
*/
public int update(String sql, Object value) {
Class<?> parameterType = value != null ? value.getClass() : null;
String msId = msUtils.updateDynamic(sql, parameterType);
return sqlSession.update(msId, value);
}
/**
* 删除数据
*
* @param sql 执行的sql
* @return
*/
public int delete(String sql) {
String msId = msUtils.delete(sql);
return sqlSession.delete(msId);
}
/**
* 删除数据
*
* @param sql 执行的sql
* @param value 参数
* @return
*/
public int delete(String sql, Object value) {
Class<?> parameterType = value != null ? value.getClass() : null;
String msId = msUtils.deleteDynamic(sql, parameterType);
return sqlSession.delete(msId, value);
}
@Override
public void close() throws Exception {
this.sqlSession.close();
}
private class MSUtils {
private Configuration configuration;
private LanguageDriver languageDriver;
private MSUtils(Configuration configuration) {
this.configuration = configuration;
languageDriver = configuration.getDefaultScriptingLanuageInstance();
}
/**
* 创建MSID
*
* @param sql 执行的sql
* @param sql 执行的sqlCommandType
* @return
*/
private String newMsId(String sql, SqlCommandType sqlCommandType) {
StringBuilder msIdBuilder = new StringBuilder(sqlCommandType.toString());
msIdBuilder.append(".").append(sql.hashCode());
return msIdBuilder.toString();
}
/**
* 是否已经存在该ID
*
* @param msId
* @return
*/
private boolean hasMappedStatement(String msId) {
return configuration.hasStatement(msId, false);
}
/**
* 创建一个查询的MS
*
* @param msId
* @param sqlSource 执行的sqlSource
* @param resultType 返回的结果类型
*/
private void newSelectMappedStatement(String msId, SqlSource sqlSource, final Class<?> resultType) {
MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, SqlCommandType.SELECT)
.resultMaps(new ArrayList<ResultMap>() {
{
add(new ResultMap.Builder(configuration, "defaultResultMap", resultType, new ArrayList<ResultMapping>(0)).build());
}
})
.build();
//缓存
configuration.addMappedStatement(ms);
}
/**
* 创建一个简单的MS
*
* @param msId
* @param sqlSource 执行的sqlSource
* @param sqlCommandType 执行的sqlCommandType
*/
private void newUpdateMappedStatement(String msId, SqlSource sqlSource, SqlCommandType sqlCommandType) {
MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, sqlCommandType)
.resultMaps(new ArrayList<ResultMap>() {
{
add(new ResultMap.Builder(configuration, "defaultResultMap", int.class, new ArrayList<ResultMapping>(0)).build());
}
})
.build();
//缓存
configuration.addMappedStatement(ms);
}
private String select(String sql) {
String msId = newMsId(sql, SqlCommandType.SELECT);
if (hasMappedStatement(msId)) {
return msId;
}
StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
newSelectMappedStatement(msId, sqlSource, Map.class);
return msId;
}
private String selectDynamic(String sql, Class<?> parameterType) {
String msId = newMsId(sql + parameterType, SqlCommandType.SELECT);
if (hasMappedStatement(msId)) {
return msId;
}
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
newSelectMappedStatement(msId, sqlSource, Map.class);
return msId;
}
private String select(String sql, Class<?> resultType) {
String msId = newMsId(resultType + sql, SqlCommandType.SELECT);
if (hasMappedStatement(msId)) {
return msId;
}
StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
newSelectMappedStatement(msId, sqlSource, resultType);
return msId;
}
private String selectDynamic(String sql, Class<?> parameterType, Class<?> resultType) {
String msId = newMsId(resultType + sql + parameterType, SqlCommandType.SELECT);
if (hasMappedStatement(msId)) {
return msId;
}
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
newSelectMappedStatement(msId, sqlSource, resultType);
return msId;
}
private String insert(String sql) {
String msId = newMsId(sql, SqlCommandType.INSERT);
if (hasMappedStatement(msId)) {
return msId;
}
StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT);
return msId;
}
private String insertDynamic(String sql, Class<?> parameterType) {
String msId = newMsId(sql + parameterType, SqlCommandType.INSERT);
if (hasMappedStatement(msId)) {
return msId;
}
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT);
return msId;
}
private String update(String sql) {
String msId = newMsId(sql, SqlCommandType.UPDATE);
if (hasMappedStatement(msId)) {
return msId;
}
StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE);
return msId;
}
private String updateDynamic(String sql, Class<?> parameterType) {
String msId = newMsId(sql + parameterType, SqlCommandType.UPDATE);
if (hasMappedStatement(msId)) {
return msId;
}
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE);
return msId;
}
private String delete(String sql) {
String msId = newMsId(sql, SqlCommandType.DELETE);
if (hasMappedStatement(msId)) {
return msId;
}
StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE);
return msId;
}
private String deleteDynamic(String sql, Class<?> parameterType) {
String msId = newMsId(sql + parameterType, SqlCommandType.DELETE);
if (hasMappedStatement(msId)) {
return msId;
}
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE);
return msId;
}
}
}
3.2.2 ApiServiceMapper
import com.hz.pro.artifact.dynamic.bean.ServiceDto;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author pp_lan
* @date 2024/1/5
*/
public interface ApiServiceMapper {
List<ServiceDto> findApis();
String findSqlByPath(@Param("path") String path);
int insertApiSql(@Param("path") String path, @Param("sqlContent") String sqlContent);
}
3.2.3 ApiServiceMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hz.pro.artifact.dynamic.mapper.main.ApiServiceMapper">
<select id="findApis" resultType="com.hz.pro.artifact.dynamic.bean.ServiceDto">
select path, sql_content from t_api_sql where in_use = 1
</select>
<select id="findSqlByPath" resultType="java.lang.String">
select sql_content from t_api_sql where path = #{path} and in_use = 1
</select>
<insert id="insertApiSql">
INSERT INTO t_api_sql VALUES(#{path}, #{sqlContent}, 1)
</insert>
</mapper>
4 效果
4.1 注册
4.2 查询
5. 其他
上述实现步骤已完成接口的注册、查询功能。但是存在一个问题,重启后接口便不存在了,需要重新初始化。后续可以使用监听读取数据库中接口配置进行接口的初始化。