前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring5以来注册Bean的各种姿势,特别最后的纯编码注册值得尝试

Spring5以来注册Bean的各种姿势,特别最后的纯编码注册值得尝试

作者头像
ImportSource
发布2018-04-03 13:14:48
2.6K0
发布2018-04-03 13:14:48
举报
文章被收录于专栏:ImportSource

各位好,今天我们的内容是有关Spring 5以来有关注册bean的几种方式。前面两三个是比较常用的方式,最后两种是只有在特殊的场合下才会被用到和想到。我们会分别介绍和演示以下五种方式:

1、@Bean方式。

2、@Component方式。

3、@Bean和@Component混用。

4、BeanDefinitionRegistryPostProcessor方式。

5、ApplicationContextInitializer方式。

铺垫

接下来我们详细的展示具体的细节。

首先我们开始新建一个项目。

这里我们通过spring initializr新建一个spring boot 应用程序。

这里我使用diff.wiki这个国内的initializr来新建项目,你也可以通过spring官方的initializr来新建。

像上面一样填写好后,点击“Generate Project”,然后下载。

五种方式

一、@Bean方式

我们打开main类。注释掉下面两行代码。

代码语言:javascript
复制
//@SpringBootApplication
public class Spring5ConfigApplication {
   public static void main(String[] args) {
      //SpringApplication.run(Spring5ConfigApplication.class, args);

   }
}

然后我们新建两个service类

代码语言:javascript
复制
class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}

class BarService{

}

然后再在main 中实例化一个appplication context,一个annotated的context。

代码语言:javascript
复制
public static void main(String[] args) {
   //SpringApplication.run(Spring5ConfigApplication.class, args);

       ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}

然后再给application类添加一个configuration注解。

代码语言:javascript
复制
//@SpringBootApplication
@Configuration
public class Spring5ConfigApplication {...

然后我们添加两个bean配置

代码语言:javascript
复制
@Bean
BarService barService(){
    return new BarService();
}

@Bean
FooService fooService(BarService barService){
    return new FooService(barService);
}

最后样子是这样:

代码语言:javascript
复制
//@SpringBootApplication
@Configuration
public class Spring5ConfigApplication {

    @Bean
    BarService barService(){
        return new BarService();
    }

    @Bean
    FooService fooService(BarService barService){
        return new FooService(barService);
    }

   public static void main(String[] args) {
      //SpringApplication.run(Spring5ConfigApplication.class, args);

        ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
   }
}

class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}

class BarService{

}

然后我们去测试类来测试上面的bean注册。

代码语言:javascript
复制
@RunWith(SpringRunner.class)
//@SpringBootTest
@ContextConfiguration(classes=Spring5ConfigApplication.class)
public class Spring5ConfigApplicationTests {

   @Autowired
   ApplicationContext applicationContext;

   @Test
   public void contextLoads() {

      Assert.assertNotNull("the BarService should not be null.",applicationContext.getBean(BarService.class));
      Assert.assertNotNull("the FooService should not be null.",applicationContext.getBean(FooService.class));

   }

}

上面的测试类把springboottest注解去掉。然后添加ContextConfiguration注解指定配置类。

然后注入application context。

然后我们断言applicationcontext中是不是已经存在了barservice实例和fooservice实例 。

然后我们运行测试

ok,测试通过。说明barservice和fooservice已经被实例化并注册进了context。

二、@Component方式

---------------

接下来我们使用另外一种玩法,通过componentscan来注册。

还是来看看现在代码是什么样子

代码语言:javascript
复制
//@SpringBootApplication
@Configuration
public class Spring5ConfigApplication {

    @Bean
    BarService barService(){
        return new BarService();
    }

    @Bean
    FooService fooService(BarService barService){
        return new FooService(barService);
    }

   public static void main(String[] args) {
      //SpringApplication.run(Spring5ConfigApplication.class, args);

        ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
   }
}

class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}

class BarService{

}

现在我们对上面的代码进行改造。

我们首先给application类添加一个componentscan注解

代码语言:javascript
复制
//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {

然后我们给barservice 和 fooservice分别添加上@Component注解

代码语言:javascript
复制
@Component
class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}

@Component
class BarService{

}

然后我们去掉下面两个bean配置

代码语言:javascript
复制
@Bean
BarService barService(){
    return new BarService();
}

@Bean
FooService fooService(BarService barService){
    return new FooService(barService);
}

现在代码变成这样了

代码语言:javascript
复制
//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {

   public static void main(String[] args) {
      //SpringApplication.run(Spring5ConfigApplication.class, args);

        ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
   }
}

@Component
class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}

@Component
class BarService{

}

然后我们运行测试

代码语言:javascript
复制
@RunWith(SpringRunner.class)
//@SpringBootTest
@ContextConfiguration(classes=Spring5ConfigApplication.class)
public class Spring5ConfigApplicationTests {

   @Autowired
   ApplicationContext applicationContext;

   @Test
   public void contextLoads() {

      Assert.assertNotNull("the BarService should not be null.",applicationContext.getBean(BarService.class));
      Assert.assertNotNull("the FooService should not be null.",applicationContext.getBean(FooService.class));

   }

}

测试代码还之前一样,没动。来看看测试结果:

发现测试通过。两个service已然被实例化并已注册到了context。

@Bean 和 @Component 混用

---------

好,我们接下来把@bean配置方式和@component混用。这就是你过去把xml配置和基于注解混用是一样的概念。

像下面这样:

代码语言:javascript
复制
@Configuration
@ComponentScan
public class Spring5ConfigApplication {


    @Bean  //新加bean BarService
    BarService barService(){
        return new BarService();
    }

   public static void main(String[] args) {
      //SpringApplication.run(Spring5ConfigApplication.class, args);

        ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
   }
}

@Component
class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}


//@Component  //注释掉@Component
class BarService{

}

运行测试

自然是通过了。

BeanDefinitionRegistryPostProcessor方式

----------

现在我们再换一种注册方式,就是通过BeanDefinitionRegistryPostProcessor

来实现注册。

代码语言:javascript
复制
//通过实现BeanDefinitionRegistryPostProcessor来注册bean
@Component
public static class  MyBRPP implements BeanDefinitionRegistryPostProcessor{

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        beanDefinitionRegistry.registerBeanDefinition("barService",BeanDefinitionBuilder.genericBeanDefinition(BarService.class).getBeanDefinition());

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

然后我们运行测试(测试代码没动)

自然也是通过了。

OK,我们现在再把foo service也通过上面的方式注册到context吧。

现在同样把foo service的component注解去掉。

代码语言:javascript
复制
//@Component
class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}

然后我们在BeanDefinitionRegistryPostProcessor的实现类中来注册吧。

代码语言:javascript
复制
//foo service
beanDefinitionRegistry.registerBeanDefinition("fooService",BeanDefinitionBuilder.genericBeanDefinition(FooService.class, new Supplier<FooService>() {
    @Override
    public FooService get() {
        BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
        return new FooService(beanFactory.getBean(BarService.class));
    }
}).getBeanDefinition());

然后我们运行测试,也是通过的。

再来展示下整体类的情况:

代码语言:javascript
复制
//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {

    //通过实现BeanDefinitionRegistryPostProcessor来注册bean
    @Component
    public static class  MyBRPP implements BeanDefinitionRegistryPostProcessor{

        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
            //bar service
            beanDefinitionRegistry.registerBeanDefinition("barService",BeanDefinitionBuilder.genericBeanDefinition(BarService.class).getBeanDefinition());

            //foo service
            beanDefinitionRegistry.registerBeanDefinition("fooService",BeanDefinitionBuilder.genericBeanDefinition(FooService.class, new Supplier<FooService>() {
                @Override
                public FooService get() {
                    BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
                    return new FooService(beanFactory.getBean(BarService.class));
                }
            }).getBeanDefinition());

        }

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

        }
    }


   public static void main(String[] args) {
      //SpringApplication.run(Spring5ConfigApplication.class, args);

        ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
   }
}

//@Component
class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}


//@Component  //注释掉@Component
class BarService{

}

值得注意的是:

1、BeanDefinitionRegistryPostProcessor是一种让我们有机会在spring的标准注册之后,还有机会去修改和添加context内的bean注册。因为所有的bean都被加载后,这时候还并被没有实例化,这个时候我们就有机会在真正实例化之前去注册一些新的bean进去。

2、Supplier。我们使用了Supplier这个接口来去实例化一个类。这是一个functional风格的做法。Supplier是在1.8的时候才有的。是lambda表达式的一种需要。可以被用作lambda表达式的一种赋值对象。

3、lambda替换。由于上面的接口中只有一个方法。这时候我们可以把上面的代码轻松替换成lambda风格,如果你喜欢的话。像这样:

代码语言:javascript
复制
//foo service
beanDefinitionRegistry.registerBeanDefinition("fooService",BeanDefinitionBuilder.genericBeanDefinition(FooService.class, () -> {
    BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
    return new FooService(beanFactory.getBean(BarService.class));
}).getBeanDefinition());

4、静态导入优化。上面有一些静态类调用。我们可以改为静态导入,让代码看起来更加的简短(如果你喜欢的话),如下:

代码语言:javascript
复制
//bar service
beanDefinitionRegistry.registerBeanDefinition("barService", genericBeanDefinition(BarService.class).getBeanDefinition());

//foo service
beanDefinitionRegistry.registerBeanDefinition("fooService", genericBeanDefinition(FooService.class, () -> {
    BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
    return new FooService(beanFactory.getBean(BarService.class));
}).getBeanDefinition());

像这样:

代码语言:javascript
复制
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*;

ApplicationContextInitializer方式

---------------

ok,接下来我们再向你展示一种注册bean的方式。这也是今天的一个重头戏。

你还记得我们在一开始的时候,就把SpringBootApplication注解去掉了。

代码语言:javascript
复制
//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {

现在让我们回到SpringBootApplication上来。我们把SpringBootApplication注解再加回来。然后去掉另外两个注解。

代码语言:javascript
复制
@SpringBootApplication
public class Spring5ConfigApplication {

同时我们把main方法中的context方式注释掉,重回springbootapplication。

代码语言:javascript
复制
public static void main(String[] args) {
   SpringApplication.run(Spring5ConfigApplication.class, args);

       //ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
}

然后我们新建一个类ApplicationContextInitializr类的实现。

代码语言:javascript
复制
class ProgrammaticBeanDefinitionInitializr implements ApplicationContextInitializer<GenericApplicationContext>{

    @Override
    public void initialize(GenericApplicationContext genericApplicationContext) {
        System.err.println("Hello ,initializr");
    }
}

为了尽早的看到效果,这里暂时我们先就写一句(为了一会显眼,我们打印一个error)

代码语言:javascript
复制
System.err.println("Hello ,initializr");

然后我们在resources目录下新建目录META-INF,然后在下面新建spring.factories文件。

文件内容:

代码语言:javascript
复制
org.springframework.context.ApplicationContextInitializer=com.importsource.spring.samples.config.ProgrammaticBeanDefinitionInitializr

值得注意的是,这里的做法叫做“自动配置”,也就是AutoConfiguration。有关自动配置的内容你可以移步这里了解:像Spring Boot那样创建一个你自己的Starter

ok,现在我们去运行测试。

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest(classes=Spring5ConfigApplication.class)
//@ContextConfiguration(classes=Spring5ConfigApplication.class)
public class Spring5ConfigApplicationTests {

   @Autowired
   ApplicationContext applicationContext;

   @Test
   public void contextLoads() {

      Assert.assertNotNull("the BarService should not be null.",applicationContext.getBean(BarService.class));
      Assert.assertNotNull("the FooService should not be null.",applicationContext.getBean(FooService.class));

   }

}

由于我们回到了spring boot 应用做法。 所以我们把之前的context configuration注释掉,重回spring boot test 注释,如上。

然后我们运行测试。

发现我们的application context initializr 已经生效了,我们看到了我们刚才写的打印输出了:Hello , initializr。

接下来,我们就通过application context initializr来进行注册bean。

下面我们把err打印注释掉,开始正式的注册工作。

代码语言:javascript
复制
class ProgrammaticBeanDefinitionInitializr implements ApplicationContextInitializer<GenericApplicationContext>{

    @Override
    public void initialize(GenericApplicationContext genericApplicationContext) {
        //System.err.println("Hello ,initializr");
        genericApplicationContext.registerBean(BarService.class);
        genericApplicationContext.registerBean(
                FooService.class,
                ()-> new FooService(genericApplicationContext.getBean(BarService.class))
        );
    }
}

值得注意的是,我们在这之前要把之前的那种BeanDefinitionRegistryPostProcessor方式的@Component注解注释掉,这样我们才能真正的看到

ProgrammaticBeanDefinitionInitializr方式的注册效果。

以上代码中我们使用initialize方法的参数genericApplicationContext来进行注册bean。我们分别注册了BarService以及FooService。其中FooService由于需要传入构造函数参数,所以还得通过registerBean的第二个参数来传入具体的实例化实现。

然后我们运行测试

发现已经测试通过。说明我们通过ProgrammaticBeanDefinitionInitializr的方式成功的将两个bean注册进入了context。

-----------

以下是本次演示的完整代码:

代码语言:javascript
复制
@SpringBootApplication
//@ComponentScan
//@Configuration
public class Spring5ConfigApplication {

    //通过实现BeanDefinitionRegistryPostProcessor来注册bean
    //@Component
    public static class  MyBRPP implements BeanDefinitionRegistryPostProcessor{

        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
            //bar service
            beanDefinitionRegistry.registerBeanDefinition("barService", genericBeanDefinition(BarService.class).getBeanDefinition());

            //foo service
            beanDefinitionRegistry.registerBeanDefinition("fooService", genericBeanDefinition(FooService.class, () -> {
                BeanFactory beanFactory= BeanFactory.class.cast(beanDefinitionRegistry);
                return new FooService(beanFactory.getBean(BarService.class));
            }).getBeanDefinition());

        }

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

        }
    }


   public static void main(String[] args) {
      SpringApplication.run(Spring5ConfigApplication.class, args);

        //ApplicationContext ac=new AnnotationConfigApplicationContext(Spring5ConfigApplication.class);
   }
}

//@Component
class FooService{
    private  final  BarService barService;

    public FooService(BarService barService){
        this.barService=barService;
    }
}


//@Component  //注释掉@Component
class BarService{

}

class ProgrammaticBeanDefinitionInitializr implements ApplicationContextInitializer<GenericApplicationContext>{

    @Override
    public void initialize(GenericApplicationContext genericApplicationContext) {
        //System.err.println("Hello ,initializr");
        genericApplicationContext.registerBean(BarService.class);
        genericApplicationContext.registerBean(
                FooService.class,
                ()-> new FooService(genericApplicationContext.getBean(BarService.class))
        );
    }
}

以及测试代码:

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest(classes=Spring5ConfigApplication.class)
//@ContextConfiguration(classes=Spring5ConfigApplication.class)
public class Spring5ConfigApplicationTests {

   @Autowired
   ApplicationContext applicationContext;

   @Test
   public void contextLoads() {

      Assert.assertNotNull("the BarService should not be null.",applicationContext.getBean(BarService.class));
      Assert.assertNotNull("the FooService should not be null.",applicationContext.getBean(FooService.class));

   }

}

总结

本文我们分别演示@Bean方式注册、@Component方式注册以及还有二者混用,然后我们向你演示了如何通过BeanDefinitionRegistryPostProcessor来实现bean的注册,最后又介绍了如何通过自定义一个ApplicationContextInitializer来实现bean的注册。

通过这么多的注册玩法让你对spring的bean注册有一个比较总体的了解。特别是最后一种通过非注解的方式,通过auto configuration的方式来定制ApplicationContextInitializer,然后把bean注册进去,这是一种纯粹的通过编码来注册的方式。这种方式的一个典型的场景就是web应用程序中会对应用程序上下文(也就是ApplicationContext)做一些初始化工作。通过这个切面我们就有机会去修改application context,从而满足我们具体的需求。

--------

点击“阅读原文”可获得本次演示的源代码。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ImportSource 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档