Spring容器的靈魂

一、背景

Spring使用BeanFactory來產(chǎn)生和管理Bean,它是工廠模式的實現(xiàn)。BeanFactory使用控制反轉(zhuǎn)模式將應用的配置和依賴性規(guī)范與實際的應用程序代碼分開。BeanFactory使用依賴注入的方式給組件提供依賴 。

image.png

二、準備工作

準備工作:運行一個Springboot工程新建一個Springboot工程,由于我平時使用的版本是2.1.3.RELEASE,所以我選擇了自己的常用版本。使用maven項目,引入spring-boot-starter依賴包.OK一個最簡單的環(huán)境已經(jīng)準備好了。

<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的規(guī)范,了解不多,不過多描述,就把它當作@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問世以來,后續(xù)使用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文件的時候發(fā)現(xiàn)注解里面可以指定reader,默認的實現(xiàn)是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這三種方式可以歸為一類,我們完全可以依葫蘆畫瓢自定義一個自己的文件格式,然后實現(xiàn)一個BeanDefinitionReader。只是這3種是Spring已經(jīng)給我們實現(xiàn)好了的可以拿來直接用。有一句說一句,工作了這么久我也是只用過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);
        //如果構(gòu)造參數(shù)特別復雜,可以借助BeanDefinitionBuilder
//        AbstractBeanDefinition rbd2 = BeanDefinitionBuilder.rootBeanDefinition(MyBeanDefinitionRegistryPostProcessorBean.class)
//                .addPropertyValue("name","myBeanDefinitionRegistryPostProcessorBean2")
//                .getBeanDefinition();
//        registry.registerBeanDefinition("myBeanDefinitionRegistryPostProcessorBean2", rbd2);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //同BeanFactoryPostProcessor,優(yōu)先加載
    }
}

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

四、條條大路通羅馬

數(shù)了一下,上述大概列出了13種向Spring容器中注入Bean的方式,不知道各位小伙伴們都用過哪些?有些方式咱們只需要稍微了解一下就可以了,但是如果想要學好Spring,那么我們一定要了解其中比較重要的幾個比如ImportSelector、FactoryBean、BeanFactoryPostProcessor,因為日后的源碼中會經(jīng)常遇到他們。

五、總結(jié)

Spring對于Bean的配置有兩種方式:

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

優(yōu)點:可以在后期維護的時候適當?shù)卣{(diào)整Bean管理模式,并且只要遵循一定的命名規(guī)范,可以讓程序員不必關(guān)心Bean之間的依賴關(guān)系 。缺點 :系統(tǒng)越龐大,XML配置文件就越大,關(guān)系錯綜復雜,容易導致錯誤 。

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

優(yōu)點:配置比較方便,我們只要在 service 層代碼設置即可實現(xiàn),不需要知道系統(tǒng)需要多少個 Bean,交給容器來注入就好了 。缺點:當你要修改或刪除一個 Bean 的時候,你無法確定到底有多少個其他的 Bean依賴于這個 Bean。(解決方法 : 需要有嚴格的開發(fā)文檔,在修改實現(xiàn)時盡可能繼續(xù)遵守相應的 接口規(guī)則,避免使其他依賴于此的 Bean不可用。)

聊完了Bean的配置方式,下次我們一起探討一下這些Bean是如何被Spring容器發(fā)現(xiàn)并組裝提供我們使用。

程序員的核心競爭力其實還是技術(shù),因此對技術(shù)還是要不斷的學習,關(guān)注 “IT 巔峰技術(shù)” 公眾號 ,該公眾號內(nèi)容定位:中高級開發(fā)、架構(gòu)師、中層管理人員等中高端崗位服務的,除了技術(shù)交流外還有很多架構(gòu)思想和實戰(zhàn)案例,作者是 《 消息中間件 RocketMQ 技術(shù)內(nèi)幕》 一書作者,同時也是 “RocketMQ 上海社區(qū)”聯(lián)合創(chuàng)始人,曾就職于拼多多、德邦等公司,現(xiàn)任上市快遞公司架構(gòu)負責人,主要負責開發(fā)框架的搭建、中間件相關(guān)技術(shù)的二次開發(fā)和運維管理、混合云及基礎(chǔ)服務平臺的建設。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容