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ī)范,可以讓程序員不必關心Bean之間的依賴關系 愉舔。缺點 :系統(tǒng)越龐大钢猛,XML配置文件就越大,關系錯綜復雜轩缤,容易導致錯誤 命迈。

  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ù)還是要不斷的學習勃黍,關注 “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ā)框架的搭建蛔六、中間件相關技術(shù)的二次開發(fā)和運維管理、混合云及基礎服務平臺的建設废亭。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末国章,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子豆村,更是在濱河造成了極大的恐慌液兽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掌动,死亡現(xiàn)場離奇詭異四啰,居然都是意外死亡,警方通過查閱死者的電腦和手機粗恢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門柑晒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人眷射,你說我怎么就攤上這事匙赞》鹨矗” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵涌庭,是天一觀的道長芥被。 經(jīng)常有香客問我,道長坐榆,這世上最難降的妖魔是什么撕彤? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮猛拴,結(jié)果婚禮上羹铅,老公的妹妹穿的比我還像新娘。我一直安慰自己愉昆,他們只是感情好职员,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著跛溉,像睡著了一般焊切。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芳室,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天专肪,我揣著相機與錄音,去河邊找鬼堪侯。 笑死物蝙,一個胖子當著我的面吹牛休吠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扼睬,長吁一口氣:“原來是場噩夢啊……” “哼贷帮!你這毒婦竟也來了敢伸?” 一聲冷哼從身側(cè)響起辛萍,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卖毁,沒想到半個月后揖曾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡亥啦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年炭剪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禁悠。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡念祭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碍侦,到底是詐尸還是另有隱情粱坤,我是刑警寧澤隶糕,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站站玄,受9級特大地震影響枚驻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜株旷,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一再登、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晾剖,春花似錦锉矢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至循头,卻和暖如春绵估,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卡骂。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工国裳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人全跨。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓缝左,卻偏偏與公主長得像,于是被迫代替她去往敵國和親螟蒸。 傳聞我的和親對象是個殘疾皇子盒使,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容