interface注入及報錯分析
一個小case
上面錯誤原因我想大家開發中都遇到過,大致錯誤原因是注入bean時,spring找到2個例項userServiceImplTest、userServiceImpl,無法確認到底使用哪個。問題出在這,原因是什麼呢,在說明前,看下面的程式碼:
@RestController public class OkController { @Autowired UserService userService; @ResponseBody @GetMapping(value = "/ok") public String ok(){ UserInfoEntity userInfoEntity = userService.selectByTel("lioswang"); } //此時專案中UserService的實現類只有UserServiceImpl @Service public class UserServiceImpl implements UserService{ @Override public UserInfoEntity selectByTel(String tel) { return null; } }
在OkController中為什麼可以直接注入介面,當專案啟動時,呼叫了UserServiceImpl類中的selectByTel方法,由於在OkController中引用了UserService,所以鎖定在OkController初始化時Spring到底幹了些什麼,根據之前原始碼分析的經驗,在org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
方法中打上條件斷點,首先看方法org.springframework.beans.factory.support.AbstractBeanFactory#createBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
,再跟進去方法
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
,繼續跟進去org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues
org.springframework.beans.factory.annotation.InjectionMetadata#inject
,跟進去org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
,繼續跟org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
,其中程式碼片段:
//獲取介面的依賴
result = doResolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);
呼叫了
org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
,該方法的程式碼片段
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
繼續跟
org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates
,其中獲取UserService所有的實現類:
//獲取到UserService的實現類
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());
//獲取到實現類後,並初始化,儲存在Map<String, Object>
result.put(candidateName, getBean(candidateName));
再看org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
方法中程式碼片段:
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(type, "", descriptor);
}
return null;
}
//獲取匹配到的bean數大於1時的邏輯處理
if (matchingBeans.size() > 1) {
String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (primaryBeanName == null) {
throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(primaryBeanName);
}
return matchingBeans.get(primaryBeanName);
}
// We have exactly one match.
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
if (autowiredBeanNames != null) {
autowiredBeanNames.add(entry.getKey());
}
return entry.getValue();
}
由於目前專案中UserService的實現類只有UserServiceImpl,所以最終獲取到的只有一個。
再回到org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
中,已經找到UserService的實現類,所以執行:
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
即把UserServiceImpl的例項設定到屬性UserService中。
所以當再OkController中呼叫UserService的selectByTel方法,其實呼叫的是UserServiceImpl的selectByTel方法。
報錯
上面分析那麼多,其實就是為了說明我們注入介面時,為什麼會呼叫實現類的方法。為了報錯,很簡單,再寫一個類實現UserService介面即可,OkController中不需要修改,其實由上面的分析知道,報錯的就是上面的這段程式碼:
if (matchingBeans.size() > 1) {
String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (primaryBeanName == null) {
throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(primaryBeanName);
}
return matchingBeans.get(primaryBeanName);
}
也就是在UserService的實現類中找到多個bean例項,這個明顯是錯誤的,
錯誤解決
如何解決這個問題呢,很簡單:
@Autowired
@Qualifier("userServiceImpl")
UserService userService;
或
@Resource(name = "userServiceImpl")
UserService userService;
因為在org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates
方法中的
for (String candidateName : candidateNames) {
if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
result.put(candidateName, getBean(candidateName));
}
}
會根據註解過濾bean,所以加上上面的註解後會解決錯誤,具體程式碼就不分析了,感興趣的同學可打斷點除錯。
思考拓展
@RestController
public class OkController {
@Autowired
Map<String,UserService> userServiceMap;
@ResponseBody
@GetMapping(value = "/ok")
public String ok(){
...
}
若OkController中程式碼修改如上,專案啟動後,發現沒有報錯,而且userServiceMap中有兩個key-value元素,無疑是UserServiceImpl、UserServiceImplTest,我想原因不難看出,org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates
返回了Map<String, Object>,其值和userServiceMap相同,不難看出spring功能非常強大。