Spring @Lookup實現單例bean依賴注入原型bean

作者: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).下圖是打印結果說明了這一點.

image

樣例代碼傳送門:https://github.com/simos-code/springboot-quick-start/tree/lookup

想學習分佈式、微服務、JVM、多線程、架構、java、python的童鞋,千萬不要掃碼,否則後果自負~

林老師帶你學編程https://wolzq.com

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