1. 程式人生 > >interface注入及報錯分析

interface注入及報錯分析

https://note.youdao.com/yws/api/personal/file/WEBfdaf58ac1dc8c2a883297003a4db402c?method=download&shareKey=fffbd764872b91bae807de4755fb5bc7

一個小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.AbstractBeanFactory#createBean,繼續跟進方法org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,
再跟進去方法org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean,繼續跟進去org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues
,在方法中找到OkController注入的元資料UserService,呼叫了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功能非常強大。