如何在多线程中注入bean?!

Carol_Li
  • 205

前几天,在sf这里也提过这个问题,但是仍未得到解决,上一个问题是这个在Service中注入Dao不成功,Dao为null。但根据大虾们的回答,我想再理顺一下这个项目的逻辑,免得各位看着都不懂我在说什么。本人是spring小白,整个项目环境搭建是前辈写下的,我只是根据他的方法来添加修改,所以有很多框架上的逻辑我并不是看得很懂。

我想实现的是——用多线程通过socket不断获取从客户端发送过来的消息,并对消息联系JDBC进行分析

P.S.代码部分都只截取了重要的部分
我先放一下其中一个service和dao的实现:

1)ConcentratorService:

public interface ConcentratorService {
    public List<Concentrator> getConcentratorListByMacAddresses(String[] macAddr) throws Exception;
}

2)ConcentratorServiceImpl:

@Service("ConcentratorService")
public class ConcentratorServiceImpl implements ConcentratorService{
    @Autowired
    ConcentratorDao concentratorDao;
    public Concentrator findConcentratorByCaddress(String caddress) throws Exception{
            // TODO Auto-generated method stub
            return concentratorDao.findConcentratorByCaddress(caddress);
    }
}

3)ConcentratorDao

public interface ConcentratorDao {
    public List<Concentrator> getConcentratorListByMacAddresses(String[] macAddr) throws Exception;
}

4)ConcentratorDaoImpl

public class ConcentratorDaoImpl implements ConcentratorDao {

    @Autowired
    SessionFactory sessionFactory;

    Session session = null;
    Transaction tx = null;
    
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory=sessionFactory;
    }    

    @Override
    public List<Concentrator> getConcentratorListByMacAddresses(String[] macAddr) throws Exception {
        session = sessionFactory.openSession();
        tx = session.beginTransaction();
        
        StringBuffer sb = new StringBuffer("from Concentrator where caddress in (");
        for(int i=0; i<macAddr.length; i++){
            if(i==macAddr.length-1){
                sb.append("?)");
            }else{
                sb.append("?,");
            }
        }
        
        Query query=session.createQuery(sb.toString());
        for(int i=0; i<macAddr.length; i++){
            query.setParameter(i, macAddr[i]);
        }
        

        @SuppressWarnings("unchecked")
        List<Concentrator> clist=query.list();
        tx.commit();
        session.close();
        return clist;
    }
    
}

这里再放一下datasource.xml,这个xml是用来配置bean的。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc 
    http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
  http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
  http://www.springframework.org/schema/util 
  http://www.springframework.org/schema/util/spring-util-3.2.xsd
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/gwsl?characterEncoding=gbk" />  <!-- ?characterEncoding=UTF-8&amp;useOldAliasMetadataBehavior=true" -->
        <property name="username" value="XXX" />
        <property name="password" value=" XXX" />
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"></property>
    </bean>
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="annotatedClasses">
            <list>
                <value>com.streetLight.model.Concentrator</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
            </props>
        </property>
    </bean>    

    <bean id="txManager"
        class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <bean id="persistenceExceptionTranslationPostProcessor"
        class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<bean id="concentratorDao" class="com.streetLight.dao.ConcentratorDaoImpl"></bean>
    <bean id="concentratorService" class="com.streetLight.services.ConcentratorServiceImpl"></bean>

</beans>

以防万一把web.xml也放一下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
  <display-name></display-name>
  
  <session-config>
  <session-timeout>30</session-timeout>
 </session-config>
 
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/config/root-context.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
  
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/config/root-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
    
</web-app>

****这里之后就是想添加的功能的代码:****
RunSocket:

//@Component
public class RunSocket {
    
    public static void main(String[] args) throws InterruptedException { 
        System.out.println("开始启动websocket"); 
        WebSocketImpl.DEBUG = false; 
        int port = 8888; // 端口随便设置,只要不跟现有端口重复就可以 
        WebSocket s = null; 
        try { 
            s = new WebSocket(port); 
        } catch (UnknownHostException e) { 
            System.out.println("启动websocket失败!"); 
            e.printStackTrace(); 
        }
    s.start(); 
    System.out.println("启动websocket成功!"); 
    
    System.out.println("开始启动ServerSocket"); 
    ReceiveThread mReceiveThread = new ReceiveThread();
    mReceiveThread.init();
    mReceiveThread.start();
    }
//    代码修改自:
//    作者: 手插口袋_ 
//    链接:http://www.imooc.com/article/12401
}

下面是thread和解析信息的代码:
ReceiveThread:(方案一)

public class ReceiveThread extends Thread {
    
    private ApplicationContext ctx;
    private SpringContextUtil scu;
    
    static final int SOCKET_PORT_0 = 8800; // 端口号
    static ServerSocket mServerTest = null;
    static Socket mSocket = null;
    static InputStream mInput = null;
    byte[] buffer;
    static final byte CONTROLLER_FAULT = (byte)0x90;

    List<Area> alist=new ArrayList<Area>();
    List<String> msgList=new ArrayList<String>();    

    ConcentratorService concentratorService;
    
    public void init() {
        buffer = new byte[65536];
        //scu = new SpringContextUtil();
        ctx = new FileSystemXmlApplicationContext("/WebRoot/WEB-INF/config/datasource.xml");
        concentratorService =(ConcentratorService) ctx.getBean("concentratorService");
//        scu.setApplicationContext(ctx);
//        controllerofService =(ControllerofService) SpringContextUtil.getBean("controllerofService");
//        concentratorService =(ConcentratorService) SpringContextUtil.getBean("concentratorService");
//        lightPoleService =(LightPoleService) SpringContextUtil.getBean("lightPoleService");
//        controllerFaultService = (ControllerFaultService) SpringContextUtil.getBean("ControllerFaultService"); 
    }

    public void run() {
        try {
            mServerTest = new ServerSocket(SOCKET_PORT_0);
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        int size = -1;
        while (true) {
            
            try {
                if (size < 0) {
                    System.out.println("等待前置机的链接....");
                    mSocket = mServerTest.accept();
                    System.out.println("服务器测试程序已链接....");
                } else {
                    byte[] realBuffer = new byte[size];
                    System.arraycopy(buffer, 0, realBuffer, 0, size);
                    System.out.print("Message from front end: ");
                    msgList=parseFrontEndMsg(realBuffer,alist);
                    WebSocket.getMsgFromServer(msgList);
                }
                mInput = mSocket.getInputStream();
                size = mInput.read(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    public List<String> parseFrontEndMsg(byte[] mBytes,List<Area> alist) throws Exception{
        ControllerFaultService controllerFaultService=this.controllerFaultService;
        byte msg_code = mBytes[1];
        String macAddr="";

        switch (msg_code = mBytes[1]) {
            case CONTROLLER_FAULT:
                //省略代码。macAddr分析自客户端发送来的信息。
                    Concentrator c=concentratorService.findConcentratorByCaddress(macAddr);
                //省略代码。
                return faultList;
        }
        return null;
    }

}

直接运行RunSocket的main函数,用上面这种方法service能被赋值,但是dao却是空的。

之后我又试了一中,本想spring自己注入的。
ReceiveThread:(方案二)

@Component
public class ReceiveThread extends Thread {
    
    static final int SOCKET_PORT_0 = 8800; // 端口号
    static ServerSocket mServerTest = null;
    static Socket mSocket = null;
    static InputStream mInput = null;
    byte[] buffer;
    static final byte CONTROLLER_FAULT = (byte)0x90;

    List<Area> alist=new ArrayList<Area>();
    List<String> msgList=new ArrayList<String>();    

    @Autowired
    ConcentratorService concentratorService;
    //@Autowired和@Resource的运行结果都一样,concentratorService为null
    
    public void init() {
        buffer = new byte[65536];
    }

//以下代码同上,省略。

但是这种方法,更是什么都没有被注入,service的值为空,我不知道问题出在哪里。
是不是spring的环境在运行RunSocket的mian函数时并没有被布置?

重申一次,service和dao的配置上应该是没什么问题的,因为在Run On Server后,controller中bean都是能成功被注入并被使用。

我在看spring的入门教程,发现和我这个工程有点差别,前辈写的接口并没有setter注入或者constructor注入,但却能运行,我猜是不是用了@Autowired这些标示就可以省略前面两种方式的注入?(如果我的问题很白痴,请不要介意啊),再此先谢过各位。

“对于我这种逻辑白痴来说,spring真的很难学。”

=================ENDING==================

由于试了多种方法都没有实现service的自动注入或用容器取得service后实现dao的注入,最终还是采取了最愚蠢的方式,建立一个容器,直接取出DaoImpl的bean(而不通过service->serviceImpl->dao->daoImpl这个途径),来最终连接数据库了。
算是一种妥协吧,唉。多谢各位这么多天的热心帮助。或许之后的某一天,当自己学好spring再回来看这个问题可能就能得到更好的解决。

回复
阅读 11.4k
7 个回答
Carol_Li
  • 205
✓ 已被采纳

你的需求简单来说就是这样:在Spring启动之后,开启一个工作线程做持续做一些事情。所以关键点就是:

  1. 确保在Spring启动之后工作线程才能开始,因为只有这个时候Spring中的Bean都已经初始化好了,你现在做的不是这样,所以你会以为Spring没有注入

  2. 这个线程应该在Spring关闭的时候也关闭

实现这个需求很简单:

  1. 把RunSocket变成一个@Component

  2. RunSocket实现SmartLifeCycle接口,实现这个接口Application启动和关闭的回调方法

  3. 在启动回调方法里,构建ReceiveThread,ReceiveThread不是Bean,它所需要的其他组件都是RunSocket给的

  4. 在关闭的回调方法里,将ReceiveThread关闭

1.在你的main函数中初始化spring,你没有初始化spring。平常写web,spring会加载是因为web.xml是web的入口,所以会主动加载,你用main函数来启动,那么它仅仅只是简单的入口,不会加载web.xml的,所以手动加载就行了。
2.然后如果在作用域外想拿到spring管理的类的话,写个工具类,继承spring的ApplicationContextAware,然后可以取到spring的applicationContext,然后可以使用这个工具类在域外,例如bean内,使用spring管理的类。
3.其实你这样可以不用spring,直接用懒汉写个单例就行了,等接口变多再用spring

你这个线程类是new出来的,所以Spring是没办法进行管理的。
如果要在Spring中使用多线程,建议你看下Spring的TaskExecutor

我也遇到过spring多线程注入的需求,使用了taskExecutor线程池

    @Resource
    private SyncTaskService syncTaskService;
    @Resource
    private ThreadPoolTaskExecutor taskExecutor;
    @Service
public class SchedulingServiceImpl implements SchedulingService {
    private class SendDuty implements Runnable {

        private final JSONObject jsonObject;

        public SendDuty(JSONObject jsonObject) {
            this.jsonObject = jsonObject;
        }

        @Override
        public void run() {
            System.out.println("多线程启动");
            SchedulingServiceImpl.this.syncTaskService.syncScheduleTask(jsonObject);
        }

    }
   }

调用的方法

         SendDuty sendDuty = new SendDuty(object);
         taskExecutor.execute(sendDuty);

下面是线程池的配置

 <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!-- 线程池维护线程的最少数量 -->
        <property name="corePoolSize" value="5"/>
        <!-- 线程池维护线程所允许的空闲时间 -->
        <property name="keepAliveSeconds" value="30000"/>
        <!-- 线程池维护线程的最大数量 -->
        <property name="maxPoolSize" value="1000"/>
        <!-- 线程池所使用的缓冲队列 -->
        <property name="queueCapacity" value="200"/>
    </bean>
geshiwuyu
  • 117

spring初始化时直接注入到web项目内,该main方法与web无关,dao当然无法注入到service内,需要在main方法中初始化spring,才能注入spring相关类

dao实现好像没有托管给spring管理 可以通过一个在类上注解@Repository

宣传栏