SpringBean加载机制-注入Bean

SpringBean加载机制 - 注入Bean

SpringBean注入方式.jpg

两种IoC容器

  1. xml配置文件加载的容器;
  2. 通过注解加载的容器;

xml容器用如下方式获得:

ApplicationContext applicationContext = 
new ClassPathXmlApplicationContext("beans.xml");

通过XML添加的所有组件只会都在该容器中,用注解容器是拿不到对应的实例对象。同理,用注解添加的组件也只会在注解容器中。

注解容器通过如下方式获得:

AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(
                MainConfig.class,
                OtherConfig.class, 
                ImportConfig.class);

如果想精确的控制可以调用AnnotationConfigApplicationContext的无参构造器,然后手动调用里面的方法;如下:

AnnotationConfigApplicationContext context = 
                new AnnotationConfigApplicationContext();
        context.register(MainConfig.class);
        context.register(MainConfig.class);
        context.register(MainConfig.class);
        context.refresh();

分步调用可以在register()执行完后进行额外的处理在调用refresh()。后面说到这些额外的处理。

注入组件的四种方式

  1. @Configuration + @Bean
    导入的第三方包里面的组件。因为我们无法直接在三方代码上加@Component。
  2. @ComponentScans + @ComponentScan +@Component
    直接在代码上添加如上注解,导入自己写的类。
  3. @Import 快速给容器中导入一个组件
    a. @Import(WantImportClass.class);容器中自动注册这个组件,id默认是全类名
    b. 协议ImportSelector:返回需要导入的组件的全类名数组;
    c. 协议ImportBeanDefinitionRegistrar:手动注册bean到容器中
  4. 使用Spring提供的 FactoryBean(工厂Bean);
    a. 默认获取到的是工厂bean调用getObject创建的对象
    b. 要获取工厂Bean本身,我们需要给id前面加一个&colorFactoryBean

方式一 @Configuration @Bean

该方式主要用来导入三方的类作为Bean添加IoC容器中。因为我们自己的类通常是在代码上添加@Component@ComponentScan直接把类注入到IoC容器中。使用时通常有几个Config类,用来描述Bean组件。注意,需要手动调用AnnotationConfigApplicationContextregister方法将配置类加载到IoC容器中,当然调用Context的有参构造器也可以。

@Configuration

@Configuration继承自@Component,说明用该注解的修饰类本身也会作为组件添加的IoC容器中。其作用范围是@Target({ElementType.TYPE}),也就是不能修饰方法上,通常用于修饰类,并在该配置类中声明要加载的bean。使用如下:

@Configuration
public class MainConfig {
    @Bean(value = "tom")
    public Person person01 (){
        return person02();
    }

    @Bean(value = "jack")
    public Person person02 (){
        return new Person("jack",20);
    }}

注意,如果类上没有@Configuration,Bean会议'lite Mode'的模式加载到IoC容器中。在lite Modetomjack是不同的实例对象。有@Configuration注解,tomjack则是同一个实例对象。
详见官方文档

@Bean

@Bean的作用范围是@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}),通常情况下用于修饰有对象返回值的方法。
@Bean常用三个属性:

  1. value或name;
    使用:@Bean("person")。如果value为空,默认用方法名作为id。
  2. initMethod;
  3. destroyMethod;

initMethoddestroyMethod这两个方法是用来指定bean的初始化之后和销毁之前调用的方法。会在Bean的生命周期在讨论。

方式二 @ComponentScans + @ComponentScan +@Component

该方式主要是将自己代码中的类注入成bean组件。

@ComponentScans + @ComponentScan

扫描制定路径下的所有类,将带有@Component注解的类注入成Bean组件。这两个注解的使用范围都是ElementType.TYPE,通常都是加在类上,Spring建议同时加上@Configuration注解使用。示例:

//告诉Spring这是一个配置类
@Configuration  
@ComponentScans(
        value = {
                @ComponentScan(value={
                "com.springDemo.Bean",
                "com.springDemo.Controller"}
                includeFilters = {
            @Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
                        @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
                        @Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
                },
                useDefaultFilters = false)
        }
)
public class MainConfig {
    // other bean ...
}

@ComponentScans的value是一个@Component的数组,用以描述多个组件扫描的规则。
@ComponentScan主要有以下四个属性:

  1. value : 指定要扫描的包,值是String数组;
  2. excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件;
  3. includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件;
  4. useDefaultFilters

默认只指定value的值,会将指定包中所有带@Component的类注入成Bean。如果不想用默认的扫描规则需要useDefaultFilters = false。同时指定excludeFiltersincludeFilters

@Filter

过滤规则使用@Filter注解。该注解用以详细描述过滤的规则,有以下5个值。常用的是FilterType.CUSTOM。这里不对过滤规则多详细介绍。

  1. FilterType.ANNOTATION:按照注解
  2. FilterType.ASSIGNABLE_TYPE:按照给定的类型;
  3. FilterType.ASPECTJ:使用ASPECTJ表达式
  4. FilterType.REGEX:使用正则指定
  5. FilterType.CUSTOM:使用自定义规则

@Component

该注解的范围是ElementType.TYPE,通常只作用在类上。使用如下:

// @Component("customAnimalName")
@Component
public class Animal {
    // ... 
}

通常不需要指定Value的值,bean的id默认是类名。value值,用以改变bean id。

方式三 @Import

使用Import管理配置类

假设有N个配置类,我们可以通过在某一个配置类中用@Import其他所有配置类,这样在AnnotationConfigApplicationContext注册时就只要注册一个配置类即可,不需要记住那么多配置类。
使用如下:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

使用Import直接导入类作为Bean

使用如下:

//@Import导入组件,id默认是组件的全类名
@Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MainConfig {}

这种方式不太建议,因为直接用@Import注册组件很难对组件做自定义设置,比如初始化方法,生命周期的管理,是单例还是多实例组件。

ImportSelector

实现接口ImportSelector,在实现类中返回要添加Bean的全类名,就会把类注入到Ioc容器中。在selectImports方法中可以做注入组件的逻辑判断,但是不能对组件的生命周期做业务处理。
示例:

//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
    //返回值,就是到导入到容器中的组件全类名
    //AnnotationMetadata:当前标注@Import注解的类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // TODO Auto-generated method stub
        //importingClassMetadata
        //方法不要返回null值
        return new String[]{"com.spring.bean.Blue","com.spring.bean.Yellow"};
    }
}
// 导入自定义组件
@Configuration
@Import({MyImportSelector.class})
public class MainConfig {
// ...
}

ImportBeanDefinitionRegistrar

实现接口ImportBeanDefinitionRegistrar,在registerBeanDefinitions方法中拿到registry对象,调用registry.registerBeanDefinition()方法将Bean对象注入到IoC容器中。这种方式比ImportSelector方法更灵活一点,可以对Bean注入做更灵活的控制。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * AnnotationMetadata:当前类的注解信息
     * BeanDefinitionRegistry:BeanDefinition注册类;
     *      把所有需要添加到容器中的bean;调用
     *      BeanDefinitionRegistry.registerBeanDefinition手工注册进来
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        
        boolean definition = registry.containsBeanDefinition("com.spring.bean.Red");
        boolean definition2 = registry.containsBeanDefinition("com.spring.bean.Blue");
        if(definition && definition2){
            //指定Bean定义信息;(Bean的类型,Bean。。。)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            //注册一个Bean,指定bean名
            registry.registerBeanDefinition("rainBow", beanDefinition);
        }
    }
}
// 导入自定义组件
@Configuration
@Import({MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MainConfig {
// ...
}

注意虽然Import了MyImportBeanDefinitionRegistrarMyImportSelector,但这两个类的Bean组件。

方式四 FactoryBean工厂注入Bean

如果一个Bean特别复杂,需要做很多处理,则可以用FactoryBean来完成。该接口的实现类的不同方法共同定义了一个Bean对象。使用有两步:

  1. 实现FactoryBean
  2. 使用@Bean注入;

使用如下:

  1. 实现FactoryBean
//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean {
    //返回一个Color对象,这个对象会添加到容器中
    @Override
    public Color getObject() throws Exception {
        // TODO Auto-generated method stub
        System.out.println("ColorFactoryBean...getObject...");
        return new Color();
    }

    @Override
    public Class getObjectType() {
        // TODO Auto-generated method stub
        return Color.class;
    }

    //是单例?
    //true:这个bean是单实例,在容器中保存一份
    //false:多实例,每次获取都会创建一个新的bean;
    @Override
    public boolean isSingleton() {
        // TODO Auto-generated method stub
        return false;
    }
}
  1. 注入Bean
@Configuration
public class MainConfig
    @Bean
    public ColorFactoryBean colorFactoryBean(){
        return new ColorFactoryBean();
    }
}

这种方式即注入了Color Bean对象,同时也注入了ColorFactoryBean对象。两个Bean对象获取如下:

// 获取 Color Bean
Object bean1 = applicationContext.getBean("colorFactoryBean");
// 获取 ColorFactoryBean
Object bean2 = applicationContext.getBean("&colorFactoryBean");

你可能感兴趣的:(SpringBean加载机制-注入Bean)