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

各位好,今天我们的内容是有关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类。注释掉下面两行代码。

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

   }
}

然后我们新建两个service类

class FooService{
    private  final  BarService barService;

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

class BarService{

}

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

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

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

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

//@SpringBootApplication
@Configuration
public class Spring5ConfigApplication {...

然后我们添加两个bean配置

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

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

最后样子是这样:

//@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注册。

@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来注册。

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

//@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注解

//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {

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

@Component
class FooService{
    private  final  BarService barService;

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

@Component
class BarService{

}

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

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

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

现在代码变成这样了

//@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{

}

然后我们运行测试

@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配置和基于注解混用是一样的概念。

像下面这样:

@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

来实现注册。

//通过实现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注解去掉。

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

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

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

//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());

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

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

//@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风格,如果你喜欢的话。像这样:

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

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

//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());

像这样:

import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*;

ApplicationContextInitializer方式

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

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

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

//@SpringBootApplication
@Configuration
@ComponentScan
public class Spring5ConfigApplication {

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

@SpringBootApplication
public class Spring5ConfigApplication {

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

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

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

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

class ProgrammaticBeanDefinitionInitializr implements ApplicationContextInitializer<GenericApplicationContext>{

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

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

System.err.println("Hello ,initializr");

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

文件内容:

org.springframework.context.ApplicationContextInitializer=com.importsource.spring.samples.config.ProgrammaticBeanDefinitionInitializr

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

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

@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打印注释掉,开始正式的注册工作。

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。

-----------

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

@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))
        );
    }
}

以及测试代码:

@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,从而满足我们具体的需求。

--------

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

原文发布于微信公众号 - ImportSource(importsource)

原文发表时间:2017-04-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOSDevLog

Learn Android Studio 3Android architectureAndroid ComponentsDesign editor.pngKeyboard Shortcuts for

2938
来自专栏CodingToDie

传统Spring项目使用FeignClient组件访问微服务

传统Spring项目使用 这里的传统 Spring项目指的是没有使用 spring boot的 spring项目,例如 ssm api 文件 和在spring ...

7.9K8
来自专栏黑泽君的专栏

常用的 default.properties 文件 + 常用的 struts-default.xml 文件 + 常用的 struts-plugin.xml 文件 + 常用的 struts.xml 文件

常用的 default.properties 文件,所在位置:\struts-2.3.15.3-all\struts-2.3.15.3\apps\struts2...

914
来自专栏Java成神之路

Spring_总结_03_装配Bean(一)之自动装配

(2)当必须要显示配置的时候,再使用类型安全并且比XML更强大的JavaConfig

1002
来自专栏Android 研究

Android系统启动——6 SystemServer启动

SystemServer是Android系统的核心之一,大部分Android提供的服务都运行在这个进程里,SystemServer中运行的服务总共有60多种。为...

5613
来自专栏Ryan Miao

Spring-AOP实践 - 统计访问时间

公司的项目有的页面超级慢,20s以上,不知道用户会不会疯掉,于是老大说这个页面要性能优化。于是,首先就要搞清楚究竟是哪一步耗时太多。 我采用spring aop...

4868
来自专栏happyJared

Spring Boot 1.0 && 2.0 + JPA 多数据源配置与使用

mysql 对应的数据源配置中,定义了实体 Student 和对应的数据层接口 StudentRepository:

2513
来自专栏好好学java的技术栈

SpringMVC+RestFul详细示例实战教程(实现跨域访问)

**REST(Representational State Transfer)**,中文翻译叫“表述性状态转移”。是 Roy Thomas Fielding 在...

2054
来自专栏菩提树下的杨过

spring-boot 速成(8) 集成druid+mybatis

spring-boot与druid、mybatis集成(包括pageHelper分页插件), 要添加以下几个依赖项: compile('mysql:my...

9289
来自专栏Coding+

LitePal 的基本用法

3552

扫码关注云+社区

领取腾讯云代金券