@Override
    protected BitList<Invoker<T>> doRoute(
            BitList<Invoker<T>> invokers,
            URL url,
            Invocation invocation,
            boolean needToPrintMessage,
            Holder<RouterSnapshotNode<T>> nodeHolder,
            Holder<String> messageHolder)
            throws RpcException {
        MeshRuleCache<T> ruleCache = this.meshRuleCache;
        if (!ruleCache.containsRule()) {
            if (needToPrintMessage) {
                messageHolder.set("MeshRuleCache has not been built. Skip route.");
            }
            return invokers;
        }

        BitList<Invoker<T>> result = new BitList<>(invokers.getOriginList(), true, invokers.getTailList());

        StringBuilder stringBuilder = needToPrintMessage ? new StringBuilder() : null;

        // loop each application
        for (String appName : ruleCache.getAppList()) {
            // find destination by invocation
            List<DubboRouteDestination> routeDestination =
                    getDubboRouteDestination(ruleCache.getVsDestinationGroup(appName), invocation);
            if (routeDestination != null) {
                // aggregate target invokers
                String subset = randomSelectDestination(ruleCache, appName, routeDestination, invokers);
                if (subset != null) {
                    BitList<Invoker<T>> destination = meshRuleCache.getSubsetInvokers(appName, subset);
                    result = result.or(destination);
                    if (stringBuilder != null) {
                        stringBuilder
                                .append("Match App: ")
                                .append(appName)
                                .append(" Subset: ")
                                .append(subset)
                                .append(' ');
                    }
                }
            }
        }

        // result = result.or(ruleCache.getUnmatchedInvokers());

        // empty protection
        if (result.isEmpty()) {
            if (needToPrintMessage) {
                messageHolder.set("Empty protection after routed.");
            }
            return invokers;
        }

        if (needToPrintMessage) {
            messageHolder.set(stringBuilder.toString());
        }
        return invokers.and(result);
    }
@Test
    void testRoute1() {
        StandardMeshRuleRouter<Object> meshRuleRouter = new StandardMeshRuleRouter<>(url);
        BitList<Invoker<Object>> invokers =
                new BitList<>(Arrays.asList(createInvoker(""), createInvoker("unknown"), createInvoker("app1")));
        assertEquals(invokers, meshRuleRouter.route(invokers.clone(), null, null, false, null));
        Holder<String> message = new Holder<>();
        meshRuleRouter.doRoute(invokers.clone(), null, null, true, null, message);
        assertEquals("MeshRuleCache has not been built. Skip route.", message.get());
    }