網頁

2021/4/26

Spring Boot 使用@Lookup取得prototype bean

Spring IoC管理的bean預設scope為singleton。若bean為有狀態(stateful)時應設為prototype才能使每一次產生的bean實例不同。而當singleton bean要取得依賴的prototype bean的實例時,可使用@Lookup annotation來取得不同的prototype bean的實例。


例如下面是singleton bean SingletonBean,其依賴於prototype bean PrototypeBean並以@Autowired注入至其成員變數prototypePrototypeBean為有狀態count的bean

SingletonBean.count()被呼叫時會呼叫PrototypeBean.addCount()累加狀態count的值並回傳。

SingletonBean

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonBean {

    @Autowired
    private PrototypeBean prototypeBean;

    public int count() {
        prototypeBean.addCount();
        return prototypeBean.getCount();
    }
}

PrototypeBean

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeBean {

    private int count;

    public void addCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

下面測試方法中呼叫了兩次SingletonBean.count(),可看到回傳的count的值是被累加的,代表SingletonBean.prototypeBean每次呼叫時都是同一個實例。

SingletonBeanTests

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class SingletonBeanTests {

    @Autowired
    private SingletonBean singletonBean;

    @Test
    public void getSamePrototypeBeanCount() {
        int count = singletonBean.count();
        System.out.println(count); // 1
        Assertions.assertEquals(1, count);

        count = singletonBean.count();
        System.out.println(count); // 2
        Assertions.assertEquals(2, count);
    }
}

若需要每次SingletonBean.count()使用的都是新的PrototypeBean實例,則可改用@Lookup的方式取得。

修改SingletonBean如下,新增一返回型態為PrototypeBean的公開方法並在前面加上@Lookup,則執行時Spring會利用CGLIB技術產生SingletonBean的子類別並覆寫此方法來返回指定形態的bean實例。

SingletonBean

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonBean {

//    @Autowired
//    private PrototypeBean prototypeBean;

    public int count() {
        PrototypeBean prototypeBean = getPrototypeBean();
        prototypeBean.addCount();
        return prototypeBean.getCount();
    }

    @Lookup
    public PrototypeBean getPrototypeBean() { // this method wiil be override by Spring CGLIB generated subclass at runtime and return correspondent prototype bean instance
        return null;
    }
}

下面測試方法中呼叫了兩次SingletonBean.count(),可看到回傳的count的值沒有累加,代表SingletonBean.count()每次被呼叫時取得的PrototypeBean實例是不同的。

SingletonBeanTests

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class SingletonBeanTests {

    @Autowired
    private SingletonBean singletonBean;

    @Test
    public void getDifferentPrototypeBeanCount() {
        int count = singletonBean.count();
        System.out.println(count); // 1
        Assertions.assertEquals(1, count);

        count = singletonBean.count();
        System.out.println(count); // 1
        Assertions.assertEquals(1, count);
    }
}


注意@Lookup方法無法在配置類中以@Bean註冊的bean中使用,因為bean實例不是由容器產生。


例如把SingletonBean改以@Bean的方式註冊。

DemoApplication

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    @Bean
    public SingletonBean getSingletonBean() {
        return new SingletonBean();
    }
}

則呼叫getPrototypeBean()返回null。

SingletonBean

import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

//@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
//@Component
public class SingletonBean {

//    @Autowired
//    private PrototypeBean prototypeBean;

    public int count() {
        PrototypeBean prototypeBean = getPrototypeBean(); // NullPointerException
        prototypeBean.addCount();
        return prototypeBean.getCount();
    }

    @Lookup
    public PrototypeBean getPrototypeBean() {
        return null;
    }
}


沒有留言:

張貼留言