多线程无法注入Bean原因:
在spring中,如果需要在异步线程中注入bean,会发现bean是空的情况。原因据说是spring bean 出于线程安全考虑,不得注入bean至线程类(Runnable接口的派生类)。
spring中Bean的注入方式:
1、实现Annotation接口的注解形式 @AutoWired、@Resource。前提是对应的类要带上组件(如@Component)
2、通过在spring配置文件中定义Bean属性(这里有一些细节问题后面会讲到)
解决方案:
Spring API 中有ApplicationContextAware 这个接口,实现了这个接口的类,可以在IOC容器初始化完成后获得容器,从而可以获得容器中所有的bean。(相当于创建了另一个容器把旧的容器所存的Bean全部赋值给新的容器,这样我们可以灵活操作Bean),把实现该接口的类注入到IOC容器中,然后在创建线程的派生类中,需要该"异步线程Bean”实现对应的逻辑把它写在run方法里。
第一步:
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext=applicationContext;
System.out.println("applicationContext---->"+applicationContext);
}
public static <T> T getBean(Class<T> clazz){
return applicationContext.getBean(clazz);
}
public static <T> T getBean(String name) {
if (applicationContext==null) {
System.out.println("applicationContext为空");
}
return (T) applicationContext.getBean(name);
}
}
第二步:
注意1、这里如果用注解注入ApplicationContextHolder对象,千万不能写道run方法里。否则会抛NullPointException,若注入ApplicationContextHolder对象是以配置属性Bean,scope不能选择prototype类型,只能选singleton类型,否则也会抛出NullPointException
注意2、这里的MyService的接口以及方法是作者自己建,读者可以尝试不同的类来注入。
public class DealThreadTask implements Runnable {
@Autowired
private ApplicationContextHolder holder;
@Override
public void run () {
MyService myService = holder.getBean("myService");
// MyService myService = holder.getBean(MyService.class);
System.out.println("myService-->"+myService);
myService.say();
}
}
第三步:
创建一个测试类(不带junit注解),用Main方法来执行。
这里spring.xml是作者自己偷懒没创包,直接在resources根目录文件下创建
public class TestMain {
public static void main (String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
DealThreadTask task = new DealThreadTask();
new Thread(task).start();
}
}
注意!这里不选择junit测试的原因是因为junit是不会等待异步线程执行完然后结束,而是junit自己本线程的代码执行完就结束了。
由于代码中设计到数据库操作,因此如果简单的用junit进行测试,可能的结果是测试完成,但是数据库操作还没有进行。如果一定要用junit进行测试,可以用其他的手段,比如thread的join操作,或者在junit的测试方法中等待键盘输入才结束,这些都是为了让异步线程执行完后才结束junit测试。
网上资料错综复杂,作者踩了好多坑,所以很多易错的细节也讲出来。
希望能帮到你