关于Handler的拦截

为什么要Hook Handler?Android系统中存在大量Handler,我们要改变系统的某些行为就需要Hook掉一些关键节点的Handler,为此,我们要清楚Handler的工作原理。

先做一个实验来看看Hook Handler的效果,如下:

public class MainActivity extends Activity {

    private Button mBtnShow;
    private Button mBtnHook;

    private final Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            Toast.makeText(MainActivity.this, (CharSequence) msg.obj,
                    Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBtnShow = (Button) findViewById(R.id.btn);
        mBtnHook = (Button) findViewById(R.id.hook);

        mBtnShow.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                mHandler.obtainMessage(0, "hello world").sendToTarget();
            }
        });

        mBtnHook.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                try {
                    hookHandler();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
    }

    private void hookHandler() throws IllegalAccessException,
            IllegalArgumentException {
        Field field = FieldUtils.getDeclaredField(Handler.class, "mCallback",
                true);

        field.set(mHandler, new Handler.Callback() {

            @Override
            public boolean handleMessage(Message msg) {
                // TODO Auto-generated method stub
                msg.obj = "hello china";
                return false;
            }
        });
    }
}

放两个按钮,一个按钮是发送消息来显示Toast,另一个按钮是Hook Handler,改变发送的消息内容。

这里Hook所做的事情很简单,其实就是通过反射改变Hander的mCallback,值得注意的是这个Callback的handleMessage中改变了Message后返回了false。这样做是为了继续调用Handler的handleMessage,只是Handler不知道发给他的Message已经被暗中动了手脚。

我们看看Handler的内在实现,Handler其实只是一个工具,用于封装消息再丢给MessageQueue而已,真正的核心在Looper和MessageQueue。我们重点看一下Looper,里面有一个loop函数,就是循环处理消息的,如下:

public static void loop() {
    final Looper me = myLooper();

    final MessageQueue queue = me.mQueue;

    for (;;) {
        Message msg = queue.next();

        msg.target.dispatchMessage(msg);

        msg.recycle();
    }
}

逻辑很简单,通过queue.next()依次从MessageQueue中取出Message,然后调用msg.target的dispatchMessage,这个target其实就是Handler,我们来看看Handler是怎么dispatchMessage的:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

首先优先调用Message中的Callback,如果没有再来调用Handler的全局Callback,如果没有mCallback则调用Handler的handleMessage,如果有mCallback但是mCallback.handleMessage返回false,同样会调用Handler的handleMessage,如果返回true则直接返回了。我们Hook的核心就在这里了。

好了,顺便再看看是怎么从MessageQueue中取出消息的,如下:

Message next() {
    for (;;) {
        ..........
        nativePollOnce(mPtr, nextPollTimeoutMillis);
        ..........
        if (mQuitting) {
            return null;
        }
    }
}

这是个无限循环的过程,除非退出标志被置位。而从消息队列中取出消息可能被阻塞,原因是队列为空了。这里nativePollOnce就是等待队列中有消息,当有消息时立即返回。我们看看里面的实现,这个是native的函数:

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
        jint ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, timeoutMillis);
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    ..........
    pollInner(timeoutMillis);
}

int Looper::pollInner(int timeoutMillis) {
    ..........

    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    for (int i = 0; i < eventCount; i++) {
        ..........
    }
}

真相大白,原来是用epoll_wait来监测fd是否可读写,这个fd通常是管道,那么是否意味着当往队列里添加消息时,需要向这个fd里写一些数据呢,这样epoll_wait就能被唤醒。我们来看看消息是如何添加到队列里的:

boolean enqueueMessage(Message msg, long when) {
    ..........
    if (needWake) {
        nativeWake(mPtr);
    }
    return true;
}

这里给消息添加到队列后,调用了nativeWake来唤醒队列:

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jint ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    return nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

void Looper::wake() {
    ssize_t nWrite;
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);

    if (nWrite != 1) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

果然如我们所料,这里唤醒就是往fd中写入了一个W字符而已。

<think>嗯,用户之前问了关于在MyBatis中如何排除特定列进行查询的问题,我给出了五种方法,包括自定义TypeHandler。现在他们想知道在使用自定义TypeHandler拦截方法时,如果数据库返回的是Map类型该怎么处理。 首先,我需要回忆一下之前提到的TypeHandler方法。原来的示例是针对User对象,通过过滤特定字段。但用户现在返回的是Map,而不是具体的Java对象。这种情况下,原来的TypeHandler可能不适用,因为TypeHandler通常是针对特定Java类型的,比如User.class。而Map的话,MyBatis默认会使用MapTypeHandler来处理,这时候需要调整策略。 用户可能的需求是,当查询结果返回Map类型时,如何排除某些列。比如,执行SELECT * FROM table,返回的Map中包含所有列,但希望过滤掉如password之类的敏感字段。这时候,直接修改TypeHandler可能不是一个好的方式,因为Map的处理方式与具体对象不同。 接下来,我需要思考如何实现这个需求。可能的方案有几种: 1. 自定义ResultHandler:MyBatis的ResultHandler接口允许在结果处理时进行拦截,可以在这里对Map进行字段的移除。这种方法比较灵活,可以在结果集处理完毕后修改Map的内容。 2. 使用拦截器(Interceptor):MyBatis的插件机制可以拦截Executor的query方法,在结果返回前修改Map中的键值对,移除不需要的字段。这种方法需要熟悉MyBatis的插件开发,可能会影响全局,需要谨慎处理。 3. 在SQL层面处理:如果可能的话,最好在SQL查询中不选择需要排除的列,而不是查询所有列后再过滤。但用户可能的情况是无法修改SQL,比如使用动态SQL或通用查询。 4. 使用自动映射时的自定义设置:MyBatis在自动映射结果到Map时,是否有配置可以忽略某些列?默认情况下,MyBatis会映射所有列,所以可能需要通过其他方式处理。 回到用户的问题,他们提到的是自定义TypeHandler拦截方法中返回Map类型的情况。这时候可能需要检查TypeHandler是否能处理Map类型。但通常,Map是由内置的MapTypeHandler处理的,用户自定义的TypeHandler可能无法直接应用,除非明确指定。 可能的解决方案是,如果用户希望当结果类型是Map时,过滤某些字段,可以通过自定义ResultHandler来实现。例如,在查询时注册一个ResultHandler,遍历结果Map,移除指定的键。 另外,使用拦截器也是一种方法,可以拦截查询结果,并修改结果中的Map。但需要注意拦截器的编写,避免影响其他查询。 现在需要验证这些思路是否正确。比如,当结果类型是Map时,MyBatis会使用默认的映射方式,将列名作为键,值作为Map的值。如果使用ResultHandler,可以在处理每个结果时修改Map。 例如,用户可能在Mapper中这样写: ```xml <select id="selectUsers" resultType="map"> SELECT * FROM user </select> ``` 返回的结果是List<Map<String, Object>>,每个Map代表一行记录,包含所有列。此时,自定义一个ResultHandler,在handleResult方法中遍历每个Map,移除指定的key,比如password。 具体步骤可能是: 1. 实现MyBatis的ResultHandler接口。 2. 在handleResult方法中,检查结果对象是否为Map,如果是,则移除指定的键。 3. 在查询时使用sqlSession.select方法,并传入该ResultHandler。 这种方法的优点是无需修改原有SQL语句,适用于无法动态生成排除字段的情况。但缺点是需要手动处理结果集,可能影响性能,尤其是大数据量时。 另一种方法是通过拦截器,在Executor的query方法执行后,对结果进行修改。例如,拦截所有返回类型为Map的查询,自动移除指定字段。这需要编写一个Interceptor,并覆盖query方法,处理返回的List中的每个Map元素。 此外,如果用户使用的是MyBatis 3.4.5及以上版本,还可以使用@Interceptor注解简化拦截器的配置。需要注意拦截点的选择,避免影响其他查询。 总结,用户可能需要的是在返回Map类型时动态排除某些字段,而原来的TypeHandler方法可能不适用,因为TypeHandler通常针对特定Java类型。正确的做法可能是使用ResultHandler拦截器来处理这种情况。 在回答时,需要详细说明这两种方法的实现步骤,并提供示例代码。同时,比较它们的优缺点,帮助用户选择最适合的方案。例如,ResultHandler适用于特定查询的场景,而拦截器更适合全局处理,但可能带来性能开销和复杂性。 还需要提醒用户,最佳实践仍然是在SQL查询中明确指定需要的列,而不是使用SELECT *,以避免不必要的数据传输和潜在的安全风险。如果无法避免使用SELECT *,再考虑使用上述的后处理方法来过滤字段。 最后,检查是否有其他可能性,比如结合自动映射时的配置,或者使用MyBatis的结果映射(ResultMap)来处理Map类型,但通常ResultMap用于指定列到Java对象的映射,而不是过滤字段。不过,可以尝试创建一个ResultMap,排除某些列,但不确定是否适用于Map类型。需要进一步验证。 经过验证,ResultMap主要用于将数据库列映射到Java对象的属性,而Map类型的处理是自动的,每个列都会成为Map的一个entry。因此,无法通过ResultMap直接排除某些列,除非在SQL中显式选择需要的列。 因此,最终的结论是,处理返回Map类型并排除特定列的有效方法是使用ResultHandler拦截器,在结果返回前修改Map内容。在回答中需要详细说明这两种方法的实现,并提供代码示例,同时比较它们的适用场景和注意事项。</think>在 MyBatis 中处理 **返回 Map 类型时动态排除字段**,可通过以下两种核心方案实现: --- ### **方案 1:自定义 ResultHandler(推荐)** **实现原理**:通过实现 MyBatis 的 `ResultHandler` 接口,在结果集处理阶段直接修改 Map 数据。 **适用场景**:需要针对特定查询进行字段过滤。 #### **实现步骤**: 1. **创建自定义结果处理器**: ```java public class MapFieldFilterResultHandler implements ResultHandler<Map<String, Object>> { private final List<Map<String, Object>> results = new ArrayList<>(); private final Set<String> excludeFields; public MapFieldFilterResultHandler(Set<String> excludeFields) { this.excludeFields = excludeFields; } @Override public void handleResult(ResultContext<? extends Map<String, Object>> resultContext) { Map<String, Object> originalMap = resultContext.getResultObject(); // 创建新 Map 并过滤字段 Map<String, Object> filteredMap = new LinkedHashMap<>(); originalMap.forEach((key, value) -> { if (!excludeFields.contains(key.toLowerCase())) { // 忽略大小写 filteredMap.put(key, value); } }); results.add(filteredMap); } public List<Map<String, Object>> getFilteredResults() { return results; } } ``` 2. **在 Mapper 接口中使用处理器**: ```java public List<Map<String, Object>> selectUsersWithoutSensitiveFields() { MapFieldFilterResultHandler resultHandler = new MapFieldFilterResultHandler(Set.of("password", "salt")); // 指定要排除的字段 sqlSession.select("selectUsers", resultHandler); // "selectUsers" 是对应的 SQL ID return resultHandler.getFilteredResults(); } ``` 3. **SQL 映射文件保持简单查询**: ```xml <select id="selectUsers" resultType="map"> SELECT * FROM user <!-- 直接查询所有字段 --> </select> ``` **优势**: - 代码侵入性低,可精准控制特定查询 - 支持动态过滤规则 - 兼容所有 MyBatis 版本 --- ### **方案 2:Interceptor 拦截器(全局处理)** **实现原理**:通过 MyBatis 插件机制拦截查询结果,全局过滤 Map 中的字段。 **适用场景**:需要统一处理所有返回 Map 类型的查询。 #### **实现步骤**: 1. **编写拦截器**: ```java @Intercepts({ @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) }) public class MapFieldFilterInterceptor implements Interceptor { private static final Set<String> GLOBAL_EXCLUDE_FIELDS = Set.of("password", "salt"); @Override public Object intercept(Invocation invocation) throws Throwable { Object result = invocation.proceed(); // 执行原始查询 // 仅处理返回类型为 List<Map> 的结果 if (result instanceof List) { List<?> list = (List<?>) result; if (!list.isEmpty() && list.get(0) instanceof Map) { return list.stream() .map(item -> filterMap((Map<String, Object>) item)) .collect(Collectors.toList()); } } return result; } private Map<String, Object> filterMap(Map<String, Object> originalMap) { return originalMap.entrySet().stream() .filter(entry -> !GLOBAL_EXCLUDE_FIELDS.contains(entry.getKey().toLowerCase())) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, LinkedHashMap::new )); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) {} } ``` 2. **注册拦截器到 MyBatis 配置**: ```xml <!-- mybatis-config.xml --> <configuration> <plugins> <plugin interceptor="com.example.MapFieldFilterInterceptor"/> </plugins> </configuration> ``` **优势**: - 全局自动生效,无需修改现有 SQL 和 Java 代码 - 统一维护过滤规则 **风险提示**: - 会拦截所有返回 Map 的查询,需谨慎评估影响范围 - 性能损耗:对大数据量结果集进行流式处理时需注意效率 --- ### **两种方案对比** | 特性 | ResultHandler 方案 | Interceptor 方案 | |--------------------|----------------------------------|----------------------------------| | **作用范围** | 单个查询 | 全局所有返回 Map 的查询 | | **性能影响** | 仅影响使用该处理器的查询 | 影响所有匹配的查询 | | **代码侵入性** | 需显式调用处理器 | 完全无侵入 | | **规则灵活性** | 可动态传递不同排除字段集合 | 需硬编码或通过配置扩展规则 | | **维护成本** | 低(按需使用) | 高(需确保不影响其他逻辑) | --- ### **补充技巧:SQL 层动态过滤** 若允许修改 SQL,更推荐在 **数据库查询阶段直接排除字段**,这是最高效的方式: ```xml <!-- 使用动态 SQL 拼接字段列表 --> <select id="selectWithExclusion" resultType="map"> SELECT <foreach item="col" collection="allColumns" separator=","> ${col} </foreach> FROM user WHERE <!-- 其他条件 --> </select> ``` **Java 层动态获取字段列表**: 可通过数据库元数据查询获取表字段,再排除敏感字段后传入 SQL: ```java List<String> columns = getTableColumns("user", Set.of("password")); params.put("allColumns", columns); ``` --- ### **最佳实践建议** 1. **优先 SQL 层过滤**:尽可能在 SQL 中明确指定所需字段,避免 `SELECT *` 2. **小范围动态过滤**:使用 `ResultHandler` 方案,通过参数传递排除字段列表 3. **生产环境慎用全局拦截器**:除非确定需要统一处理所有 Map 结果 4. **性能关键场景**:将字段排除逻辑前移至数据库查询阶段(如动态列选择) **完整示例代码**(动态 ResultHandler): ```java // 在 Service 层动态控制过滤字段 public List<Map<String, Object>> getUsers(Set<String> excludeFields) { MapFieldFilterResultHandler handler = new MapFieldFilterResultHandler(excludeFields); sqlSession.select("selectUsers", handler); return handler.getFilteredResults(); } // 调用示例:排除 password 和 email List<Map<String, Object>> users = userService.getUsers(Set.of("password", "email")); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值