消息监听器容器是一个用于查看JMS目标等待消息到达的特殊bean,一旦消息到达它就可以获取到消息,并通过调用onMessage()方法将消息传递给一个MessageListener实现。Spring中消息监听器容器的类型如下。

SimpleMessageListenerContainer:最简单的消息监听器容器,只能处理固定数量的JMS会话,且不支持事务。

DefaultMessageListenerContainer:这个消息监听器容器建立在SimpleMessageListenerContainer容器之上,添加了对事务的支持。

serversession.ServerSessionMessage.ListenerContainer:这是功能最强大的消息监听器,与DefaultMessageListenerContainer相同,它支持事务,但是它还允许动态地管理JMS会话。

下面以DefaultMessageListenerContainer为例进行分析,看看消息监听器容器的实现。在使用消息监听器容器时一定要将自定义的消息监听器置入到容器中,这样才可以在收到信息时,容器把消息转向监听器处理。查看DefaultMessageListenerContainer层次结构图,我们看到此类实现了InitializingBean接口,按照以往的风格我们还是首先查看接口方法afterPropertiesSet()中的逻辑,其方法实现在其父类AbstractJmsListeningContainer中。

  @Override
public void afterPropertiesSet() {
//验证connectionFactory
super.afterPropertiesSet();
//验证配置文件
validateConfiguration();
//初始化
initialize();
}

监听器容器的初始化只包含了三句代码,其中前两句只用于属性的验证,比如connectionFacory或者destination等属性是否为空等,而真正用于初始化的操作委托在initialize()中执行。

  public void initialize() throws JmsException {
try {
//lifecycleMonitor用于控制生命周期的同步处理
synchronized (this.lifecycleMonitor) {
this.active = true;
this.lifecycleMonitor.notifyAll();
}
doInitialize();
}
catch (JMSException ex) {
synchronized (this.sharedConnectionMonitor) {
ConnectionFactoryUtils.releaseConnection(this.sharedConnection, getConnectionFactory(), this.autoStartup);
this.sharedConnection = null;
}
throw convertJmsAccessException(ex);
}
}
@Override
protected void doInitialize() throws JMSException {
synchronized (this.lifecycleMonitor) {
for (int i = 0; i < this.concurrentConsumers; i++) {
scheduleNewInvoker();
}
}
}

这里用到了concurrentConsumers属性,消息监听器允许创建多个Session和MessageConsumer来接收消息。具体的个数由concurrentConsumers属性指定。需要注意的是,应该只是在Destination为Queue的时候才使用多个MessageConsumer(Queue中的一个消息只能被一个Consumer接收),虽然使用多个MessageConsumer会提高消息处理的性能,但是消息处理的顺序却得不到保证。消息被接收的顺序仍然是消息发送时的顺序,但是由于消息可能会被并发处理,因此消息处理的顺序可能和消息发送的顺序不同。此外,不应该在Destination为Topic的时候使用多个MessageConsumer,因为多个MessageConsumer会接收到同样的消息。

对于具体的实现逻辑我们只能继续查看源码:

  private void scheduleNewInvoker() {
AsyncMessageListenerInvoker invoker = new AsyncMessageListenerInvoker();
if (rescheduleTaskIfNecessary(invoker)) {
// This should always be true, since we're only calling this when active.
this.scheduledInvokers.add(invoker);
}
}
protected final boolean rescheduleTaskIfNecessary(Object task) {
if (this.running) {
try {
doRescheduleTask(task);
}
catch (RuntimeException ex) {
logRejectedTask(task, ex);
this.pausedTasks.add(task);
}
return true;
}
else if (this.active) {
this.pausedTasks.add(task);
return true;
}
else {
return false;
}
}
@Override
protected void doRescheduleTask(Object task) {
this.taskExecutor.execute((Runnable) task);
}
 

分析源码得知,根据concurrentConsumers数量建立了对应数量的线程,即使读者不了解线程池的使用,至少根据以上代码可以推断出doRescheduleTask函数其实是在开启一个线程执行Runnable。我们反追踪这个传入的参数,可以看到这个参数其实是AsyncMessageListenerInvoker类型实例。因此我们可以推断,Spring根据concurrentConsumers数量建立了对应数量的线程,而每个线程都作为一个独立的接收者在循环接收消息。于是我们把所有的焦点转向AsyncMessageListenerInvoker这个类的实现,由于它是作为一个Runnable角色去执行,所以对以这个类的分析从run方法开始。

@Override
public void run() {
//并发控制
synchronized (lifecycleMonitor) {
activeInvokerCount++;
lifecycleMonitor.notifyAll();
}
boolean messageReceived = false;
try {
//根据每个任务设置的最大处理消息数量而作不同处理
//小于0默认为无限制,一直接收消息
if (maxMessagesPerTask < 0) {
messageReceived = executeOngoingLoop();
}
else {
int messageCount = 0;
//消息数量控制,一旦超出数量则停止循环
while (isRunning() && messageCount < maxMessagesPerTask) {
messageReceived = (invokeListener() || messageReceived);
messageCount++;
}
}
}
catch (Throwable ex) {
//清理操作,包括关闭session等
clearResources();
if (!this.lastMessageSucceeded) {
// We failed more than once in a row or on startup - sleep before
// first recovery attempt.
sleepBeforeRecoveryAttempt();
}
this.lastMessageSucceeded = false;
boolean alreadyRecovered = false;
synchronized (recoveryMonitor) {
if (this.lastRecoveryMarker == currentRecoveryMarker) {
handleListenerSetupFailure(ex, false);
recoverAfterListenerSetupFailure();
currentRecoveryMarker = new Object();
}
else {
alreadyRecovered = true;
}
}
if (alreadyRecovered) {
handleListenerSetupFailure(ex, true);
}
}
finally {
synchronized (lifecycleMonitor) {
decreaseActiveInvokerCount();
lifecycleMonitor.notifyAll();
}
if (!messageReceived) {
this.idleTaskExecutionCount++;
}
else {
this.idleTaskExecutionCount = 0;
}
synchronized (lifecycleMonitor) {
if (!shouldRescheduleInvoker(this.idleTaskExecutionCount) || !rescheduleTaskIfNecessary(this)) {
// We're shutting down completely.
scheduledInvokers.remove(this);
if (logger.isDebugEnabled()) {
logger.debug("Lowered scheduled invoker count: " + scheduledInvokers.size());
}
lifecycleMonitor.notifyAll();
clearResources();
}
else if (isRunning()) {
int nonPausedConsumers = getScheduledConsumerCount() - getPausedTaskCount();
if (nonPausedConsumers < 1) {
logger.error("All scheduled consumers have been paused, probably due to tasks having been rejected. " +
"Check your thread pool configuration! Manual recovery necessary through a start() call.");
}
else if (nonPausedConsumers < getConcurrentConsumers()) {
logger.warn("Number of scheduled consumers has dropped below concurrentConsumers limit, probably " +
"due to tasks having been rejected. Check your thread pool configuration! Automatic recovery " +
"to be triggered by remaining consumers.");
}
}
}
}
}

以上函数中主要根据变量maxMessagesPerTask的值来分为不同的情况处理,当然,函数中还使用了大量的代码处理异常机制的数据维护,我们更加关注程序的正常流程是如何处理的。

其实核心的处理就是调用invokeListener来接收消息并激活消息监听器,但是之所以两种情况分开处理,正是考虑到在无限制循环接收消息的情况下,用户可以通过设置标志位running来控制消息接收的暂停与恢复,并维护当前消息监听器的数量。

        private boolean executeOngoingLoop() throws JMSException {
boolean messageReceived = false;
boolean active = true;
while (active) {
synchronized (lifecycleMonitor) {
boolean interrupted = false;
boolean wasWaiting = false;
//如果当前任务已经处于激活状态但是却给了暂时终止的命令
while ((active = isActive()) && !isRunning()) {
if (interrupted) {
throw new IllegalStateException("Thread was interrupted while waiting for " +
"a restart of the listener container, but container is still stopped");
}
//如果并非处于等待状态则说明是第一次执行,需要将激活任务数量减少
if (!wasWaiting) {
decreaseActiveInvokerCount();
}
wasWaiting = true;
//开始进入等待状态,等待任务的恢复命令
try {
//通过wait等待,也就是等待notify或者notifyAll
lifecycleMonitor.wait();
}
catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other threads to react.
Thread.currentThread().interrupt();
interrupted = true;
}
}
if (wasWaiting) {
activeInvokerCount++;
}
if (scheduledInvokers.size() > maxConcurrentConsumers) {
active = false;
}
}
//正常处理流程
if (active) {
messageReceived = (invokeListener() || messageReceived);
}
}
return messageReceived;
}

如果按照正常的流程其实是不会进入while循环中的,而是直接进入函数invokeListener()来接收消息并激活监听器,但是,我们不可能让循环一直持续下去,我们要考虑到暂停线程或者恢复线程的情况,这时,isRunning()函数就派上用场了。

isRunning()用来检测标志位this.running状态进而判断是否需要进入while循环。由于要维护当前线程激活数量,所以引入了wasWaiting变量,用来判断线程是否处于等待状态。如果线程首次进入等待状态,则需要减少线程激活数量计数器。

当然,还有一个地方需要提一下,就是线程等待不是一味地采用while循环来控制,因为如果单纯地采用while循环会浪费CPU的始终周期,给资源造成巨大的浪费。这里,Spring采用的是使用全局控制变量lifecycleMonitor的wait()方法来暂停线程,所以,如果终止线程需要再次恢复的话,除了更改this.running标志位外,还需要调用lifecycleMonitor.notify或者lifecycleMonitor.notifyAll来使线程恢复。

接下来就是消息接收的处理了invokeListener。

    private boolean invokeListener() throws JMSException {
//初始化资源包括首次创建的时候创建session与consumer
initResourcesIfNecessary();
boolean messageReceived = receiveAndExecute(this, this.session, this.consumer);
//改变标志位,信息成功处理
this.lastMessageSucceeded = true;
return messageReceived;
}
private void initResourcesIfNecessary() throws JMSException {
if (getCacheLevel() <= CACHE_CONNECTION) {
updateRecoveryMarker();
}
else {
if (this.session == null && getCacheLevel() >= CACHE_SESSION) {
updateRecoveryMarker();
this.session = createSession(getSharedConnection());
}
if (this.consumer == null && getCacheLevel() >= CACHE_CONSUMER) {
this.consumer = createListenerConsumer(this.session);
synchronized (lifecycleMonitor) {
registeredWithDestination++;
}
}
}
}
protected boolean receiveAndExecute(Object invoker, Session session, MessageConsumer consumer)
throws JMSException {
  </span><span style="color: #0000ff;">if</span> (<span style="color: #0000ff;">this</span>.transactionManager != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
</span><span style="color: #008000;">//</span><span style="color: #008000;"> Execute receive within transaction.</span>
TransactionStatus status = <span style="color: #0000ff;">this</span>.transactionManager.getTransaction(<span style="color: #0000ff;">this</span><span style="color: #000000;">.transactionDefinition);
</span><span style="color: #0000ff;">boolean</span><span style="color: #000000;"> messageReceived;
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
messageReceived </span>=<span style="color: #000000;"> doReceiveAndExecute(invoker, session, consumer, status);
}
</span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (JMSException ex) {
rollbackOnException(status, ex);
</span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> ex;
}
</span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (RuntimeException ex) {
rollbackOnException(status, ex);
</span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> ex;
}
</span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Error err) {
rollbackOnException(status, err);
</span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> err;
}
</span><span style="color: #0000ff;">this</span><span style="color: #000000;">.transactionManager.commit(status);
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> messageReceived;
} </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
</span><span style="color: #008000;">//</span><span style="color: #008000;"> Execute receive outside of transaction.</span>
<span style="color: #0000ff;">return</span> doReceiveAndExecute(invoker, session, consumer, <span style="color: #0000ff;">null</span><span style="color: #000000;">);
}
}</span></pre>

在介绍消息监听器容器的分类时,已介绍了DefaultMessageListenerContainer消息监听器容器建立在SimpleMessageListenerContainer容器之上,添加了对事务的支持,那么此时,事务特性的实现已经开始了。如果用户配置了this.transactionManager ,也就是配置了事务,那么,消息的接收会被控制在事务之内,一旦出现任何异常都会被回滚,而回滚操作也会交由事务管理器统一处理,比如this.transactionManager.rollback(status)。

doReceiveAndExecute包含了整个消息的接收处理过程,由于参杂着事务,所以并没有复用模板中的方法。

  protected boolean doReceiveAndExecute(
Object invoker, Session session, MessageConsumer consumer, TransactionStatus status)
throws JMSException {
Connection conToClose </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;
Session sessionToClose </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;
MessageConsumer consumerToClose </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
Session sessionToUse </span>=<span style="color: #000000;"> session;
</span><span style="color: #0000ff;">boolean</span> transactional = <span style="color: #0000ff;">false</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">if</span> (sessionToUse == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
sessionToUse </span>=<span style="color: #000000;"> ConnectionFactoryUtils.doGetTransactionalSession(
getConnectionFactory(), </span><span style="color: #0000ff;">this</span>.transactionalResourceFactory, <span style="color: #0000ff;">true</span><span style="color: #000000;">);
transactional </span>= (sessionToUse != <span style="color: #0000ff;">null</span><span style="color: #000000;">);
}
</span><span style="color: #0000ff;">if</span> (sessionToUse == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
Connection conToUse;
</span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (sharedConnectionEnabled()) {
conToUse </span>=<span style="color: #000000;"> getSharedConnection();
}
</span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
conToUse </span>=<span style="color: #000000;"> createConnection();
conToClose </span>=<span style="color: #000000;"> conToUse;
conToUse.start();
}
sessionToUse </span>=<span style="color: #000000;"> createSession(conToUse);
sessionToClose </span>=<span style="color: #000000;"> sessionToUse;
}
MessageConsumer consumerToUse </span>=<span style="color: #000000;"> consumer;
</span><span style="color: #0000ff;">if</span> (consumerToUse == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
consumerToUse </span>=<span style="color: #000000;"> createListenerConsumer(sessionToUse);
consumerToClose </span>=<span style="color: #000000;"> consumerToUse;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;">接收消息</span>
Message message =<span style="color: #000000;"> receiveMessage(consumerToUse);
</span><span style="color: #0000ff;">if</span> (message != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
</span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (logger.isDebugEnabled()) {
logger.debug(</span>"Received message of type [" + message.getClass() + "] from consumer [" +<span style="color: #000000;">
consumerToUse </span>+ "] of " + (transactional ? "transactional " : "") + "session [" +<span style="color: #000000;">
sessionToUse </span>+ "]"<span style="color: #000000;">);
}
</span><span style="color: #008000;">//</span><span style="color: #008000;">模板方法,当消息接收且在未处理前给子类机会做相应处理,当前空实现</span>

messageReceived(invoker, sessionToUse);

boolean exposeResource = (!transactional && isExposeListenerSession() &&

!TransactionSynchronizationManager.hasResource(getConnectionFactory()));

if (exposeResource) {

TransactionSynchronizationManager.bindResource(

getConnectionFactory(), new LocallyExposedJmsResourceHolder(sessionToUse));

}

try {

//激活监听器

doExecuteListener(sessionToUse, message);

}

catch (Throwable ex) {

if (status != null) {

if (logger.isDebugEnabled()) {

logger.debug("Rolling back transaction because of listener exception thrown: " + ex);

}

status.setRollbackOnly();

}

handleListenerException(ex);

// Rethrow JMSException to indicate an infrastructure problem

// that may have to trigger recovery...

if (ex instanceof JMSException) {

throw (JMSException) ex;

}

}

finally {

if (exposeResource) {

TransactionSynchronizationManager.unbindResource(getConnectionFactory());

}

}

// Indicate that a message has been received.

return true;

}

else {

if (logger.isTraceEnabled()) {

logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") +

"session [" + sessionToUse + "] did not receive a message");

}

//接收到空消息的处理

noMessageReceived(invoker, sessionToUse);

// Nevertheless call commit, in order to reset the transaction timeout (if any).

// However, don't do this on Tibco since this may lead to a deadlock there.

if (shouldCommitAfterNoMessageReceived(sessionToUse)) {

commitIfNecessary(sessionToUse, message);

}

// Indicate that no message has been received.

return false;

}

}

finally {

JmsUtils.closeMessageConsumer(consumerToClose);

JmsUtils.closeSession(sessionToClose);

ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);

}

}

//监听器的激活处理

protected void doExecuteListener(Session session, Message message) throws JMSException {

if (!isAcceptMessagesWhileStopping() && !isRunning()) {

if (logger.isWarnEnabled()) {

logger.warn("Rejecting received message because of the listener container " +

"having been stopped in the meantime: " + message);

}

rollbackIfNecessary(session);

throw new MessageRejectedWhileStoppingException();

}

</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
invokeListener(session, message);
}
</span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (JMSException ex) {
rollbackOnExceptionIfNecessary(session, ex);
</span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> ex;
}
</span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (RuntimeException ex) {
rollbackOnExceptionIfNecessary(session, ex);
</span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> ex;
}
</span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Error err) {
rollbackOnExceptionIfNecessary(session, err);
</span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> err;
}
<span style="color: #ff0000;">commitIfNecessary</span>(session, message);

}

protected void invokeListener(Session session, Message message) throws JMSException {

Object listener = getMessageListener();

if (listener instanceof SessionAwareMessageListener) {

doInvokeListener((SessionAwareMessageListener) listener, session, message);

}

else if (listener instanceof MessageListener) {

doInvokeListener((MessageListener) listener, message);

}

else if (listener != null) {

throw new IllegalArgumentException(

"Only MessageListener and SessionAwareMessageListener supported: " + listener);

}

else {

throw new IllegalStateException("No message listener specified - see property 'messageListener'");

}

}

protected void doInvokeListener(MessageListener listener, Message message) throws JMSException {

listener.onMessage(message);

}

通过层层调用,最终提取监听器并使用listener.onMessage(message)激活了监听器,也就是激活了用户自定义的监听器逻辑。这里还有一句重要的代码很容易被忽略掉,commitIfNecessary(session, message),完成的功能是session.commit()。完成消息服务的事务提交,涉及两个事务,我们常说的DefaultMessageListenerContainer增加了事务的支持,是通用的事务,也就是说我们在消息接收过程中如果产生其他操作,比如向数据库中插入数据,一旦出现异常时就需要全部回滚,包括回滚插入数据库中的数据。但是,除了我们常说的事务之外,对于消息本身还有一个事务,当接收一个消息的时候,必须使用事务提交的方式,这是在告诉消息服务器本地已经正常接收消息,消息服务器接收到本地的事务提交后便可以将此消息删除,否则,当前消息会被其他接收者重新接收。

原文地址:https://www.cnblogs.com/wade-luffy/p/6090933.html

SpringJMS解析--监听器的更多相关文章

  1. SpringJMS解析3-监听器

    消息监听器容器是一个用于查看JMS目标等待消息到达的特殊bean,一旦消息到达它就可以获取到消息,并通过调用onMessage()方法将消息传递给一个MessageListener实现.Spring中 ...

  2. SpringJMS解析2-JmsTemplate

    尽管消息接收可以使用消息监听器的方式替代模版方法,但是在发送的时候是无法替代的,在Spring中必须要使用JmsTemplate提供的方法来进行发送操作,可见JmsTemplate类的重要性,那么我们 ...

  3. SpringJMS解析-JmsTemplate

    目录 通用代码抽取execute() 发送消息的实现 接收消息 尽管消息接收可以使用消息监听器的方式替代模版方法,但是在发送的时候是无法替代的,在Spring中必须要使用JmsTemplate提供的方 ...

  4. SpringJMS解析1-使用示例

    Spring配置文件: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="h ...

  5. SpringJMS解析--使用示例

    Spring配置文件: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="h ...

  6. Excel解析easyexcel工具类

    Excel解析easyexcel工具类 easyexcel解决POI解析Excel出现OOM <!-- https://mvnrepository.com/artifact/com.alibab ...

  7. spring整合activemq发送MQ消息[queue模式]实例

    queue类型消息 pom依赖 <dependency> <groupId>junit</groupId> <artifactId>junit</ ...

  8. 阿里巴巴excel工具easyexcel 助你快速简单避免OOM

    Java解析.生成Excel比较有名的框架有Apache poi.jxl.但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有 ...

  9. 【面试题】Java EE面试题

    第一部分:Servlet/JSP快速入门 1.      请列举至少6种javaEE技术,并简述其作用. 解析: javaEE是一系列的技术,主要包扩13种.对于开发人员来说,了解几种主要的技术是非常 ...

随机推荐

  1. CI框架在辅助函数中使用配置文件中的变量

    问题: 现有一个自定义的辅助函数,想要获取配置文件中的配置项(配置文件路径为application/config/config.php) 分析: 辅助函数并不是定义在一个class中,而是很多个可供外 ...

  2. nginx for Windows Known issues:path

    http://nginx.org/en/docs/windows.html nginx/Windows uses the directory where it has been run as the ...

  3. VMware 桥接 Bridge 复制物理网络连接状态

    https://zhidao.baidu.com/question/535593443.html 意思就是说,VM上使用的是虚拟的网卡,也就是说VM虚拟机上的网卡不是真实存在的,而桥接还有其他的网路链 ...

  4. Linux 清楚历史history命令的一个简单方法

    有时候做了一些历史记录 不想让别人知道, 发现最简单的办法是 set HISTSIZE=0 然后 history 就没有历史记录命令了 然后为了能用上下键进行历史命令使用 再 set HISTSIZE ...

  5. laravel5 报错419,form 添加crrf_field 后让然失败,本地环境配置问题

    这个是因为laravel自带CSRF验证的问题 解决方法 方法一:去关掉laravel的csrf验证,但这个人不建议,方法也不写出来了. 方法二:把该接口写到api.php上就好了 方法三: 首先在页 ...

  6. python 安装influxdb-python

    一.Linux下安装 1.yum install -y git 2.安装pip,参考:https://app.yinxiang.com/shard/s41/sh/0338ba85-5443-453f- ...

  7. Java之数组遍历

    package basic; //数组遍历方法 public class ForEach { public static void main(String[] args) { // 原始数组 Stri ...

  8. maven 父工程 消除重复 对子模块进行管理 主要继承依赖

     子类继承父类的 可以不需要groupid与versionid

  9. UVALive5874 - Social Holidaying-二分图匹配/匈牙利算法

    有n个家庭,m个房间,一个房间只能两个家庭住.求最大匹配. 比较标准的二分图问题.先初始化把可能的家庭建边,然后跑一边匈牙利算法. 最后的答案是最大匹配数/2,因为建图时有重复. #include & ...

  10. mvc Filters 过滤器

    项目需要控制controller和action的访问权限. 看了下资料,发觉还是很方便的. 首先在mvc项目下创建一个文件夹 Filters, 然后在Filters中创建一个类.代码如下 namesp ...