在PullConsumer中,有关消息的拉取RocketMQ提供了很多API,但总的来说分为两种,同步消息拉取和异步消息拉取

同步消息拉取
以同步方式拉取消息都是通过DefaultMQPullConsumerImpl的pullSyncImpl方法:

 private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, boolean block,
     long timeout)
     throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
     this.makeSureStateOK();

     if (null == mq) {
         throw new MQClientException("mq is null", null);
     }

     if (offset < 0) {
         throw new MQClientException("offset < 0", null);
     }

     if (maxNums <= 0) {
         throw new MQClientException("maxNums <= 0", null);
     }

     this.subscriptionAutomatically(mq.getTopic());

     int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);

     long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;

     boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
     PullResult pullResult = this.pullAPIWrapper.pullKernelImpl(
         mq,
         subscriptionData.getSubString(),
         subscriptionData.getExpressionType(),
         isTagType ? 0L : subscriptionData.getSubVersion(),
         offset,
         maxNums,
         sysFlag,
         0,
         this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(),
         timeoutMillis,
         CommunicationMode.SYNC,
         null
     );
     this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData);
     if (!this.consumeMessageHookList.isEmpty()) {
         ConsumeMessageContext consumeMessageContext = null;
         consumeMessageContext = new ConsumeMessageContext();
         consumeMessageContext.setConsumerGroup(this.groupName());
         consumeMessageContext.setMq(mq);
         consumeMessageContext.setMsgList(pullResult.getMsgFoundList());
         consumeMessageContext.setSuccess(false);
         this.executeHookBefore(consumeMessageContext);
         consumeMessageContext.setStatus(ConsumeConcurrentlyStatus.CONSUME_SUCCESS.toString());
         consumeMessageContext.setSuccess(true);
         this.executeHookAfter(consumeMessageContext);
     }
     return pullResult;
 }

首先通过subscriptionAutomatically方法检查Topic是否订阅

 public void subscriptionAutomatically(final String topic) {
     if (!this.rebalanceImpl.getSubscriptionInner().containsKey(topic)) {
         try {
             SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPullConsumer.getConsumerGroup(),
                 topic, SubscriptionData.SUB_ALL);
             this.rebalanceImpl.subscriptionInner.putIfAbsent(topic, subscriptionData);
         } catch (Exception ignore) {
         }
     }
 }

若是没有就新建一条订阅数据保存在rebalanceImpl的subscriptionInner中

之后调用pullKernelImpl方法:

 public PullResult pullKernelImpl(
     final MessageQueue mq,
     final String subExpression,
     final String expressionType,
     final long subVersion,
     final long offset,
     final int maxNums,
     final int sysFlag,
     final long commitOffset,
     final long brokerSuspendMaxTimeMillis,
     final long timeoutMillis,
     final CommunicationMode communicationMode,
     final PullCallback pullCallback
 ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
     FindBrokerResult findBrokerResult =
         this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
             this.recalculatePullFromWhichNode(mq), false);
     if (null == findBrokerResult) {
         this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
         findBrokerResult =
             this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                 this.recalculatePullFromWhichNode(mq), false);
     }

     if (findBrokerResult != null) {
         {
             // check version
             if (!ExpressionType.isTagType(expressionType)
                 && findBrokerResult.getBrokerVersion() < MQVersion.Version.V4_1_0_SNAPSHOT.ordinal()) {
                 throw new MQClientException("The broker[" + mq.getBrokerName() + ", "
                     + findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null);
             }
         }
         int sysFlagInner = sysFlag;

         if (findBrokerResult.isSlave()) {
             sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);
         }

         PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();
         requestHeader.setConsumerGroup(this.consumerGroup);
         requestHeader.setTopic(mq.getTopic());
         requestHeader.setQueueId(mq.getQueueId());
         requestHeader.setQueueOffset(offset);
         requestHeader.setMaxMsgNums(maxNums);
         requestHeader.setSysFlag(sysFlagInner);
         requestHeader.setCommitOffset(commitOffset);
         requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);
         requestHeader.setSubscription(subExpression);
         requestHeader.setSubVersion(subVersion);
         requestHeader.setExpressionType(expressionType);

         String brokerAddr = findBrokerResult.getBrokerAddr();
         if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
             brokerAddr = computPullFromWhichFilterServer(mq.getTopic(), brokerAddr);
         }

         PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
             brokerAddr,
             requestHeader,
             timeoutMillis,
             communicationMode,
             pullCallback);

         return pullResult;
     }

     throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
 }

首先通过findBrokerAddressInSubscribe方法查找关于消息队列的Broker信息

这里的recalculatePullFromWhichNode方法:

 public long recalculatePullFromWhichNode(final MessageQueue mq) {
     if (this.isConnectBrokerByUser()) {
         return this.defaultBrokerId;
     }

     AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);
     if (suggest != null) {
         return suggest.get();
     }

     return MixAll.MASTER_ID;
 }

根据消息队列,在pullFromWhichNodeTable查找其对应的Broker的ID
pullFromWhichNodeTable记录了消息对了和BrokerID的映射

 private ConcurrentMap<MessageQueue, AtomicLong/* brokerId */> pullFromWhichNodeTable =
         new ConcurrentHashMap<MessageQueue, AtomicLong>(32);

(master的BrokerID为0,slave的BrokerID大于0)

findBrokerAddressInSubscribe方法:

 public FindBrokerResult findBrokerAddressInSubscribe(
     final String brokerName,
     final long brokerId,
     final boolean onlyThisBroker
 ) {
     String brokerAddr = null;
     boolean slave = false;
     boolean found = false;

     HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
     if (map != null && !map.isEmpty()) {
         brokerAddr = map.get(brokerId);
         slave = brokerId != MixAll.MASTER_ID;
         found = brokerAddr != null;

         if (!found && !onlyThisBroker) {
             Entry<Long, String> entry = map.entrySet().iterator().next();
             brokerAddr = entry.getValue();
             slave = entry.getKey() != MixAll.MASTER_ID;
             found = true;
         }
     }

     if (found) {
         return new FindBrokerResult(brokerAddr, slave, findBrokerVersion(brokerName, brokerAddr));
     }

     return null;
 }

这里就根据brokerAddrTable表查找该BrokerID对应的Broker的地址信息,以及是否是slave
封装为FindBrokerResult返回

若是没有找到Broker的路由信息,则通过updateTopicRouteInfoFromNameServer方法向NameServer请求更新,更新完成后再调用findBrokerAddressInSubscribe方法查找

之后会根据相应的信息封装请求消息头PullMessageRequestHeader

然后调用pullMessage方法:

 public PullResult pullMessage(
     final String addr,
     final PullMessageRequestHeader requestHeader,
     final long timeoutMillis,
     final CommunicationMode communicationMode,
     final PullCallback pullCallback
 ) throws RemotingException, MQBrokerException, InterruptedException {
     RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);

     switch (communicationMode) {
         case ONEWAY:
             assert false;
             return null;
         case ASYNC:
             this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
             return null;
         case SYNC:
             return this.pullMessageSync(addr, request, timeoutMillis);
         default:
             assert false;
             break;
     }

     return null;
 }

这里就可以看出我前面说的两种类型,同步拉取和异步拉取

pullMessageSync方法:

 private PullResult pullMessageSync(
     final String addr,
     final RemotingCommand request,
     final long timeoutMillis
 ) throws RemotingException, InterruptedException, MQBrokerException {
     RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
     assert response != null;
     return this.processPullResponse(response);
 }

这里其实就是通过invokeSync方法,由Netty进行同步发送,将请求发送给Broker
关于消息的发送详见:

【RocketMQ中Producer消息的发送源码分析】

在收到响应后由processPullResponse方法处理
processPullResponse方法:

 private PullResult processPullResponse(
     final RemotingCommand response) throws MQBrokerException, RemotingCommandException {
     PullStatus pullStatus = PullStatus.NO_NEW_MSG;
     switch (response.getCode()) {
         case ResponseCode.SUCCESS:
             pullStatus = PullStatus.FOUND;
             break;
         case ResponseCode.PULL_NOT_FOUND:
             pullStatus = PullStatus.NO_NEW_MSG;
             break;
         case ResponseCode.PULL_RETRY_IMMEDIATELY:
             pullStatus = PullStatus.NO_MATCHED_MSG;
             break;
         case ResponseCode.PULL_OFFSET_MOVED:
             pullStatus = PullStatus.OFFSET_ILLEGAL;
             break;

         default:
             throw new MQBrokerException(response.getCode(), response.getRemark());
     }

     PullMessageResponseHeader responseHeader =
         (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class);

     return new PullResultExt(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(),
         responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody());
 }

根据响应的状态,设置PullStatus状态

然后通过decodeCommandCustomHeader方法,将响应中的信息解码
最后由PullResultExt封装消息信息

 public class PullResultExt extends PullResult {
     private final long suggestWhichBrokerId;
     private byte[] messageBinary;

     public PullResultExt(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset,
         List<MessageExt> msgFoundList, final long suggestWhichBrokerId, final byte[] messageBinary) {
         super(pullStatus, nextBeginOffset, minOffset, maxOffset, msgFoundList);
         this.suggestWhichBrokerId = suggestWhichBrokerId;
         this.messageBinary = messageBinary;
     }
     ......
 }

 public class PullResult {
     private final PullStatus pullStatus;
     private final long nextBeginOffset;
     private final long minOffset;
     private final long maxOffset;
     private List<MessageExt> msgFoundList;

     public PullResult(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset,
         List<MessageExt> msgFoundList) {
         super();
         this.pullStatus = pullStatus;
         this.nextBeginOffset = nextBeginOffset;
         this.minOffset = minOffset;
         this.maxOffset = maxOffset;
         this.msgFoundList = msgFoundList;
     }
     ......
 }

拉取到的消息可能是多条,具体内容在PullResult 中的msgFoundList保存,MessageExt是Message的超类

回到pullSyncImpl方法,在拉取到消息后,调用processPullResult方法:

 public PullResult processPullResult(final MessageQueue mq, final PullResult pullResult,
     final SubscriptionData subscriptionData) {
     PullResultExt pullResultExt = (PullResultExt) pullResult;

     this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId());
     if (PullStatus.FOUND == pullResult.getPullStatus()) {
         ByteBuffer byteBuffer = ByteBuffer.wrap(pullResultExt.getMessageBinary());
         List<MessageExt> msgList = MessageDecoder.decodes(byteBuffer);

         List<MessageExt> msgListFilterAgain = msgList;
         if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode()) {
             msgListFilterAgain = new ArrayList<MessageExt>(msgList.size());
             for (MessageExt msg : msgList) {
                 if (msg.getTags() != null) {
                     if (subscriptionData.getTagsSet().contains(msg.getTags())) {
                         msgListFilterAgain.add(msg);
                     }
                 }
             }
         }

         if (this.hasHook()) {
             FilterMessageContext filterMessageContext = new FilterMessageContext();
             filterMessageContext.setUnitMode(unitMode);
             filterMessageContext.setMsgList(msgListFilterAgain);
             this.executeHook(filterMessageContext);
         }

         for (MessageExt msg : msgListFilterAgain) {
             String traFlag = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
             if (traFlag != null && Boolean.parseBoolean(traFlag)) {
                 msg.setTransactionId(msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
             }
             MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MIN_OFFSET,
                 Long.toString(pullResult.getMinOffset()));
             MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MAX_OFFSET,
                 Long.toString(pullResult.getMaxOffset()));
         }

         pullResultExt.setMsgFoundList(msgListFilterAgain);
     }

     pullResultExt.setMessageBinary(null);

     return pullResult;
 }

首先调用updatePullFromWhichNode方法:

 public void updatePullFromWhichNode(final MessageQueue mq, final long brokerId) {
    AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);
     if (null == suggest) {
         this.pullFromWhichNodeTable.put(mq, new AtomicLong(brokerId));
     } else {
         suggest.set(brokerId);
     }
 }

这里就会将pullFromWhichNodeTable中记录的消息队列和BrokerID的映射,更新为Broker发送过来的建议ID
结合上一篇博客来看,若是采用集群模式,就完成了消费者端的负载均衡

在PullStatus.FOUND情况下,会调用MessageDecoder的decodes方法,将CommitLog格式的消息数据进行解码,转化为真正可读的消息

之后会对Tag进行判断,设置了Tag,添加Tag消息记录

之后,在设置了FilterMessageHook钩子情况下,通过executeHook方法执行FilterMessageHook钩子的filterMessage方法:

 public void executeHook(final FilterMessageContext context) {
     if (!this.filterMessageHookList.isEmpty()) {
         for (FilterMessageHook hook : this.filterMessageHookList) {
             try {
                 hook.filterMessage(context);
             } catch (Throwable e) {
                 log.error("execute hook error. hookName={}", hook.hookName());
             }
         }
     }
 }

然后对消息进行属性设置

processPullResult完成后,若是设置了ConsumeMessageHook钩子,调用executeHookBefore和executeHookAfter方法,分别执行钩子中的consumeMessageBefore和consumeMessageAfter方法:

 public void executeHookBefore(final ConsumeMessageContext context) {
     if (!this.consumeMessageHookList.isEmpty()) {
         for (ConsumeMessageHook hook : this.consumeMessageHookList) {
             try {
                 hook.consumeMessageBefore(context);
             } catch (Throwable ignored) {
             }
         }
     }
 }

 public void executeHookAfter(final ConsumeMessageContext context) {
     if (!this.consumeMessageHookList.isEmpty()) {
         for (ConsumeMessageHook hook : this.consumeMessageHookList) {
             try {
                 hook.consumeMessageAfter(context);
             } catch (Throwable ignored) {
             }
         }
     }
 }

PullConsumer消息的同步拉取到此结束

异步消息拉取

异步拉取的API都通过pullAsyncImpl方法实现:

 private void pullAsyncImpl(
     final MessageQueue mq,
     final SubscriptionData subscriptionData,
     final long offset,
     final int maxNums,
     final PullCallback pullCallback,
     final boolean block,
     final long timeout) throws MQClientException, RemotingException, InterruptedException {
     this.makeSureStateOK();

     if (null == mq) {
         throw new MQClientException("mq is null", null);
     }

     if (offset < 0) {
         throw new MQClientException("offset < 0", null);
     }

     if (maxNums <= 0) {
         throw new MQClientException("maxNums <= 0", null);
     }

     if (null == pullCallback) {
         throw new MQClientException("pullCallback is null", null);
     }

     this.subscriptionAutomatically(mq.getTopic());

     try {
         int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);

         long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;

         boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
         this.pullAPIWrapper.pullKernelImpl(
             mq,
             subscriptionData.getSubString(),
             subscriptionData.getExpressionType(),
             isTagType ? 0L : subscriptionData.getSubVersion(),
             offset,
             maxNums,
             sysFlag,
             0,
             this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(),
             timeoutMillis,
             CommunicationMode.ASYNC,
             new PullCallback() {

                 @Override
                 public void onSuccess(PullResult pullResult) {
                     pullCallback
                         .onSuccess(DefaultMQPullConsumerImpl.this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData));
                 }

                 @Override
                 public void onException(Throwable e) {
                     pullCallback.onException(e);
                 }
             });
     } catch (MQBrokerException e) {
         throw new MQClientException("pullAsync unknow exception", e);
     }
 }

相比同步,参数多了个PullCallback,用于处理异步拉取后的回调

过程基本上个同步拉取类似,只不过在调用pullKernelImpl方法时,会创建一个PullCallback
在onSuccess和onException中,实际上调用了pullCallback的相应方法,这样就完成了异步的回调

在onSuccess回调的参数中,同同步方式类似,会通过processPullResult方法,对结果进一步加工

之后的pullKernelImpl方法和同步一样

只不过最后调用了pullMessageAsync方法:

 private void pullMessageAsync(
     final String addr,
     final RemotingCommand request,
     final long timeoutMillis,
     final PullCallback pullCallback
 ) throws RemotingException, InterruptedException {
     this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
         @Override
         public void operationComplete(ResponseFuture responseFuture) {
             RemotingCommand response = responseFuture.getResponseCommand();
             if (response != null) {
                 try {
                     PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response);
                     assert pullResult != null;
                     pullCallback.onSuccess(pullResult);
                 } catch (Exception e) {
                     pullCallback.onException(e);
                 }
             } else {
                 if (!responseFuture.isSendRequestOK()) {
                     pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
                 } else if (responseFuture.isTimeout()) {
                     pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
                         responseFuture.getCause()));
                 } else {
                     pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
                 }
             }
         }
     });
 }

这里实际上也是通过Netty完成异步发送
详见:

【RocketMQ中Producer消息的发送源码分析】

由于是异步发送,这里又设置了一个回调InvokeCallback
当请求发送完成,收到响应后,就会执行InvokeCallback的operationComplete方法,

在operationComplete方法中,和同步一样,执行processPullResponse方法,处理响应
之后调用pullCallback的onSuccess方法,也就是刚才创建的回调接口,进而执行用户传入的回调接口的方法

消息异步拉取也就到此结束

RocketMQ中PullConsumer的消息拉取源码分析的更多相关文章

  1. java中的==、equals()、hashCode()源码分析(转载)

    在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. ==  java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...

  2. 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...

  3. 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  4. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...

  5. Java并发包中Semaphore的工作原理、源码分析及使用示例

    1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...

  6. Android中Handler的消息处理机制以及源码分析

    在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui.我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui.而一些耗时操作,比如数据库,网络请 ...

  7. java中的==、equals()、hashCode()源码分析

    转载自:http://www.cnblogs.com/xudong-bupt/p/3960177.html 在Java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际 ...

  8. HashMap中的TreeNode,红黑树源码分析

    在看HashMap的源码时候看到了TreeNode.因此需要对其进行一个了解.是一个红黑树.可以百度一下红黑树的数据结构.分析了下源码,还是比较枯燥的 红黑树的性质:本身是一个二叉查找树(所有左节点的 ...

  9. Django中CBV(Class Base Views)模型源码分析

    在view文件中编写一个类,并配置好路由 class Test(View): def get(self, request, *args, **kwargs): return HttpResponse( ...

  10. [编织消息框架][netty源码分析]6 ChannelPipeline 实现类DefaultChannelPipeline职责与实现

    ChannelPipeline 负责channel数据进出处理,如数据编解码等.采用拦截思想设计,经过A handler处理后接着交给next handler ChannelPipeline 并不是直 ...

随机推荐

  1. 解读ASP.NET 5 &amp; MVC6系列(6):Middleware详解

    在第1章项目结构分析中,我们提到Startup.cs作为整个程序的入口点,等同于传统的Global.asax文件,即:用于初始化系统级的信息(例如,MVC中的路由配置).本章我们就来一一分析,在这里如 ...

  2. Apache配置手札

    一.绑定域名到子目录 在httpd.conf文件末尾添加 #不同的域名对应到的目录 <VirtualHost *:80> DocumentRoot "D:\wamp\www\ba ...

  3. EF INNER JOIN,LEFT JOIN,GROUP JOIN

    IQueryable<TOuter>的扩展方法中提供了 INNER JOIN,GROUP JOIN但是没有提供LEFT JOIN GROUP JOIN适用于一对多的场景,如果关联的GROU ...

  4. Eclipse启动tomcat时出现报错-拒绝访问

    今天新建项目,当选择项目的发布路径为tomcat的路径时 启动tomcat出现如下错误: Publishing the configuration... Error copying file to D ...

  5. NGUI无法按住鼠标按住时无法监听OnHover事件

    UICamera.cs 修改前: if ((!isPressed) && highlightChanged) { currentScheme = ControlScheme.Mouse ...

  6. python(4) - 装饰器2

    接下来修改一下上一篇的login,将用户名传递给验证函数. def login(func): #接收一个函数作为参数 def inner(name): print("用户验证通过....&q ...

  7. C#使用多态求方形面积周长和圆的面积周长

    class class1 { public static void Main(string[] args) { //使用多态求矩形面积与周长和圆的面积与周长 Shape cl = ); double ...

  8. SOHO路由器的静态路由的不同

    网络拓扑如下,其中RA与RB皆为TP-LINK家用路由器 最终在TP-LINK官网的官网上找到这么一段话 静态路由是在路由器中手工设置的固定的路由条目.我司路由器静态路由是基于ICMP重定向原理,与其 ...

  9. PAT 团体程序设计天梯赛-练习集L1-011. A-B

    本题要求你计算A-B.不过麻烦的是,A和B都是字符串 —— 即从字符串A中把字符串B所包含的字符全删掉,剩下的字符组成的就是字符串A-B. 输入格式: 输入在2行中先后给出字符串A和B.两字符串的长度 ...

  10. 【bug】java.lang.NoSuchMethodError: android.widget.TextView.setBackground

    安卓的背景色设置需要根据SDK的版本来分情况考虑: if (Build.VERSION.SDK_INT >= 16) { textView.setBackground(null); } else ...