springbank 开发日志 一次因为多线程问题导致的applicationContext.getBean()阻塞
几天前遇到的这个问题。由于交易是配置的,不同的交易是同一个类的不同实例,所以不可能提前将其以@autowired类似的方式注入到需要的类中
<op:transaction id="Recharge" template="TransTemplate"></op:transaction> <op:transaction id="QueryAgreementAcct" template="TransTemplate"></op:transaction>
只能写一个工具类,实现ApplicationContextAware,取得容器,然后收到交易报文的时候,根据报文里的交易名,去取得容器中对应的transaction bean。然后根据bean的template等等信息,往下执行。
但是当我在做这件事情的时候,遇到一个奇怪的问题。这个问题我在cnblog上问过(https://q.cnblogs.com/q/95168/)。问了之后这段时间在准备一场面试,唉唉,失败了。所以今天才回过头来看这个问题:
public class TcpServer implements ApplicationContextAware{ private OpenbankExecutor executor; private int port; private PacketHandler packetHandler; private ServerSocket serverSocket; private Socket socket; private ApplicationContext applicationContext; private final Logger log = LoggerFactory.getLogger(getClass()); public void init() throws IOException { serverSocket = new ServerSocket(port); log.debug("TcpServer 成功启动"); while(true) { socket = serverSocket.accept(); System.out.println("收到socket请求"); executor.execute(new Runnable() { @Override public void run() { try { Map context = packetHandler.handle(socket.getInputStream()); String tranCode = (String) context.get("tranCode"); Assert.hasText(tranCode); Transaction transaction = (Transaction) applicationContext.getBean(tranCode); Template template = (Template) applicationContext.getBean(transaction.getTemplate()); log.debug("transaction: "+transaction.getId()); log.debug("transaction: "+template.getId()); } catch (Exception e) { e.printStackTrace(); } } }); } }
这个TcpServer 类也是Spring容器管理的:
<bean id="tcpServer" class="com.openbank.portal.server.TcpServer" init-method="init"> <property name="executor" ref="openbankThreadPool"/> <property name="port" value="${tcp.port}"/> <property name="packetHandler" ref="xmlpacketHandler"/> </bean>
结果我测了一下,收到交易报文之后,走到上面代码红色的地方就“卡”住了。就像程序执行完了一样,但是没有打印出后面的debug信息,纠结了一段时间没有搞懂为什么。
今天我把spring的源代码下载下来了,DEBUG了一下,发现好像是锁的问题,因为走到了一个synchronized方法就没有后文了,可见是一直没有获取到锁
然后我又写了一些测试,把Spring启动期间所有被实例化的Singleton全部打印出来看,最后才慢慢搞明白
原来就是线程的问题。
因为啊因为啊,我上面代码里面可以看到,socket = serverSocket.accept();
这一段,是直接在TcpServer被初始化时就要运行的,但是这里阻塞的,一直等待报文的到来。就是这样,导致spring加载bean的过程,加载到这个bean也就卡住了,甚至后面还有bean根本没有机会得到实例化。
解决方式很简单,我把socket = serverSocket.accept(); 这个放到一个新开的线程里面取处理就好了。