Spring容器的灵魂

1,161 阅读5分钟

一、背景

Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转模式将应用的配置和依赖性规范与实际的应用程序代码分开。BeanFactory使用依赖注入的方式给组件提供依赖 。

image.png 二、准备工作

准备工作:运行一个Springboot工程新建一个Springboot工程,由于我平时使用的版本是2.1.3.RELEASE,所以我选择了自己的常用版本。使用maven项目,引入spring-boot-starter依赖包.OK一个最简单的环境已经准备好了。

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.1.3.RELEASE</version>
 </dependency
 @SpringBootApplication
  public class BootStarter {
      public static void main(String[] args) {
          ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
      }
  }

三、定义一个Bean有哪些方式?

3.1 @Component注解方式

启动类默认扫描路径为当前所在目录及子目录,只要确保加了@Component,@Service,@Configuration等注解,Spring就会将该Bean注入到容器,这也应该是我们最常用到的一种方式。

  @Component
   public class MyCompBean { 
     private String name="myCompBean";
   }

   @SpringBootApplication
   public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //@Component
        Object myCompBean = context.getBean("myCompBean");
        System.out.println(myCompBean);
      }
   }

3.2 @Bean方式

 这个注解我们用的应该也挺多的,大家都很熟悉,就不过多介绍了。

   @Data
    public class MyAnnoBean {
        private String name = "myAnnoBean";
    }

    @Configuration
    public class MyConfig {
        @Bean
        public MyAnnoBean myAnnoBean(){
            return new MyAnnoBean();
        }
    }

   @SpringBootApplication
   public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //anno
        Object myAnnoBean = context.getBean("myAnnoBean");
        System.out.println(myAnnoBean);
      }
   }

3.3 @Named

Jsr330的规范,了解不多,不过多描述,就把它当作@Component使用就好了 需要在pom中引入javax.inject依赖

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
@Data
@Named
public class MyNamedBean {
    private String name="myNamedBean";
}

@SpringBootApplication
public class BootStarter {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //@Named
        Object myNamedBean = context.getBean("myNamedBean");
        System.out.println(myNamedBean);
      }
}

3.4 XML方式

       我相信大部分人对于Spring的XML配置文件不会陌生(实际上自从Springboot问世以来,后续使用Spring的人还真就不会再去接触XML了),但是作为一个使用Spring的“老人”,我们肯定是不会忘本的。

在resources目录下新建beans.xml

<bean class="org.example.springbean.xml.MyXmlBean" id="myXmlBean"></bean>

由于我们使用Springboot启动,因此xml还需要通过@ImportResource注解来引入一下

   @Configuration
   @ImportResource(locations={"beans.xml"})
   public class XmlConfig {
   }

   @SpringBootApplication
   public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //xml
        Object myXmlBean= context.getBean("myXmlBean");
        System.out.println(myXmlBean);
      }
   }

3.5 Properties方式

在resources目录下新建beans.properties

myPropertiesBean.(class)=org.example.springbean.props.MyPropertiesBean

这个方式是在使用@ImportResource注解引入XML文件的时候发现注解里面可以指定reader,默认的实现是XmlBeanDefinitionReader,这里需要指定reader = PropertiesBeanDefinitionReader.class

@Configuration
@ImportResource(locations={"beans.properties"},reader = PropertiesBeanDefinitionReader.class)
public class PropsConfig {
}
@SpringBootApplication
public class BootStarter {

  public static void main(String[] args) {
     ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
     //properties
     Object myPropertiesBean = context.getBean("myPropertiesBean");
     System.out.println(myPropertiesBean);
   }
}

3.6 Groovy方式

这个姑且也算是一种方式吧,跟xml差不多除了需要制定reader = GroovyBeanDefinitionReader.class之外,还需要额外引入一个groovy-xml的依赖

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-xml</artifactId>
    <version>3.0.8</version>
</dependency>

在resources目录下新建一个beans.groovy


import org.example.springbean.groovy.MyGroovyBean;

beans{
    myGroovyBean(MyGroovyBean){}
}
@Data
public class MyGroovyBean {
    private String name="myGroovyBean";
}

@Configuration
@ImportResource(locations = {"beans.groovy"},reader = GroovyBeanDefinitionReader.class)
public class GroovyConfig {
}
   @SpringBootApplication
   public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //groovy
        Object myGroovyBean = context.getBean("myGroovyBean");
        System.out.println(myGroovyBean);
      }
   }

从加载的方式上来看,properties,xml,groovy这三种方式可以归为一类,我们完全可以依葫芦画瓢自定义一个自己的文件格式,然后实现一个BeanDefinitionReader。只是这3种是Spring已经给我们实现好了的可以拿来直接用。有一句说一句,工作了这么久我也是只用过Xml方式。。。

3.7 FactoryBean方式

@Data
public class MyFacBean {
    private String name = "myFacBean";
}

@Component
public class MyFacBeanFactory implements FactoryBean<MyFacBean> {

    @Override
    public MyFacBean getObject() throws Exception {
        return new MyFacBean();
    }

    @Override
    public Class<?> getObjectType() {
        return MyFacBean.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

@SpringBootApplication
public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //factoryBean
        //1.找不到这个bean
        //myFacBean = (MyFacBean)context.getBean("myFacBean");

        //2.通过工厂的名称获取bean获取的是实际的bean
        Object myFacBean = context.getBean("myFacBeanFactory");
        System.out.println(myFacBean);

        //3.获取工厂bean必须在name前加上一个 &
        Object myFacBeanFactory = context.getBean("&myFacBeanFactory");
        System.out.println(myFacBeanFactory);
      }
   }
}

3.8 @Import

public class MyImportBean {
    private String name="myImportBean";
}

@SpringBootApplication
@Import(value = {MyImportBean.class})
public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //必须使用全路径
        //Object myImportBean = context.getBean("myImportBean");
        Object myImportBean = context.getBean("org.example.springbean.ipt.MyImportBean");
        System.out.println(myImportBean);
      }
   }
}

3.9 ImportSelector

@Data
public class MyImportSelectorBean {
    private String name="myImportSelectorBean";
}

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MyImportSelectorBean.class.getName()};
    }
}

@SpringBootApplication
@Import(value = {MyImportSelector.class})
public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //ImportSelector
        Object myImportSelectorBean = context.getBean("org.example.springbean.ipt.MyImportSelectorBean");
            System.out.println(myImportSelectorBean);
      }
   }
}

3.10 ImportBeanDefinitionRegistrar

@Data
public class MyImportBeanDefinitionRegistrarBean {
    private String name="importBeanDefinitionRegistrar";
}

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(MyImportBeanDefinitionRegistrarBean.class);
        registry.registerBeanDefinition("myImportBeanDefinitionRegistrarBean", beanDefinition);
    }
}

@SpringBootApplication
@Import(value = {MyImportBeanDefinitionRegistrar.class})
public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //ImportBeanDefinitionRegistrar
        Object myImportBeanDefinitionRegistrarBean = context.getBean("myImportBeanDefinitionRegistrarBean");
            System.out.println(myImportBeanDefinitionRegistrarBean);
      }
   }
}

3.11 BeanFactoryPostProcessor


@Data
public class MyBeanFactoryPostProcessorBean {
    private String name="myBeanFactoryPostProcessorBean";
}

@Configuration
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerSingleton("myBeanFactoryPostProcessorBean",new MyBeanFactoryPostProcessorBean());
    }
}

@SpringBootApplication
public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //BeanFactoryPostProcessor
        Object myBeanFactoryPostProcessorBean = context.getBean("myBeanFactoryPostProcessorBean");
            System.out.println(myBeanFactoryPostProcessorBean);
      }
   }
}

3.12 BeanDefinitionRegistryPostProcessor


@Data
public class MyBeanDefinitionRegistryPostProcessorBean {
    private String name = "myBeanDefinitionRegistryPostProcessorBean";
}

@Configuration
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        RootBeanDefinition rbd = new RootBeanDefinition(MyBeanDefinitionRegistryPostProcessorBean.class);
        registry.registerBeanDefinition("myBeanDefinitionRegistryPostProcessorBean", rbd);
        //如果构造参数特别复杂,可以借助BeanDefinitionBuilder
//        AbstractBeanDefinition rbd2 = BeanDefinitionBuilder.rootBeanDefinition(MyBeanDefinitionRegistryPostProcessorBean.class)
//                .addPropertyValue("name","myBeanDefinitionRegistryPostProcessorBean2")
//                .getBeanDefinition();
//        registry.registerBeanDefinition("myBeanDefinitionRegistryPostProcessorBean2", rbd2);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //同BeanFactoryPostProcessor,优先加载
    }
}

@SpringBootApplication
public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //BeanDefinitionRegistryPostProcessor
        Object myBeanDefinitionRegistryPostProcessorBean = context.getBean("myBeanDefinitionRegistryPostProcessorBean");
            System.out.println(myBeanDefinitionRegistryPostProcessorBean);
      }
   }
}

3.13 借助ApplicationContext

@Data
public class CustomBean {
    private String name="customBean";
}

@Component
public class CustomContext {

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init(){
        BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) this.applicationContext;
        RootBeanDefinition rbd = new RootBeanDefinition(CustomBean.class);
        beanDefinitionRegistry.registerBeanDefinition("customBean",rbd);
    }

}

@SpringBootApplication
public class BootStarter {

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootStarter.class);
        //custom
        Object customBean = context.getBean("customBean");
        System.out.println(customBean);
      }
   }
}

四、条条大路通罗马

数了一下,上述大概列出了13种向Spring容器中注入Bean的方式,不知道各位小伙伴们都用过哪些?有些方式咱们只需要稍微了解一下就可以了,但是如果想要学好Spring,那么我们一定要了解其中比较重要的几个比如ImportSelector、FactoryBean、BeanFactoryPostProcessor,因为日后的源码中会经常遇到他们。

五、总结

Spring对于Bean的配置有两种方式:

  1. 基于资源文件解析的方式,其中最常用的是XML配置

优点:可以在后期维护的时候适当地调整Bean管理模式,并且只要遵循一定的命名规范,可以让程序员不必关心Bean之间的依赖关系 。缺点 :系统越庞大,XML配置文件就越大,关系错综复杂,容易导致错误 。

  1. 基于 JavaConfig 的注解配置方式

优点:配置比较方便,我们只要在 service 层代码设置即可实现,不需要知道系统需要多少个 Bean,交给容器来注入就好了 。缺点:当你要修改或删除一个 Bean 的时候,你无法确定到底有多少个其他的 Bean依赖于这个 Bean。(解决方法 : 需要有严格的开发文档,在修改实现时尽可能继续遵守相应的 接口规则,避免使其他依赖于此的 Bean不可用。)

聊完了Bean的配置方式,下次我们一起探讨一下这些Bean是如何被Spring容器发现并组装提供我们使用。

程序员的核心竞争力其实还是技术,因此对技术还是要不断的学习,关注 “IT 巅峰技术” 公众号 ,该公众号内容定位:中高级开发、架构师、中层管理人员等中高端岗位服务的,除了技术交流外还有很多架构思想和实战案例,作者是 《 消息中间件 RocketMQ 技术内幕》 一书作者,同时也是 “RocketMQ 上海社区”联合创始人,曾就职于拼多多、德邦等公司,现任上市快递公司架构负责人,主要负责开发框架的搭建、中间件相关技术的二次开发和运维管理、混合云及基础服务平台的建设。