🚀 优质资源分享 🚀
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
1.mybatis拦截器介绍
拦截器可在mybatis进行sql底层处理的时候执行额外的逻辑,最常见的就是分页逻辑、对结果集进行处理过滤敏感信息等。
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}public Executor newExecutor(Transaction transaction) {return newExecutor(transaction, defaultExecutorType);}public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;}
从上面的代码可以看到mybatis支持的拦截类型只有四种(按拦截顺序)
1.Executor 执行器接口
2.StatementHandler sql构建处理器
3.ParameterHandler 参数处理器
4.ResultSetHandler 结果集处理器
2.拦截器原理
public class InterceptorChain {private final List interceptors = new ArrayList<>();// 遍历定义的拦截器,对拦截的对象进行包装public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}public List getInterceptors() {return Collections.unmodifiableList(interceptors);}}#Interceptor
public interface Interceptor {Object intercept(Invocation invocation) throws Throwable;default Object plugin(Object target) {return Plugin.wrap(target, this);}default void setProperties(Properties properties) {// NOP}}
mybatis拦截器本质上使用了jdk动态代理,interceptorChain拦截器链中存储了用户定义的拦截器,会遍历进行对目标对象代理包装。
用户自定义拦截器类需要实现Interceptor接口,以及实现intercept方法,plugin和setProperties方法可重写,plugin方法一般不会改动,该方法调用了Plugin的静态方法wrap实现了对目标对象的代理
public class Plugin implements InvocationHandler {// 拦截目标对象private final Object target;// 拦截器对象-执行逻辑private final Interceptor interceptor;// 拦截接口和拦截方法的映射private final Map, Set> signatureMap;private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}// 获取jdk代理对象public static Object wrap(Object target, Interceptor interceptor) {// 存储拦截接口和拦截方法的映射Map, Set> signatureMap = getSignatureMap(interceptor);Class type = target.getClass();// 获取拦截目标对象实现的接口,若为空则不代理Class[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 获取需要拦截的方法集合,若不存在则使用目标对象执行Set methods = signatureMap.get(method.getDeclaringClass());if (methods != null && methods.contains(method)) {// Invocation存储了目标对象、拦截方法以及方法参数return interceptor.intercept(new Invocation(target, method, args));}return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}private static Map, Set> getSignatureMap(Interceptor interceptor) {// 获取Intercepts注解值不能为空Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);// issue #251if (interceptsAnnotation == null) {throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());}Signature[] sigs = interceptsAnnotation.value();// key 拦截的类型Map, Set> signatureMap = new HashMap<>();for (Signature sig : sigs) {Set methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());try {// 获取拦截的方法Method method = sig.type().getMethod(sig.method(), sig.args());methods.add(method);} catch (NoSuchMethodException e) {throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);}}return signatureMap;}private static Class[] getAllInterfaces(Class type, Map, Set> signatureMap) {Set> interfaces = new HashSet<>();while (type != null) {for (Class c : type.getInterfaces()) {if (signatureMap.containsKey(c)) {interfaces.add(c);}}type = type.getSuperclass();}return interfaces.toArray(new Class[interfaces.size()]);}}折叠
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {/*** Returns method signatures to intercept.** @return method signatures*/Signature[] value();
}@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {/*** Returns the java type.** @return the java type*/Class type();/*** Returns the method name.** @return the method name*/String method();/*** Returns java types for method argument.* @return java types for method argument*/Class[] args();
}
可以看到,当被拦截的方法被执行时主要调用自定义拦截器的intercept方法,把拦截对象、方法以及方法参数封装成Invocation对象传递过去。
在getSignatureMap方法中可以看到,自定义的拦截器类上需要添加Intercepts注解并且Signature需要有值,Signature注解中的type为需要拦截对象的接口(Executor.class/StatementHandler/ParameterHandler/ResultSetHandler),method为需要拦截的方法的方法名,args为拦截方法的方法参数类型。
3.参考例子
接下来举一个拦截器实现对结果集下划线转驼峰的例子来简要说明
/*** @author dxu2* @date 2022/7/14* map结果转驼峰*/
@Intercepts(value = {@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MyInterceptor implements Interceptor {@SuppressWarnings("unchecked")@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 调用目标方法List result = (List) invocation.proceed();for (Object o : result) {if (o instanceof Map) {processMap((Map) o);} else {break;}}return result;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}private void processMap(Map map) {Set keySet = new HashSet<>(map.keySet());for (String key : keySet) {if ((key.charAt(0) >= 'A' && key.charAt(0) <= 'Z') || key.indexOf("\_") > 0) {Object value = map.get(key);map.remove(key);map.put(camel(key), value);}}}// 下划线转驼峰private String camel(String fieldName) {StringBuffer stringBuffer = new StringBuffer();boolean flag = false;for (int i = 0; i < fieldName.length(); i++) {if (fieldName.charAt(i) == '\_') {if (stringBuffer.length() > 0) {flag = true;}} else {if (flag) {stringBuffer.append(Character.toUpperCase(fieldName.charAt(i)));flag = false;} else {stringBuffer.append(Character.toLowerCase(fieldName.charAt(i)));}}}return stringBuffer.toString();}
}折叠
这个例子拦截的是ResultSetHandler的handleResultSets方法,这个方法是用来对结果集处理的,看intercept方法首先调用了目标对象的方法接着强转为List类型,这里为什么可以强转呢,因为我们可以看到handleResultSets方法定义 List handleResultSets(Statement stmt) throws SQLException;
返回的是List类型,然后遍历列表,若元素是map类型的再进行处理把key值转化为驼峰形式重新put到map中。
最后不要忘了把自定义的拦截器添加到配置中,这边是使用xml配置的,添加完后接着运行测试代码,可以看到列user_id已经转换成驼峰形式了。
<plugins><plugin interceptor="org.apache.ibatis.study.interceptor.MyInterceptor">plugin>
plugins>
#mapper接口
List selectAllUsers();#mapper.xml
"selectAllUsers" resultType="map">select user\_id, username, password, nicknamefrom user#java测试类
public class Test {public static void main(String[] args) throws IOException {try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {// 构建session工厂 DefaultSqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);System.out.println(userMapper.selectAllUsers());}}}