spring+jersey寫api服務端,@autowired引起的併發請求問題和解決過程

問題描述

給一個系統寫服務端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多例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章