单例bean中依赖了原型bean引发的问题

本文探讨了在Spring环境中,当单例bean依赖于原型bean时遇到的问题,即每次调用单例bean的方法时,原型bean并未重新创建。文章列举了两种解决方法:1)实现ApplicationContextAware接口,通过容器获取新的原型bean实例;2)使用@Lookup注解,由容器动态生成子类以重写方法返回新的原型bean。这两种方法均能有效解决该问题,但前者侵入性强,后者依赖于CGLIB库的字节码生成技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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,具体为什么只需要提供一个抽象方法,没有方法体就可以实现,不是太懂,希望懂得大神指点。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值