問題描述
給一個系統寫服務端api,採用的spring+jersey
的代碼架構如下圖
定義了一個InfoResource
,其中使用@autowired 來注入對應的InfoQueryService
,調用方式如下。測試時發現,如果單線程調用接口則一切正常,如果多線程併發調用這個接口,則部分請求返回內容是不完整的,然而程序運行並沒有任何報錯。
// InfoResource.java
@Path("/info")
@Component
public class InfoResource {
@Autowired
private InfoQueryService infoQueryService;
.......
@POST
@Path("/json/upload")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED })
@Produces(MediaType.APPLICATION_JSON)
public Response uploadJson(@Context final HttpHeaders headers, final RequestBodyBean requestBodybean) {
NormalResponseBean normalResponseBean = null;
try {
normalResponseBean = infoQueryService.doResponseService(headers, requestBodybean);
} catch (Exception e) {
e.printStackTrace();
}
......
}
}
// InfoQueryService.java
@Component
public class InfoQueryService {
@Autowired
private ConfigProperty configProperty;
@Autowired
private OrgDAO orgDAO;
@Autowired
private InfoDAO infoDAO;
/**
* 查詢請求接口service 主函數
*
* @param headers
* @param bodyBean
* @return
*/
public NormalResponseBean doResponseService(HttpHeaders inHeaders, RequestBodyBean bodyBean) {
......
}
}
問題分析
在service上添加synchronized,發現問題解決了。但是接口速度明顯降低,公網調用從80ms降低到130ms,這顯然不能接受。刪除synchronized。
繼續測試,程序不報錯說明service注入是成功的,那麼哪一層錯了呢?
在service層打日誌,查看每次接口拼裝的返回結果都是對的。
在resource層打日誌,發現部分返回結果是錯誤的。也就是service處理是正確的,返回給resource層就錯誤了。
判定是service注入到resource的過程有問題。
爲什麼呢?
spring對組建的注入(@autowired)默認用的單例模式,在上面代碼中,spring使用了一個infoQueryService實例來處理多個接口請求。高併發情況下就可能導致service實例在內存層面出現衝突。
有問題的代碼:
@Autowired
private InfoQueryService infoQueryService;
舉例子,service實例是一個服務員,服務員一次服務一個客戶是沒問題的,但是同時服務多個客戶時,服務員思維就不能保持清晰了。
怎麼解決?
多配置服務員!
把單例注入修改爲多例注入,每次請求新建一個infoQueryService實例。
代碼實現
在spring的單例中注入多例由多種實現方式,筆者偏好於使用@Lookup
註解,實現如下。
// InfoResource.java
@Path("/info")
@Component
public class InfoResource {
// @Autowired // 不再使用autowired
private InfoQueryService infoQueryService;
.......
// 使用Lookup實現多例注入
@Lookup
public InfoQueryService getPrototypeBean() {
// 返回null即可,spring會自動重寫這個方法,爲你返回正確的InfoQueryService實例
return null;
}
@POST
@Path("/json/upload")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED })
@Produces(MediaType.APPLICATION_JSON)
public Response uploadJson(@Context final HttpHeaders headers, final RequestBodyBean requestBodybean) {
NormalResponseBean normalResponseBean = null;
try {
infoQueryService = getPrototypeBean(); // 每次請求會新建一個實例
normalResponseBean = infoQueryService.doResponseService(headers, requestBodybean);
} catch (Exception e) {
e.printStackTrace();
}
......
}
}
最後,多例注入顯然會比單例注入慢,測試結果看會慢2到5個毫秒。
更多多例注入的實現方法:
Java編程中的Spring多例