作者:simoscode
地址:https://www.jianshu.com/p/5254e1947d77
大多數場景,在Spring容器的大多數bean都是單例的.當一個單例bean A依賴另一個單例bean B,直接在A中定義一個屬性與bean B類型一樣,然後通過setter方法注入或者構造函數參數注入即可.但是當bean的生命週期不一樣就會有問題。
比如一個單例bean A需要使用一個非單例(原型)bean B,A每次方法調用都需要一個新的bean B.容器只創建單例bean一次,這樣只有一次機會設置這個值.容器不能給bean A提供一個新的bean B實例在bean A需要的時候.如何解決這個問題呢?
Spring 給我提供兩種解決方法,如下:
* 一種解決的方法就是放棄依賴注入.你可以讓bean A通過實現`ApplicationContextAware`接口並且在
bean A每次需要bean B的時候通過調用getBean("B")向容器請求一個新的bean B實例
* 另外一種方法是使用`@Lookup`註解
考慮一下這個場景:假如我們有大量的消息需要推送,爲了提高性能,我們會使用一個任務池去實現,每個需要推送的消息就是一個任務.從這個業務場景中,我們至少可以提取幾個bean,一個是實現推送(阿里雲移動推送,蘋果apns等)的單例bean,發送消息任務原型bean,推送組件(任務池)單例bean,還有一個是業務邏輯層的推送單例bean(這個bean依賴推送組件bean).我們用兩種方法實現。
實現推送(阿里雲移動推送,蘋果apns等)的單例bean
package com.simos.service;
import org.springframework.stereotype.Service;
/**
* Created by l2h on 18-4-25.
* Desc:模擬真正實現推送功能的底層類
* @author l2h
*/
@Service
public class PushService {
public void pushMsg(String msg){
System.out.println(msg);
}
}
發送消息任務原型bean
/**
* Created by l2h on 18-4-25.
* Desc: 推送消息任務
* @author l2h
*/
@Service("task")
@Scope(SCOPE_PROTOTYPE)
public class PushMsgTask implements Runnable{
private String msg ;
public PushMsgTask(){
}
public PushMsgTask(String msg){
this.msg = msg;
}
@Autowired
PushService pushService;
@Override
public void run() {
pushService.pushMsg(msg);
}
public void setMsg(String msg){
this.msg = msg;
}
}
通過實現ApplicationContextAware接口單例bean中獲取原型bean
package com.simos.service;
import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* Created by l2h on 18-4-25.
* Desc:消息推送任務池組件.使用aware,這樣業務代碼就依賴了Spring框架
* @author l2h
*/
@Service
public class AwarePushMsgPool implements ApplicationContextAware{
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 線程池
*/
private ThreadPoolExecutor executorService;
/**
* 任務隊列
*/
private TaskQueue taskqueue ;
/**
* 最大隊列數量.通常配置在配置文件中.這裏樣例代碼不加太多東西.
* 簡單點使用@value注入,複雜點像springboot一樣@Configuration+@ConfigurationProperties
*/
private final int acceptCount = 10000;
/**
*核心線程數
*/
private final int corePoolSize = 20;
/**
* 最大線程數
*/
private final int maxPoolSize = 100;
/**
* 線程保活時間
*/
private final int keepAliveTime =60;
public AwarePushMsgPool(){
taskqueue = new TaskQueue(acceptCount);
TaskThreadFactory tf = new TaskThreadFactory("simos-pool-msg-",true,Thread.NORM_PRIORITY);
executorService = new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime, TimeUnit.SECONDS,
taskqueue, tf);
executorService.setThreadRenewalDelay(org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY);
taskqueue.setParent(executorService);
}
public void pushMsg(String msg){
if (msg!=null){
try {
//所需要的原型bean不是通過依賴注入的,而是直接bean容器拿到的,違反了IoC原則
PushMsgTask task = pushMsgTask(msg);
task.setMsg(msg);
System.out.println("aware class:"+this.getClass());
executorService.submit(task);
}
catch (Exception exception){
System.out.println("推送失敗,失敗原因:"+exception.getMessage());
}
}
}
protected PushMsgTask pushMsgTask(String msg){
PushMsgTask task = applicationContext.getBean("task",PushMsgTask.class);
task.setMsg(msg);
return task;
}
}
通過實現@Lookup接口單例bean中獲取原型bean
package com.simos.service;
import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* Created by l2h on 18-4-25.
* Desc:消息推送任務池組件
* @author l2h
*/
@Service
public class LookupPushMsgPool {
/**
* 線程池
*/
private ThreadPoolExecutor executorService;
/**
* 任務隊列
*/
private TaskQueue taskqueue ;
/**
* 最大隊列數量.通常配置在配置文件中.這裏樣例代碼不加太多東西.
* 簡單點使用@value注入,複雜點像springboot一樣@Configuration+@ConfigurationProperties
*/
private final int acceptCount = 10000;
/**
*核心線程數
*/
private final int corePoolSize = 20;
/**
* 最大線程數
*/
private final int maxPoolSize = 100;
/**
* 線程保活時間
*/
private final int keepAliveTime =60;
public LookupPushMsgPool(){
taskqueue = new TaskQueue(acceptCount);
TaskThreadFactory tf = new TaskThreadFactory("simos-pool-msg-",true,Thread.NORM_PRIORITY);
executorService = new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime, TimeUnit.SECONDS,
taskqueue, tf);
executorService.setThreadRenewalDelay(org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY);
taskqueue.setParent(executorService);
}
public void pushMsg(String msg){
if (msg!=null){
try {
System.out.println("lookup class:"+this.getClass());
PushMsgTask task = pushMsgTask(msg);
executorService.submit(task);
}
catch (Exception exception){
System.out.println("推送失敗,失敗原因:"+exception.getMessage());
}
}
}
@Lookup
protected PushMsgTask pushMsgTask(String msg){
return new PushMsgTask(msg);
}
}
通過對比LookupPushMsgPool與AwarePushMsgPool實現可以看出,AwarePushMsgPool通過實現ApplicationContextAware接口,從而得到動態獲取容器裏面bean的能力,違反了依賴注入的原則,業務代碼耦合了Spring框架,實現了Spring框架的接口,通常我們業務bean不應該去實現Spring的接口,這種方法雖然實現了功能,但是不建議這麼使用.而通過@Lookup方法注入,就是依賴注入,不需要去實現特定接口什麼的.
@Lookup方法注入實現簡介
@Lookup
protected PushMsgTask pushMsgTask(String msg){
return new PushMsgTask(msg);
}
protected PushMsgTask pushMsgTask(String msg){
PushMsgTask task = applicationContext.getBean("task",PushMsgTask.class);
task.setMsg(msg);
return task;
}
通過對比發現,被@Lookup註解的pushMsgTask(String msg)方法幫我們實現的功能就是等價於AwarePushMsgPool的pushMsgTask(String msg).包含@Lookup註解方法的類,容器初始化的時候會通過cglib字節碼庫動態生成一個LookupPushMsgPool的子類,並且會覆蓋父類的實現,子類的pushMsgTask方法實現等價於AwarePushMsgPool的pushMsgTask(String msg).下圖是打印結果說明了這一點.
樣例代碼傳送門:https://github.com/simos-code/springboot-quick-start/tree/lookup
想學習分佈式、微服務、JVM、多線程、架構、java、python的童鞋,千萬不要掃碼,否則後果自負~
林老師帶你學編程:https://wolzq.com