interface注入及报错分析

本文深入解析Spring框架中接口注入实现类的工作原理,通过源码分析解释为何能直接注入接口并调用实现类方法,以及当存在多个实现类时的错误处理与解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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功能非常强大。

<think>好的,用户现在遇到了“params报错”的问题,需要帮助分析原因和提供解决方案。首先,我需要理解用户的具体情境。params通常出现在编程中,比如函数参数、URL参数、框架中的参数传递等。不同的上下文环境,报错的原因和解决方法也会不同。接下来,我要参考用户提供的四个引用,看看是否有相关的线索。 引用[1]提到的是Xcode编译错误,可能与别名和段有关,可能不直接相关。引用[2]是关于Elasticsearch的索引配置问题,用户将字段的index设置为true解决了问题。引用[3]是服务端action未实现导致的报错,需要删除或实现对应的响应。引用[4]是Vue的props使用错误,涉及HTML标记嵌套问题。这些引用虽然涉及不同的技术场景,但可以归纳出参数配置错误、未实现的逻辑、语法或结构错误等常见原因。 用户的问题是关于params报错,可能涉及参数未定义、类型错误、格式不正确、作用域问题或框架特定配置。需要结合这些可能性,分情况讨论。例如,在JavaScript中,如果函数期望params为对象,但传入字符串,会报错。在Vue或React中,props未正确声明也可能导致错误。另外,URL参数解析错误或API请求参数格式不符也是常见问题。 接下来,我需要整理这些可能性,给出具体的例子和解决方案。比如,检查参数是否存在、验证类型、查看框架文档、作用域是否正确等。同时,结合用户提供的引用中的解决方法,如检查配置(如Elasticsearch的index设置)、实现未完成的action(引用3)、修正HTML结构(引用4)等,这些都可以作为参考。 最后,需要生成相关问题,帮助用户进一步排查问题,比如参数未定义的常见场景、不同框架中的参数处理差异等。确保回答结构清晰,步骤明确,覆盖不同可能的情况,并引导用户根据具体上下文进行排查。</think>### Params报错原因及解决方案分析 #### 一、常见原因分类 1. **参数未定义** 在函数调用或组件使用时,未正确传递必要参数。例如: ```javascript function getUser(params) { console.log(params.id) // 当params未定义时触发报错 } ``` - 典型错误:`Cannot read property 'id' of undefined` 2. **参数类型错误** 接收方预期参数为特定类型(如对象/数组),但实际传入原始值。例如: ```python def calculate_area(params): return params['width'] * params['height'] calculate_area(5) # 传入整数而非字典 ``` 3. **参数格式不符** 在Web开发中常见于: - REST API路径参数:`/user/{id}` 但未传id - 表单参数:缺少必填字段 - GraphQL查询:参数结构不匹配 4. **框架特定限制** 如Vue中props类型验证失败(引用[4]): ```html <child-component :size="'large'"></child-component> <!-- 若size定义为Number类型则报错 --> ``` 5. **作用域问题** 在闭包或异步操作中意外修改参数值: ```javascript function delayedLog(params) { setTimeout(() => { console.log(params) // 当外部params已被修改时输出错误值 }, 1000) } ``` #### 二、解决方案步骤 1. **基础排查** ```bash # 控制台输出参数状态 console.log('Params类型:', typeof params); console.log('Params内容:', JSON.stringify(params)); ``` 2. **类型验证(以TypeScript为例)** ```typescript interface UserParams { id: number; name?: string; } function getUser(params: UserParams) { // 编译器会进行类型检查 } ``` 3. **框架特定处理** - **Vue props验证(引用[4])**: ```javascript props: { size: { type: Number, required: true, validator: (value) => value > 0 } } ``` - **React PropTypes**: ```javascript import PropTypes from 'prop-types'; MyComponent.propTypes = { params: PropTypes.shape({ id: PropTypes.number.isRequired }) }; ``` 4. **API参数处理** Express路由示例: ```javascript app.get('/api/user/:id', (req, res) => { if (!req.params.id) return res.status(400).send('缺少ID参数'); if (isNaN(req.params.id)) return res.status(422).send('ID必须为数字'); }); ``` 5. **防御性编程** ```javascript function safeAccess(params) { const id = params?.data?.user?.id || 0; // 可选链操作符 try { return JSON.parse(params.input); } catch (e) { return null; } } ``` #### 三、典型错误对照表 | 错误现象 | 可能原因 | 解决方案 | |---------|---------|---------| | `undefined` 错误 | 参数未传递或路径错误 | 添加默认值:`params = {}` | | `TypeError` | 类型不匹配 | 使用类型检查工具(TypeScript/JSDoc) | | 框架警告(如Vue的prop验证) | 参数格式不符合声明规范 | 检查props类型定义[^4] | | 网络请求400错误 | API参数缺失或格式错误 | 使用Postman验证请求结构 | | 数据库查询失败 | SQL参数注入问题 | 改用参数化查询:`WHERE id = ?` |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值