spring梳理(二) 基于注解的方式注册bean

我们知道,如果想要将bean交由spring容器管理,就需要首先将bean注册在spring容器中,而bean可以通过xml或者注解的方式进行注册,基于xml的配置一般是通过<bean><context:component-scan>等xml标签进行配置,然后由spring容器扫描xml文件进行注册;基于注解的注册主要是通过几种spring定义的注解进行配置,同样是由spring容器扫描并创建一些bean注册到容器中,spring基于注解的开发已经越来越流行,在spring boot等spring家族的框架中也是大量使用注解来驱动开发。学习spring注解的开发方式,对理解和学习spring boot有很大的帮助。

本文介绍基于注解注册bean的方式:

  • 方式一: 使用@Configuration@Bean结合
@Configuration
public class CarConfig{
    
    @Bean
    public Car car() {
        return new Car();
    }
}

@Configuration注解标识的类自动获得@Component的特性,因为该注解本身也是使用了@Component注解,具体可以查看@Configuration的源码定义,并且该类会作为spring的一个配置类,在创建该类型的bean时,spring会扫描当中所有@Bean注解标注的方法,并自动执行,返回值自动注册在容器中,默认使用方法名作为bean的name。也可以通过提供@Beanvalue值或设置bean的name属性来给bean起名字。

  • 方式二:使用@ComponentScan注解自动注册
@ComponentScan("cn.wyn")
@Configuration
public class BookConfig {
    
}

package cn.wyn;
@Component
public class Book {
    
}

和在xml中配置<context:component-scan base-package="">是类似的,通过在@Component或者相关注解(比如@Controller@Configuration@Service都是)标注的类上使用@ComponentScan注解,spring会根据指定的扫描包路径进行扫描,自动创建所有标有@Component相关注解的类的实例,并将其注册到spring容器中,如果是@Configuration标注的,还会执行其中的@Bean方法。

我们还可以对扫描的类进行过滤,比如扫描排除包含@Controller的类:

@ComponentScan(value = "cn.wyn", 
        excludeFilters = { @ComponentScan.Filter(
        type = FilterType.ANNOTATION,
        classes = {Controller.class})})
@Configuration
public class MyConfig {
}

上面的配置等同于xml配置:

<context:component-scan base-package="cn.wyn">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

比如只扫描包含@Service注解的类:

@ComponentScan(value = "cn.wyn",
        includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,
        classes = Service.class)}, 
        useDefaultFilters = false)
@Configuration
public class MyConfig {
}

上面的配置等同于xml配置:

<context:component-scan base-package="cn.wyn" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

配置include的同时还需要指定useDefaultFilters为false,这样spring就不会自动注册包下所有的Component。

备注: @ComponentScan可以重复使用在同一个类上,用于实现多个扫描,但是这个特性需要使用jdk8及以上版本的jdk,如果使用的jdk版本低于jdk8,可以使用@ComponentScans来实现多个扫描。

我们还可以对扫描的规则进行自定义,通过指定include或者exclude的type值为CUSTOM,指定处理规则的TypeFilter类,我们需要自定义一个实现TypeFilter接口的类,并重写match方法:

class MyTypeFilte implements TypeFilter {

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return false;
    }
}

@Configuration
@ComponentScan(value = "cn.wyn",
        includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM,
        classes = MyTypeFilter.class)}, 
        useDefaultFilters = false)
class MyConfig {
}

说明:通过match方法的参数metadataReader可以获取正在扫描的类的元信息,比如类名,类上的注解信息等,match方法返回值如果是true,则是匹配。返回true的情况下,如果是include,则是注册,如果是exclude则是忽略。返回false则反之。

  • 方式三: 使用@Import注解导入某个类注册到spring容器中

@Configuration
@Import({Car.class, Book.class})
class MyConfig {
}

通过在配置类上标注@Import注解,可以快速创建某个类的实例,并导入到spring容器中。

方式三扩展一: @Import 使用ImportSelector 批量导入:

具体方法是指定@Import的值为一个实现了ImportSelector接口的类,该类重写selectImports方法,selectImports方法返回值为一个String数组,这个数组包含要导入的全限定类名。使用了ImportSelector不会将ImportSelector实现类导入,只会将selectImports方法返回的数组指定的类导入

class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"java.lang.String"};
    }
}

@Configuration
@Import({MyImportSelector.class})
class BeanConfig {

}

方式三扩展二:@Import 使用ImportBeanDefinitionRegistrar

自定义一个ImportBeanDefinitionRegistrar类,实现ImportBeanDefinitionRegistrar接口,重写registerBeanDefinitions方法,通过参数registry可以注册bean,比如:

class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        
        //创建一个BeanDefinition对象
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Book.class);
        //注册一个名为xixi的BeanDefinition
        registry.registerBeanDefinition("xixi", rootBeanDefinition);
    }
}


@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
class BeanConfig {

}

同样是使用@Import注解将ImportBeanDefinitionRegistrar导入,同样是只会将registerBeanDefinitions方法中注册的bean注册不会将ImportDefinitionRegistrar这个类注册进来

  • 方式四:使用FactoryBean(工厂Bean)注册bean

实现FactoryBean接口,实现以下三个方法:

方法名 作用
getObject 通过这个方法获得bean
getObjectType 通过这个方法获得Bean的Class对象
isSingleton 通过这个方法来指定bean的作用域是否为单例

示例:

class MyFactoryBean implements FactoryBean {


    //获取bean的具体方法
    @Override
    public Object getObject() throws Exception {
        return new String("哈哈哈");
    }

    //获取bean的Class类型
    @Override
    public Class<?> getObjectType() {
        return String.class;
    }

    //根据这个方法来指定bean是否单例
    @Override
    public boolean isSingleton() {
        return true;
    }
}
@Configuration
class BeanConfig {

    @Bean("beanName")
    public MyFactoryBean createBean() {
        return new MyFactoryBean();
    }
}

spring判断@Bean注解的方法的返回值是一个工厂Bean,会执行工厂bean的getObject方法获得一个实例,并注册到容器中,如果是单例,则只注册一次。而不是将FactoryBean的实现类注册进来。如果想要获得工厂bean本身这个实例,可以在获取bean的时候指定的bean name前加上“&”前缀,如context.getBean("&bean")

设置bean的作用域--注解配置方式

bean的作用域有:

  • singleton : 单例,整个应用中只存在一个实例bean
  • prototype : 与单例相对,每次getBean都会重新生成一个Bean。
  • request : web环境下,每个请求都会创建一个bean,在一次请求中只存在一个Bean,不同request的bean不同
  • session : web环境下,session生命周期下,获取的是同一个bean

默认情况下是singleton单实例,可以通过以下方式来指定Bean的作用域。

@Configuration
class BeanConfig {

    @Bean("book")
    @Scope("prototype")
    public Book createBean() {
        return new Book();
    }
}


bean 懒加载 --注解配置方式

默认情况下,所有单实例bean都会在创建spring容器的时候创建,如果在bean第一次使用的时候创建,我们称为懒加载

配置很简单,在创建bean的方法上添加@Lazy注解即可

@Configuration
class BeanConfig {

    @Bean("book")
    @Lazy
    public Book createBean() {
        return new Book();
    }
}

按照条件注册Bean

我们可以通过某些条件,来选择是否注册Bean,通过@Condition注解来实现。

@Configuration
class BeanConfig {

    @Bean("book")
    @Conditional(MyCondition.class)
    public Book createBean() {
        return new Book();
    }
}

//实现Condition接口,并重写matches方法,根据该方法返回的布尔值来决定是否注册Bean
class MyCondition implements Condition {


    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
        /**
        * 根据环境变量是否存在my.env=hello的属性来决定是否创建,
        * 可以通过启动参数指定-Dmy.env=hello来测试。
        **/
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("my.env");
        if ("hello".equals(property)) {
            return true;
        }
        return false;
    }
}


//测试
public class MainTest {

    @Test
    public void test() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
    }
}

//测试用到的类
class Book {
    public Book() {
        System.out.println("init book ...");
    }
}

@Conditional注解可以添加在方法上也可以添加在类上,放在类上是对类中所有@Bean方法统一设置。

Profile 的使用

日常开发中,我们可能需要根据不同的环境来注册一套不同的Bean,比如:我们生产环境、测试环境、开发环境会使用不同的数据源。通过Profile配置,就可以指定该Bean是在某个Profile被激活时才会注册到spring容器中,这与maven中的profile是一个道理。

如下:

    @Profile("dev")
    public Book book1(){
        return new Book();
    }

    @Profile("test") Book book2() {
        return new Book();
    }

    @Profile("prod") Book book3() {
        return new Book();
    }

通过指定环境变量,或者jvm启动参数:-Dspring.profiles.active=dev都可以来激活profile,也可以在代码中激活profile,如:

public class MainTest {

    @Test
    public void test() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        
        //设置激活的profile
        context.getEnvironment().setActiveProfiles("dev");
        context.register(BeanConfig.class);
        context.refresh();
    }
}


@Configuration
class BeanConfig {


    @Bean
    public Book book(){
        return new Book();
    }

    @Bean
    @Profile("dev")
    public Book book1(){
        return new Book();
    }

    @Bean
    @Profile("test") Book book2() {
        return new Book();
    }

    @Bean
    @Profile("prod") Book book3() {
        return new Book();
    }
}

@Profile注解同样可以写在配置类上,整个配置类的所有配置会在指定profile下才激活。

转载请注明出处
作者:Coder_Ring
原文链接:https://www.jianshu.com/p/bdca18850673

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