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> {
    //返回一个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");
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352