写点什么

Spring 容器的灵魂

作者:IT巅峰技术
  • 2022 年 4 月 04 日
  • 本文字数:6850 字

    阅读完需:约 22 分钟

一、背景

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



二、准备工作


准备工作:运行一个 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@Namedpublic class MyNamedBean {    private String name="myNamedBean";}
@SpringBootApplicationpublic 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 {}@SpringBootApplicationpublic 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){}}
复制代码


@Datapublic 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 方式

@Datapublic class MyFacBean {    private String name = "myFacBean";}
@Componentpublic 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; }
@SpringBootApplicationpublic 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

@Datapublic 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

@Datapublic 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


@Datapublic class MyBeanFactoryPostProcessorBean { private String name="myBeanFactoryPostProcessorBean";}
@Configurationpublic class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerSingleton("myBeanFactoryPostProcessorBean",new MyBeanFactoryPostProcessorBean()); }}
@SpringBootApplicationpublic 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


@Datapublic class MyBeanDefinitionRegistryPostProcessorBean { private String name = "myBeanDefinitionRegistryPostProcessorBean";}
@Configurationpublic 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,优先加载 }}
@SpringBootApplicationpublic 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

@Datapublic class CustomBean {    private String name="customBean";}
@Componentpublic 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); }
}
@SpringBootApplicationpublic 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 上海社区”联合创始人,曾就职于拼多多、德邦等公司,现任上市快递公司架构负责人,主要负责开发框架的搭建、中间件相关技术的二次开发和运维管理、混合云及基础服务平台的建设。

发布于: 刚刚阅读数: 2
用户头像

一线架构师、二线开发、三线管理 2021.12.07 加入

Redis6.X、ES7.X、Kafka3.X、RocketMQ5.0、Flink1.X、ClickHouse20.X、SpringCloud、Netty5等热门技术分享;架构设计方法论与实践;作者热销新书《RocketMQ技术内幕》;

评论

发布
暂无评论
Spring容器的灵魂_Spring 框架漏洞_IT巅峰技术_InfoQ写作平台