Spring为IOC容器注入Bean的方式

      Spring提供的主要功能就是对于bean的管理,提供了多种方式可以向容器中注入bean,下面来总结一下向IOC容器注入bean的几种方式(以下注入bean的方式都是基于注解完成的):

      1、@ComponentScan+@Component方式

       @ComponentScan可以扫描指定包下的类,如果该包下的类标有@Component、@Service、@Repository、@Controller、@RestController和@Configuration,都会被注入到IOC容器中,这种方式也是我们写代码最常用的,一般针对自己写的类。

       我们写的配置类,在上面标有@ComponentScan,制定扫描的的包,这时被扫描的类需要提供无参构造方法,不然会报错。

@ComponentScan(basePackages = {"it.cast.componentScan"})
public class Config {

}

       这个person类在it.cast.componentScan包下面,会被扫描并注入到容器中

package it.cast.componentScan.config;

import org.springframework.stereotype.Component;

//使用这种方式需要替换无参构造的方法,因为spring是调用无参构造方法创建类的
@Component
public class Person {
    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

}

        2、使用@Configuration + @Bean注解

        该方法一般用于导入的第三方包里面的组件,因为第三方包里面没有添加Spring相关的注解,所以使用第一种方式就不行了。

@Configuration
public class Config1 {

    /**
     * 使用@Bean方式向容器注入bean,适用于导入的第三方包里面的组件
     * 在@Bean后面不跟其他属性时,bean的名称默认使用方法名
     * 在@Bean("person"), 如指定方法名,则使用定制的方法名
     * 在@bean中还有initMethod属性和destroyMethod属性,可以指定初始话方法和销毁方法
     */

    @Bean("person")
    public Person person(){
        return new Person();
    }
}

      3、使用@import注解

         该方法注入的bean的id默认是组件的全类名 ,使用@import就是将类注入到容器中,如果要注入的类没有被标注@Component也能被注入进来,一般注入的都是标注了@Configuration的配置类。

/**
 * 该方式会将Bike类注入到容器中
 *
 * */
@Configuration
@Import({Bike.class})
public class Config1 {

    @Bean("person")
    public Person person(){
        return new Person();
    }
}

public class Bike {

}

      4.实现ImportSelector接口来向容器注入bean

      注意:使用这种方式的话返回值不能为null,不然会出现空指针异常

/**
*使用@Import注解,是将MyImportSelector类注入到IOC容器中,至于它是不是ImportSelector的实现类,
*这个@Import注解是不进行判断的,在注入这个bean后,有其他的组件会找到ImportSelector的实现类,并调
*用selectImports方法进行注册bean
* */

@Configuration
@Import({MyImportSelector.class})
public class ImportConfig {

}

class MyImportSelector implements ImportSelector {
    /**
     *返回值,就是到导入到容器中的组件全类名,返回值时一个字符串数组,可以导入多个bean
     *AnnotationMetadata:当前标注@Import注解的类的所有注解信息
     * */
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"it.cast.Bike"};
    }
}

       5.实现ImportBeanDefinitionRegistrar接口来向容器注入bean

       这里的代码来自于DataSourceInitializedPublisher$Registrar类

/**
 * {@link ImportBeanDefinitionRegistrar} to register the
 * {@link DataSourceInitializedPublisher} without causing early bean instantiation
 * issues.
 */
static class Registrar implements ImportBeanDefinitionRegistrar {

    private static final String BEAN_NAME = "dataSourceInitializedPublisher";

      /**
       * AnnotationMetadata:当前类的注解信息
       * BeanDefinitionRegistry:BeanDefinition注册类;
       *      把所有需要添加到容器中的bean;调用
       *      BeanDefinitionRegistry.registerBeanDefinition手工注册进来
       */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition(BEAN_NAME)) {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DataSourceInitializedPublisher.class);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            // We don't need this one to be post processed otherwise it can cause a
            // cascade of bean instantiation that we would rather avoid.
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
        }
    }

}

        该方法可以有选择性的注入bean,传递的参数可以获取到IOC容器中的bean定义,使用这样方式比较灵活,在查看Spring源码时,大量使用了这种方式。

       下面这段代码是我在没学习Spring源码前错误的理解:

     只要把ImportBeanDefinitionRegistrar的实现类放到容器后,Spring会在合适的使用收集到所有的ImportBeanDefinitionRegistrar的实现类,然后挨个调用registerBeanDefinitions方法。(这句话的错误的)

      正确的理解:

      ImportBeanDefinitionRegistrar的实现类,必须是被@Import进行导入的,如@Import(Registrar .class),如果是我们不使用@Import注解导入Registrar类,而是使用一个@Component注解,将Registrar类通过扫描的方式放入到容器中,那么registerBeanDefinitions方法就不会被执行。

@Import导入的原理:

       处理这个@Import是在ConfigurationClassPostProcessor类中进行的,ConfigurationClassPostProcessor类会扫描出所有的对象,封装成beanDefinition对象,然后判断是否对象中是否加了@Import注解,加了的话判断是否为ImportBeanDefinitionRegistrar的实现类,或者是ImportBeanDefinitionRegistrar的实现类,如果是,则执行接口对象的方法(这只是见简单的说一下,其实步骤复杂的多,会有递归调用什么的,这里简单理解一下就行)

      所以,在看Spring源码前的理解是错误的,ImportBeanDefinitionRegistrar和ImportBeanDefinitionRegistrar只能在@Import中实现,而且是一个类一个类的判断有没有@Import注解,不是找到所有的之后再进行调用,后面有时间我会写一篇关于处理@import的博客。

        6.实现FactoryBean接口来向容器注入bean

        使用Spring提供的 FactoryBean(工厂Bean),默认获取到的是工厂bean调用getObject创建的对象,要获取工厂Bean本身,我们需要给id前面加一个&

@Configuration
public class FactoryBeanConfig {
    /**
     * 在容器里面注入FactoryBeanTest
     * 在获取factoryBeanTest名称的bean时,得到的是Pig类型的bean,如想要得到FactoryBeanTest类型的bean,需要使用
     * AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FactoryBeanConfig.class);
     * String[] beanDefinitionNames = context.getBeanDefinitionNames("&factoryBeanTest");  这种获取的是FactoryBeanTest类型的bean
     * String[] beanDefinitionNames = context.getBeanDefinitionNames("factoryBeanTest");  这种获取的是Pig类型的bean
     * */
    @Bean
    public FactoryBeanTest factoryBeanTest(){
        return new FactoryBeanTest();
    }
}

public class FactoryBeanTest implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        return new Pig();
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public Class<?> getObjectType() {
        return Pig.class;
    }
}
发布了225 篇原创文章 · 获赞 30 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_35634181/article/details/104044528