Spring 环境 5.0.x ,JDK 8
Bean的作用域
常用的singleton或prototype,其他的都依赖于web环境,这里就先不说明了。下面是spring官网对单例和原型的解释。
众所周知,在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官网找到了解决问题的方法,有两种
第一种 实现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,具体为什么只需要提供一个抽象方法,没有方法体就可以实现,不是太懂,希望懂得大神指点。