singleton bean中依賴了prototype bean引發的問題

Spring 環境 5.0.x ,JDK 8

Bean的作用域

常用的singleton或prototype,其他的都依賴於web環境,這裏就先不說明了。下面是spring官網對單例和原型的解釋。

singleton

prototype

衆所周知,在Spring容器中,bean的scope默認是singleton單例的。

如果在singleton的bean中依賴了prototype的bean,那麼會出現下面的問題,原型的bean每次獲取的都是同一個對象。

package com.along.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

/**
 * scope默認是singleton
 */
@Repository
public class OrderDao {

	@Autowired
	private IndexDao indexDao;
    
        //這裏分別打印當前orderDao的hashCode和它所依賴indexDao的hashCode
	public void order(){
		System.out.println("orderDao:"+this.hashCode());
		System.out.println("indexDao:"+indexDao.hashCode());
	}
}
package com.along.dao;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

@Repository
@Scope("prototype")
public class IndexDao {
	public void test(){
		System.out.println("IndexDao#test()");
	}
}
package com.along.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.along")
public class AppConfig {
}
package com.along;

import com.along.config.AppConfig;
import com.along.dao.OrderDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


public class App
{
    public static void main( String[] args ) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AppConfig.class);
        context.refresh();
        OrderDao orderDao = (OrderDao) context.getBean("orderDao");
    	orderDao.order();
    	orderDao.order();
        context.close();
    }
}

上述代碼可以看到,單例的OrderDao依賴了原型的IndexDao,在OrderDao的order()方法中,分別打印當前orderDao的hashCode和它所依賴indexDao的hashCode,並且在main方法中調用了兩次orderDao的order()方法。講道理,indexDao每次輸出的hashCode應該不一樣,因爲indexDao的原型的,每次獲取的bean應該是重新創建的。但下面的結果可以看到,indexDao輸出的hashCode是一樣的,證明並沒有在每次調用時重新去new一個新的bean。

orderDao:1513712028
indexDao:1018547642
orderDao:1513712028
indexDao:1018547642

原因是因爲OrderDao只實例化了一次,那麼他只有一次機會爲他的依賴IndexDao去設置屬性,由於OtderDao是單例的,所以沒辦法再爲IndexDao去設置屬性

 

那麼如果解決這個問題呢?

在Spring官網找到了解決問題的方法,有兩種

https://docs.spring.io/spring/docs/5.0.14.RELEASE/spring-framework-reference/core.html#beans-factory-method-injection

第一種  實現ApplicationContextAware接口

1.4.6. Method injection

In most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean, or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the dependency by defining one bean as a property of the other. A problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container only creates the singleton bean A once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.

A solution is to forego some inversion of control. You can make bean A aware of the container by implementing the ApplicationContextAware interface, and by making a getBean("B") call to the container ask for (a typically new) bean B instance every time bean A needs it. The following is an example of this approach:

上面這段話的大概意思是

在大多數應用程序場景中,容器中的大多數bean都是單例的。當一個單例bean需要與另一個單例bean協作,或者一個非單例bean需要與另一個非單例bean協作時,通常通過將一個bean定義爲另一個bean的屬性來處理依賴關係。當bean的生命週期不同時,就會出現問題。假設單例bean A需要使用非單例(原型)bean B,可能是在A上的每個方法調用上。容器只創建單例bean A一次,因此只有一次機會設置屬性。容器不能每次需要bean B的新實例時都向bean A提供一個。

您可以通過實現applicationcontext - ware接口,以及在bean A每次需要bean B實例時對容器發出getBean(“B”)調用來讓bean A知道容器。下面是這種方法的一個例子:

按照官網的代碼做了修改,代碼如下:

package com.along.dao;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Repository;

@Repository
public class OrderDao implements ApplicationContextAware {

	private ApplicationContext applicationContext;

	@Autowired
	public IndexDao indexDao;

	public void order(){
		System.out.println("orderDao:"+this.hashCode());
		indexDao = (IndexDao) applicationContext.getBean("indexDao");
		System.out.println("indexDao:"+indexDao.hashCode());
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

輸出結果:

orderDao:1513712028
indexDao:1018547642
orderDao:1513712028
indexDao:1456208737

實現 ApplicationContextAware 接口的,並重寫setApplicationContext方法(),並通過applicationContext獲取bean,每次獲取到的就是不同的bean。

這個是第一種實現方法,但是這個嚴重依賴於Spring的API,侵入性太強。

第二種 @Lookup註解

Lookup method injection

Lookup method injection is the ability of the container to override methods on container managed beans, to return the lookup result for another named bean in the container. The lookup typically involves a prototype bean as in the scenario described in the preceding section. The Spring Framework implements this method injection by using bytecode generation from the CGLIB library to generate dynamically a subclass that overrides the method.

根據官網的代碼,修改了OrderDao的代碼

package com.along.dao;

import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Repository;

@Repository
public abstract class OrderDao {

	@Lookup("indexDao")
	public abstract IndexDao getIndexDao();

	public void order(){
		System.out.println("orderDao:"+this.hashCode());
		System.out.println("indexDao:"+getIndexDao().hashCode());
	}
}

執行結果

orderDao:488970385
indexDao:1209271652
orderDao:488970385
indexDao:93122545

同樣可以實現。個人理解是去查找@Lookup中指定name的bean,具體爲什麼只需要提供一個抽象方法,沒有方法體就可以實現,不是太懂。

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