為什么多線程、junit 中無法使用spring 依賴注入?


  為什么多線程、junit 中無法使用spring 依賴注入? 這個問題,其實體現了,我們對spring已依賴太深,以至於不想自己寫實例了。 那么到底是為什么在多線程和junit單元測試中不能使用依賴注入呢?

一、為什么多線程下spring的依賴注入失效了呢?

  答:因為spring為了考慮安全性問題,在多線程情況下,不支持直接使用 @Resouce 注解方式進行直接的bean注入,那么也就是說,如果在多線程調用該注入實例化的變量時,將會報NullPointerException 。

  解決辦法: 多線程情況下,通過調用的service進行傳入需要操作的bean變量,而多線程只是將前台工作轉移到后台。如下:

# CalculateServiceImpl.java 服務實現,傳入需要調用的bean
    package com.xx.op.user;
    
    import com.xx.note.dubbo.dto.CertificateApplyDto;
    import com.xx.con.dubbo.api.ContractRemoteService;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.annotation.Resource;
    
    public class CalculateServiceImpl implements CalculateService {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
        
        @Resource(name = "threadPoolTaskExecutor")
        private ThreadPoolTaskExecutor threadPoolTaskExecutor;
        
        @Resource                // 默認name可寫可不寫
        private ContractRemoteService contractRemoteService;
        
        @Override
        public void doSth(String userId) throws Exception
            CertificateApplyDto certificateApplyDto = new CertificateApplyDto();
            certificateApplyDto.setBorrowNid(userId);
            SignContractThread signContractThread = new SignContractThread(contractRemoteService, certificateApplyDto);
            threadPoolTaskExecutor.execute(signContractThread);                //異步簽署協議
        }
    }

# SignContractThread.java 異步實現調用

    package com.xx.cc.common.async;

    import com.alibaba.fastjson.JSON;
    import com.dianping.cat.message.Event;
    import com.xx.framework.dto.ResponseEntity;
    import com.xx.no.dubbo.dto.CertificateApplyDto;
    import com.xx.con.dubbo.api.ContractRemoteService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class SignContractThread implements Runnable {
        private Logger logger = LoggerFactory.getLogger(this.getClass());

        /**
         * 無法使用依賴注入實現多純種的bean, 從外部傳入方式
         */
        private ContractRemoteService contractRemoteService;

        private CertificateApplyDto certificateApplyDto;

        public SignContractThread(ContractRemoteService contractRemoteService, CertificateApplyDto certificateApplyDto) {
            this.contractRemoteService = contractRemoteService;
            this.certificateApplyDto = certificateApplyDto;
        }

        @Override
        public void run() {
            String requestParamJson = JSON.toJSONString(this.doSSt);
            logger.debug("===========>>>>> 異步調用, 參數: {} ==============>>>>>>>>>", requestParamJson);
            try {
                ResponseEntity responseEntity = contractRemoteService.doSSt(certificateApplyDto);
                logger.debug("<<<<<<<<<<=========== 異步調用,method:doSSt,返回結果:{}", responseEntity);
                EE.logEvent("Monitor_signContract", "asyncSignContractResult", Event.SUCCESS, JSON.toJSONString(responseEntity));
            } catch (Exception e) {
                logger.error("==-------===異步調用,發生異常,請求參數: {}, 異常-{}", requestParamJson, e););
                throw new RuntimeException("異步調用_doSSt,發生異常", e);
            } finally {
                // ... 調用完畢
            }
        }
    }

  這樣,通過傳入外部依賴注入的bean,線程進行調用,即可避免線程無法注入bean的問題了。當然了,你可能還會想到使用 getBean的方法獲取,其實也是可以的,不過應該有一定的危險性,因為相當於你得再次將applicationContext里的東西再實例化一遍。

二、junit中無法使用依賴注入的問題?

  答:因為junit一般會走最小化的方式,而非每次都要將整個框架的東西載入,從而減少加載時間。當然,如果確實需要,這個問題,其實目前在高版本的junit中,已經不存在了,通過加載 SpringJUnit4ClassRunner,即可進行注入值。

  解決方案1:使用高版本的junit進行測試,如下:

    
    package com.xx.mybatis3spring3intg.junit;

    import java.util.List;

    import com.xx.mybatis3spring.bean.User;
    import com.xx.mybatis3spring.service.UserService;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({"/config/application*.xml"})
    public class UserServiceTest {
        
        @Resource
        private UserService userService;
        
        @Test
        public void c1() {
            List<User> userList = userService.query(new User());
            System.out.println(userList);
        }

    }

  解決方案2:通過getBean的方式獲取需要的bean,因為僅僅是單元測試,加載資源稍微多些也沒有關系。

    package com.xx.c.order;

    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationCoimport com.xx.c.service.order.OrderService;

    public class OrderServiceTest {
        private static ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        private static OrderService orderService = (OrderService)context.getBean("orderService");
        private static Object getBean(String name) {
            if (context == null) {
                context = new ClassPathXmlApplicationContext("applicationContext.xml");
            }
            return context.getBean(name);
        }
        public static void main(String[] args) {
            String borrowNid = "11111111111";
            com.xx.c.dubboapi.biz.OutApiManager service = (com.xx.c.dubboapi.biz.OutApiManager)getBean("outApiManagerImpl");
            System.out.println("========"+service.getWang(borrowNid));
            System.out.println("========"+service.getRepayList("111111111111111111"));
       }    
    }

 

  以上,就基本解決了如題所問,當然也可以作為所有無法注入bean的問題的解決方案。信不信由你,反正我是信了。 

  注意的幾點就是:

      1, 多線程的執行,面向C端的用戶,一定不能直接 new Thread() 進行多線程操作,否則會死得很慘。

    2,多做好日志記錄,在出錯的時候進行排查真的很方便,但也得做日志的清理工作,否則服務器空間被占滿也不是很長時間的事。

      3,單元測試還是有必要做的,否則提交測試時,自己哪來的底氣呢。

      4,多了解spring核心的東西,做到知其然知其所以然,不要脫離spring就立刻變小白了。

  積跬步,致千里!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM