Spring组件注入注意事项之一

序言

最近新同事在写完代码时,使用Spring框架实现的接口一直报Failed to load ApplicationContext错误。找我帮忙看看,后面就发现他写代码时犯了一些常见的基本错误。

个人建议:使用之前多了解一点基本原理,即使发现了问题也可以更快的定位出来。

1、照葫芦画瓢时注入组件没有使用@Autowired或@Resource注解。

2、接口实现及接口组件重名。

依赖注入和控制反转

如果对Spring框架有了解的话,应该会知道DI(Dependency Inject)依赖注入和IOC(Inversion of Control)控制反转。

  • 控制反转:在面向对象传统编程方式中,获取对象的方式通常是用new关键字主动创建一个对象。而Spring容器(IOC容器)初始化、装配及管理的对象叫做bean,对象的生成则不需要new了,而是通过Spring的配置来自动装配生成单例(scope=singleton)或原型(scope=prototype)对象。
  • 依赖注入:IOC容器在运行期间动态地将某种依赖关系注入到对象之中,如对象A 注入(赋值)给对象B的成员变量。

Spring框架的主要功能是通过其核心容器来实现的。Spring框架提供的两种核心容器分别是BeanFactory和ApplicationContext。

常见的注入组件的方式是通过spring.xml配置文件进行注入组件bean,如下,更多参数参考文末链接1。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bean" class="com.xxx.BeanName"></bean>
</beans>

依赖注入主要有下面几种方式,

  1. 基于set方法的依赖注入
  2. 基于有参构造函数的依赖注入
  3. 基于工厂方法的依赖注入
  4. 基于注解的依赖注入

基于set方法的依赖注入

通过set方法注入,在<bean ...></bean>加上下面的配置,前提是BeanName类中有field-name成员变量的setName方法。

<property name = "field_name" value = "field_value"></property>

基于有参构造函数的依赖注入

通过BeanName类的有参构造方法,并在配置中添加构造方法的参数和类型。

<constructor-arg value="name" type="java.lang.Demo"></constructor-arg>

基于工厂方法的依赖注入

还需要写一个BeanNameFactory的工厂方法,例如,

public class BeanNameFactory{
    public static BeanName getDemoBean(String name){
        return new BeanName(name);
    }
}

然后在配置中添加工厂类及工厂方法的配置参数,

    <bean id="beanNameFactory" class="com.xxx.BeanNameFactory"
		factory-method="getDemoBean">
		<constructor-arg value="test"></constructor-arg>
	</bean>

然后在使用时通过应用上下文拿到对应的bean,

        //获取spring ioc容器
		ApplicationContext ctx=new ClassPathXmlApplicationContext("spring.xml");
		
		//获取bean,org.springframework.context.ApplicationContext接口getBean方法可以通过类名获取对应的bean
		BeanName bean=(BeanName)ctx.getBean("beanNameFactory");

基于注解的依赖注入

最后一种方式是最为常见的方式,通过注解@Autowired和@Resource注入组件bean,

如下示例所示,注入了一个门面类,

@Service
@Slf4j
public class LogicImpl extends implements Logic {
    @Resource
    private Facade facade;

    public Message<Long> insert(ReqDto reqDto) {
        // doing something
    }
}

Spring的包路径扫描

在spring.xml文件中加上下面配置,可以在com.xxx.packagePath找到所有加上了@RestController、@Controller、@Service、 @Repository、 @Component等注解的类,将这些类解析成一个个的BeanDefinition

<context:component-scan base-package="com.xxx.packagePath"/>

component-scan扫包的标签是通过ComponentScanBeanDefinitionParser解析类进行解析的。

SpringBoot的包路径扫描

SpringBoot在Spring的基础上大部分采用注解的方式进行配置,方式如下,@ComponentScan(value = {"com.xxx.packagePath"})。

ComponentScanBeanDefinitionParser解析类中的parse方法,利用ClassPathBeanDefinitionScanner.doScan方法扫描包路径下的所有组件,并将他们注入到BeanDefinitionHolder中去。

服务启动后,也可以在org.springframework.context.support.AbstractApplicationContext的refresh方法中打断点,查看BeanDefinitionMap(beanFactory对象)已加载的bean。

问题定位

1、如果作为新人了解了上面的基本知识,那么就不那么容器在依赖注入时漏掉注解,没加注解就相当于在类中定义了一个空对象,再去调用空对象的非静态方法肯定会报错。

2、不同包路径下的相同接口作为组件交给spring管理时注意接口的实现类名不能相同,否则会抛出org.springframework.context.annotation.ConflictingBeanDefinitionException异常。

异常详细信息如下,

Annotation-specified bean name 'xxxImpl' for bean class [com.test.xxx] conflicts with existing, non-compatible bean definition of same name and class[com.test.xxx]

参考链接:

1、基本原理 - 容器和bean - [ Spring中文手册 ] - 在线原生手册 - php中文网

猜你喜欢

转载自blog.csdn.net/zkkzpp258/article/details/126653641