这一切的一切都源于一个NPE(NULL POINTER EXCEPTION)。
有这样几个类,
@Service
public class TestService{
@autowired
private Testdao testDao;
public void aaa(){
testDao.query();
}
}
@Repository
public class TestDao{}
public class TestUtil{
public static void ttt(){
TestService ts = new TestService();
ts.aaa();
}
}
当调用util时,就会报dao的空指针,问题描述完了。
下面有几点分析(是我经过反复测试得出来的):
1. 为什么会空指针,难道没有注入进去?
答案是已经注入进去了。
2. 为什么已经注入进去了?
这要从服务器启动和spring扫描包开始说起,在服务器启动的时候,spring会将扫描包下的类得到一个实例bean, 然后交由spring统一管理; 比如, 扫描了service层,然后有@Service, 那么spring容器中就会有一个bean id="testService", 你 getBean的时候就能得到这个bean实例, dao层 controller层同理;
那么@autowired这个注解,spring是什么时候发现的呢? 其实就是在扫描service的过程中。 扫描service,首先会得到实例,(这个时候如果TestService中构造器有打印语句,就会打印出来,说明是实例化了),然后接着就会识别到@Autowired注解,
这时会把之前扫描dao得到的实例注入到这个成员变量里面(也就是赋值给private Testdao testDao;)。
这时会涉及到另一个问题, 那么扫描dao是不是必须在扫描service之前?
答案是不一定。 因为我们在配置扫描包的时候有两个地方可以配置: 第一个地方是在spring.xml中, 第二是spring-mvc.xml中;而在这两个地方配置的话,情况是不同的;
在spring中配置的时候,必须按照顺序来,即先扫描dao, 再扫描service, 否则,在扫描service的时候,就会找不到dao对应的实例, spring就会报错,无法完成注入,应至少有一个dao对应的实例;
在spring-mvc配置的时候就不需要顺序了,扫描controller、service、dao可以不按照顺序来,是不会报错的;
3. 既然已经注入进去了,那么为什么还是null?
方式一: TestService ts = new TestService(); ts.aaa();
方式二:
ApplicationContext ac = new FileSystemXmlApplicationContext("classpath:conf/spring.xml");
TestService ts = ac.getBean("testService"); ts.aaa();
测试之后会发现方式二是不会报空指针的。
我猜, @Service的testService 和@Autowired的 testDao是有联系的。
扫描service包的时候,spring发现@Service,会产生一个 id 为testService 的bean; 几乎同时会识别到@Autowired,然后去容器中取 id为 testDao的bean,赋值给成员变量@autowired private Testdao testDao; 关键的地方在于这个被赋值的成员变量testDao,是属于实例 id 为testService 的bean的; 通过new TestService()得到的实例下的成员变量仍然是空;
所以,关键就在于两者的实例是不同的。所以在工具类中使用service的正确打开方式应该是用getBean的方式。
4. 扫描包可以放在两个地方,那么怎么放比较好?
个人认为,直接放在spring-mvc.xml就可以。 new FileSystemXmlApplicationContext("classpath:conf/spring-mvc.xml");
也是可以取到bean的。 这样说来,spring.xml中就什么都不放了。
以上内容,个人理解,仅供参考,如有雷同,纯属巧合。 欢迎批评与指正,互相学习与进步。