1. 驚呆了,Spring中竟然有12種定義bean的方法

        共 19098字,需瀏覽 39分鐘

         ·

        2021-09-15 18:51

        往期熱門(mén)文章:

        1、代碼寫(xiě)的垃圾被嫌棄?

        2、牛逼!SpringBoot+Vue企業(yè)級(jí)支付系統(tǒng)!附源碼!

        3、你真的會(huì)寫(xiě)for循環(huán)嗎?來(lái)看看這些常見(jiàn)的for循環(huán)優(yōu)化方式

        4、JDK17 來(lái)了!不好意思,我還在用JDK8!

        5、字節(jié)員工要哭了,薪資普調(diào)-17%!

        前言

        在龐大的java體系中,spring有著舉足輕重的地位,它給每位開(kāi)發(fā)者帶來(lái)了極大的便利和驚喜。我們都知道spring是創(chuàng)建和管理bean的工廠,它提供了多種定義bean的方式,能夠滿足我們?nèi)粘9ぷ髦械亩喾N業(yè)務(wù)場(chǎng)景。

        那么問(wèn)題來(lái)了,你知道spring中有哪些方式可以定義bean?

        我估計(jì)很多人會(huì)說(shuō)出以下三種:

        沒(méi)錯(cuò),但我想說(shuō)的是以上三種方式只是開(kāi)胃小菜,實(shí)際上spring的功能遠(yuǎn)比你想象中更強(qiáng)大。

        各位看官如果不信,請(qǐng)繼續(xù)往下看。

        1. xml文件配置bean

        我們先從xml配置bean開(kāi)始,它是spring最早支持的方式。后來(lái),隨著springboot越來(lái)越受歡迎,該方法目前已經(jīng)用得很少了,但我建議我們還是有必要了解一下。

        1.1 構(gòu)造器

        如果你之前有在bean.xml文件中配置過(guò)bean的經(jīng)歷,那么對(duì)如下的配置肯定不會(huì)陌生:

        <bean id="personService" class="com.sue.cache.service.test7.PersonService">
        </bean>

        這種方式是以前使用最多的方式,它默認(rèn)使用了無(wú)參構(gòu)造器創(chuàng)建bean。

        當(dāng)然我們還可以使用有參的構(gòu)造器,通過(guò)<constructor-arg>標(biāo)簽來(lái)完成配置。

        <bean id="personService" class="com.sue.cache.service.test7.PersonService">
           <constructor-arg index="0" value="susan"></constructor-arg>
           <constructor-arg index="1" ref="baseInfo"></constructor-arg>
        </bean>

        其中:

        • index表示下標(biāo),從0開(kāi)始。
        • value表示常量值
        • ref表示引用另一個(gè)bean

        1.2 setter方法

        除此之外,spring還提供了另外一種思路:通過(guò)setter方法設(shè)置bean所需參數(shù),這種方式耦合性相對(duì)較低,比有參構(gòu)造器使用更為廣泛。

        先定義Person實(shí)體:

        @Data
        public class Person {
            private String name;
            private int age;
        }

        它里面包含:成員變量name和age,getter/setter方法。

        然后在bean.xml文件中配置bean時(shí),加上<property>標(biāo)簽設(shè)置bean所需參數(shù)。

        <bean id="person" class="com.sue.cache.service.test7.Person">
           <property name="name" value="susan"></constructor-arg>
           <property name="age" value="18"></constructor-arg>
        </bean>

        1.3 靜態(tài)工廠

        這種方式的關(guān)鍵是需要定義一個(gè)工廠類(lèi),它里面包含一個(gè)創(chuàng)建bean的靜態(tài)方法。例如:

        public class SusanBeanFactory {
            public static Person createPerson(String name, int age) {
                return new Person(name, age);
            }
        }

        接下來(lái)定義Person類(lèi)如下:

        @AllArgsConstructor
        @NoArgsConstructor
        @Data
        public class Person {
            private String name;
            private int age;
        }

        它里面包含:成員變量name和age,getter/setter方法,無(wú)參構(gòu)造器和全參構(gòu)造器。

        然后在bean.xml文件中配置bean時(shí),通過(guò)factory-method參數(shù)指定靜態(tài)工廠方法,同時(shí)通過(guò)<constructor-arg>設(shè)置相關(guān)參數(shù)。

        <bean class="com.sue.cache.service.test7.SusanBeanFactory" factory-method="createPerson">
           <constructor-arg index="0" value="susan"></constructor-arg>
           <constructor-arg index="1" value="18"></constructor-arg>
        </bean>

        1.4 實(shí)例工廠方法

        這種方式也需要定義一個(gè)工廠類(lèi),但里面包含非靜態(tài)的創(chuàng)建bean的方法。

        public class SusanBeanFactory {
            public Person createPerson(String name, int age) {
                return new Person(name, age);
            }
        }

        Person類(lèi)跟上面一樣,就不多說(shuō)了。

        然后bean.xml文件中配置bean時(shí),需要先配置工廠bean。然后在配置實(shí)例bean時(shí),通過(guò)factory-bean參數(shù)指定該工廠bean的引用。

        <bean id="susanBeanFactory" class="com.sue.cache.service.test7.SusanBeanFactory">
        </bean>
        <bean factory-bean="susanBeanFactory" factory-method="createPerson">
           <constructor-arg index="0" value="susan"></constructor-arg>
           <constructor-arg index="1" value="18"></constructor-arg>
        </bean>

        1.5 FactoryBean

        不知道大家有沒(méi)有發(fā)現(xiàn),上面的實(shí)例工廠方法每次都需要?jiǎng)?chuàng)建一個(gè)工廠類(lèi),不方面統(tǒng)一管理。

        這時(shí)我們可以使用FactoryBean接口。

        public class UserFactoryBean implements FactoryBean<User{
            @Override
            public User getObject() throws Exception {
                return new User();
            }

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

        在它的getObject方法中可以實(shí)現(xiàn)我們自己的邏輯創(chuàng)建對(duì)象,并且在getObjectType方法中我們可以定義對(duì)象的類(lèi)型。

        然后在bean.xml文件中配置bean時(shí),只需像普通的bean一樣配置即可。

        <bean id="userFactoryBean" class="com.sue.async.service.UserFactoryBean">
        </bean>

        輕松搞定,so easy。

        注意:getBean("userFactoryBean");獲取的是getObject方法中返回的對(duì)象。而getBean("&userFactoryBean");獲取的才是真正的UserFactoryBean對(duì)象。

        我們通過(guò)上面五種方式,在bean.xml文件中把bean配置好之后,spring就會(huì)自動(dòng)掃描和解析相應(yīng)的標(biāo)簽,并且?guī)臀覀儎?chuàng)建和實(shí)例化bean,然后放入spring容器中。

        雖說(shuō)基于xml文件的方式配置bean,簡(jiǎn)單而且非常靈活,比較適合一些小項(xiàng)目。但如果遇到比較復(fù)雜的項(xiàng)目,則需要配置大量的bean,而且bean之間的關(guān)系錯(cuò)綜復(fù)雜,這樣久而久之會(huì)導(dǎo)致xml文件迅速膨脹,非常不利于bean的管理。

        2. Component注解

        為了解決bean太多時(shí),xml文件過(guò)大,從而導(dǎo)致膨脹不好維護(hù)的問(wèn)題。在spring2.5中開(kāi)始支持:@Component、@Repository、@Service、@Controller等注解定義bean。

        如果你有看過(guò)這些注解的源碼的話,就會(huì)驚奇得發(fā)現(xiàn):其實(shí)后三種注解也是@Component

        @Component系列注解的出現(xiàn),給我們帶來(lái)了極大的便利。我們不需要像以前那樣在bean.xml文件中配置bean了,現(xiàn)在只用在類(lèi)上加Component、Repository、Service、Controller,這四種注解中的任意一種,就能輕松完成bean的定義。

        @Service
        public class PersonService {
            public String get() {
                return "data";
            }
        }

        其實(shí),這四種注解在功能上沒(méi)有特別的區(qū)別,不過(guò)在業(yè)界有個(gè)不成文的約定:

        • Controller 一般用在控制層
        • Service 一般用在業(yè)務(wù)層
        • Repository 一般用在數(shù)據(jù)層
        • Component 一般用在公共組件上

        太棒了,簡(jiǎn)直一下子解放了我們的雙手。

        不過(guò),需要特別注意的是,通過(guò)這種@Component掃描注解的方式定義bean的前提是:需要先配置掃描路徑。

        目前常用的配置掃描路徑的方式如下:

        1. 在applicationContext.xml文件中使用<context:component-scan>標(biāo)簽。例如:
        <context:component-scan base-package="com.sue.cache" />
        1. 在springboot的啟動(dòng)類(lèi)上加上@ComponentScan注解,例如:
        @ComponentScan(basePackages = "com.sue.cache")
        @SpringBootApplication
        public class Application {

            public static void main(String[] args) {
                new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
            }
        }
        1. 直接在SpringBootApplication注解上加,它支持ComponentScan功能:
        @SpringBootApplication(scanBasePackages = "com.sue.cache")
        public class Application {
            
            public static void main(String[] args) {
                new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
            }
        }

        當(dāng)然,如果你需要掃描的類(lèi)跟springboot的入口類(lèi),在同一級(jí)或者子級(jí)的包下面,無(wú)需指定scanBasePackages參數(shù),spring默認(rèn)會(huì)從入口類(lèi)的同一級(jí)或者子級(jí)的包去找。

        @SpringBootApplication
        public class Application {
            
            public static void main(String[] args) {
                new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
            }
        }

        此外,除了上述四種@Component注解之外,springboot還增加了@RestController注解,它是一種特殊的@Controller注解,所以也是@Component注解。

        @RestController還支持@ResponseBody注解的功能,即將接口響應(yīng)數(shù)據(jù)的格式自動(dòng)轉(zhuǎn)換成json。

        @Component系列注解已經(jīng)讓我們愛(ài)不釋手了,它目前是我們?nèi)粘9ぷ髦凶疃嗟亩xbean的方式。

        3. JavaConfig

        @Component系列注解雖說(shuō)使用起來(lái)非常方便,但是bean的創(chuàng)建過(guò)程完全交給spring容器來(lái)完成,我們沒(méi)辦法自己控制。

        spring從3.0以后,開(kāi)始支持JavaConfig的方式定義bean。它可以看做spring的配置文件,但并非真正的配置文件,我們需要通過(guò)編碼java代碼的方式創(chuàng)建bean。例如:

        @Configuration
        public class MyConfiguration {

            @Bean
            public Person person() {
                return new Person();
            }
        }

        在JavaConfig類(lèi)上加@Configuration注解,相當(dāng)于配置了<beans>標(biāo)簽。而在方法上加@Bean注解,相當(dāng)于配置了<bean>標(biāo)簽。

        此外,springboot還引入了一些列的@Conditional注解,用來(lái)控制bean的創(chuàng)建。

        @Configuration
        public class MyConfiguration {

            @ConditionalOnClass(Country.class)
            @Bean
            public Person person() 
        {
                return new Person();
            }
        }

        @ConditionalOnClass注解的功能是當(dāng)項(xiàng)目中存在Country類(lèi)時(shí),才實(shí)例化Person類(lèi)。換句話說(shuō)就是,如果項(xiàng)目中不存在Country類(lèi),就不實(shí)例化Person類(lèi)。

        這個(gè)功能非常有用,相當(dāng)于一個(gè)開(kāi)關(guān)控制著Person類(lèi),只有滿足一定條件才能實(shí)例化。

        spring中使用比較多的Conditional還有:

        • ConditionalOnBean
        • ConditionalOnProperty
        • ConditionalOnMissingClass
        • ConditionalOnMissingBean
        • ConditionalOnWebApplication

        下面用一張圖整體認(rèn)識(shí)一下@Conditional家族:

        nice,有了這些功能,我們終于可以告別麻煩的xml時(shí)代了。

        4. Import注解

        通過(guò)前面介紹的@Configuration和@Bean相結(jié)合的方式,我們可以通過(guò)代碼定義bean。但這種方式有一定的局限性,它只能創(chuàng)建該類(lèi)中定義的bean實(shí)例,不能創(chuàng)建其他類(lèi)的bean實(shí)例,如果我們想創(chuàng)建其他類(lèi)的bean實(shí)例該怎么辦呢?

        這時(shí)可以使用@Import注解導(dǎo)入。

        4.1 普通類(lèi)

        spring4.2之后@Import注解可以實(shí)例化普通類(lèi)的bean實(shí)例。例如:

        先定義了Role類(lèi):

        @Data
        public class Role {
            private Long id;
            private String name;
        }

        接下來(lái)使用@Import注解導(dǎo)入Role類(lèi):

        @Import(Role.class)
        @Configuration
        public class MyConfig 
        {
        }

        然后在調(diào)用的地方通過(guò)@Autowired注解注入所需的bean。

        @RequestMapping("/")
        @RestController
        public class TestController {

            @Autowired
            private Role role;

            @GetMapping("/test")
            public String test() {
                System.out.println(role);
                return "test";
            }
        }

        聰明的你可能會(huì)發(fā)現(xiàn),我沒(méi)有在任何地方定義過(guò)Role的bean,但spring卻能自動(dòng)創(chuàng)建該類(lèi)的bean實(shí)例,這是為什么呢?

        這也許正是@Import注解的強(qiáng)大之處。

        此時(shí),有些朋友可能會(huì)問(wèn):@Import注解能定義單個(gè)類(lèi)的bean,但如果有多個(gè)類(lèi)需要定義bean該怎么辦呢?

        恭喜你,這是個(gè)好問(wèn)題,因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">@Import注解也支持。

        @Import({Role.classUser.class})
        @Configuration
        public class MyConfig 
        {
        }

        甚至,如果你想偷懶,不想寫(xiě)這種MyConfig類(lèi),springboot也歡迎。

        @Import({Role.class, User.class})
        @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
                DataSourceTransactionManagerAutoConfiguration.class})
        public class Application {

            public static void main(String[] args) {
                new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
            }
        }

        可以將@Import加到springboot的啟動(dòng)類(lèi)上。

        這樣也能生效?

        springboot的啟動(dòng)類(lèi)一般都會(huì)加@SpringBootApplication注解,該注解上加了@SpringBootConfiguration注解。

        而@SpringBootConfiguration注解,上面又加了@Configuration注解所以,springboot啟動(dòng)類(lèi)本身帶有@Configuration注解的功能。

        意不意外?驚不驚喜?

        4.2 Configuration類(lèi)

        上面介紹了@Import注解導(dǎo)入普通類(lèi)的方法,它同時(shí)也支持導(dǎo)入Configuration類(lèi)。

        先定義一個(gè)Configuration類(lèi):

        @Configuration
        public class MyConfig2 {

            @Bean
            public User user() {
                return  new User();
            }

            @Bean
            public Role role() {
                return new Role();
            }
        }

        然后在另外一個(gè)Configuration類(lèi)中引入前面的Configuration類(lèi):

        @Import({MyConfig2.class})
        @Configuration
        public class MyConfig 
        {
        }

        這種方式,如果MyConfig2類(lèi)已經(jīng)在spring指定的掃描目錄或者子目錄下,則MyConfig類(lèi)會(huì)顯得有點(diǎn)多余。因?yàn)镸yConfig2類(lèi)本身就是一個(gè)配置類(lèi),它里面就能定義bean。

        但如果MyConfig2類(lèi)不在指定的spring掃描目錄或者子目錄下,則通過(guò)MyConfig類(lèi)的導(dǎo)入功能,也能把MyConfig2類(lèi)識(shí)別成配置類(lèi)。這就有點(diǎn)厲害了喔。

        其實(shí)下面還有更高端的玩法。

        swagger作為一個(gè)優(yōu)秀的文檔生成框架,在spring項(xiàng)目中越來(lái)越受歡迎。接下來(lái),我們以swagger2為例,介紹一下它是如何導(dǎo)入相關(guān)類(lèi)的。

        眾所周知,我們引入swagger相關(guān)jar包之后,只需要在springboot的啟動(dòng)類(lèi)上加上@EnableSwagger2注解,就能開(kāi)啟swagger的功能。

        其中@EnableSwagger2注解中導(dǎo)入了Swagger2DocumentationConfiguration類(lèi)。

        該類(lèi)是一個(gè)Configuration類(lèi),它又導(dǎo)入了另外兩個(gè)類(lèi):

        • SpringfoxWebMvcConfiguration
        • SwaggerCommonConfiguration

        SpringfoxWebMvcConfiguration類(lèi)又會(huì)導(dǎo)入新的Configuration類(lèi),并且通過(guò)@ComponentScan注解掃描了一些其他的路徑。

        SwaggerCommonConfiguration同樣也通過(guò)@ComponentScan注解掃描了一些額外的路徑。

        如此一來(lái),我們通過(guò)一個(gè)簡(jiǎn)單的@EnableSwagger2注解,就能輕松的導(dǎo)入swagger所需的一系列bean,并且擁有swagger的功能。

        還有什么好說(shuō)的,狂起點(diǎn)贊,簡(jiǎn)直完美。

        4.3 ImportSelector

        上面提到的Configuration類(lèi),它的功能非常強(qiáng)大。但怎么說(shuō)呢,它不太適合加復(fù)雜的判斷條件,根據(jù)某些條件定義這些bean,根據(jù)另外的條件定義那些bean。

        那么,這種需求該怎么實(shí)現(xiàn)呢?

        這時(shí)就可以使用ImportSelector接口了。

        首先定義一個(gè)類(lèi)實(shí)現(xiàn)ImportSelector接口:

        public class DataImportSelector implements ImportSelector {
            @Override
            public String[] selectImports(AnnotationMetadata importingClassMetadata) {
                return new String[]{"com.sue.async.service.User""com.sue.async.service.Role"};
            }
        }

        重寫(xiě)selectImports方法,在該方法中指定需要定義bean的類(lèi)名,注意要包含完整路徑,而非相對(duì)路徑。

        然后在MyConfig類(lèi)上@Import導(dǎo)入這個(gè)類(lèi)即可:

        @Import({DataImportSelector.class})
        @Configuration
        public class MyConfig 
        {
        }

        朋友們是不是又發(fā)現(xiàn)了一個(gè)新大陸?

        不過(guò),這個(gè)注解還有更牛逼的用途。

        @EnableAutoConfiguration注解中導(dǎo)入了AutoConfigurationImportSelector類(lèi),并且里面包含系統(tǒng)參數(shù)名稱:spring.boot.enableautoconfiguration。AutoConfigurationImportSelector類(lèi)實(shí)現(xiàn)了ImportSelector接口。

        并且重寫(xiě)了selectImports方法,該方法會(huì)根據(jù)某些注解去找所有需要?jiǎng)?chuàng)建bean的類(lèi)名,然后返回這些類(lèi)名。其中在查找這些類(lèi)名之前,先調(diào)用isEnabled方法,判斷是否需要繼續(xù)查找。該方法會(huì)根據(jù)ENABLED_OVERRIDE_PROPERTY的值來(lái)作為判斷條件。而這個(gè)值就是spring.boot.enableautoconfiguration。

        換句話說(shuō),這里能根據(jù)系統(tǒng)參數(shù)控制bean是否需要被實(shí)例化,優(yōu)秀。

        我個(gè)人認(rèn)為實(shí)現(xiàn)ImportSelector接口的好處主要有以下兩點(diǎn):

        1. 把某個(gè)功能的相關(guān)類(lèi),可以放到一起,方面管理和維護(hù)。
        2. 重寫(xiě)selectImports方法時(shí),能夠根據(jù)條件判斷某些類(lèi)是否需要被實(shí)例化,或者某個(gè)條件實(shí)例化這些bean,其他的條件實(shí)例化那些bean等。我們能夠非常靈活的定制化bean的實(shí)例化。

        4.4 ImportBeanDefinitionRegistrar

        我們通過(guò)上面的這種方式,確實(shí)能夠非常靈活的自定義bean。

        但它的自定義能力,還是有限的,它沒(méi)法自定義bean的名稱和作用域等屬性。

        有需求,就有解決方案。

        接下來(lái),我們一起看看ImportBeanDefinitionRegistrar接口的神奇之處。

        先定義CustomImportSelector類(lèi)實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口:

        public class CustomImportSelector implements ImportBeanDefinitionRegistrar {

            @Override
            public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
                RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);
                registry.registerBeanDefinition("role", roleBeanDefinition);

                RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
                userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
                registry.registerBeanDefinition("user", userBeanDefinition);
            }
        }

        重寫(xiě)registerBeanDefinitions方法,在該方法中我們可以獲取BeanDefinitionRegistry對(duì)象,通過(guò)它去注冊(cè)bean。不過(guò)在注冊(cè)bean之前,我們先要?jiǎng)?chuàng)建BeanDefinition對(duì)象,它里面可以自定義bean的名稱、作用域等很多參數(shù)。

        然后在MyConfig類(lèi)上導(dǎo)入上面的類(lèi):

        @Import({CustomImportSelector.class})
        @Configuration
        public class MyConfig 
        {
        }

        我們所熟悉的fegin功能,就是使用ImportBeanDefinitionRegistrar接口實(shí)現(xiàn)的:具體細(xì)節(jié)就不多說(shuō)了,有興趣的朋友可以加我微信找我私聊。

        5. PostProcessor

        除此之外,spring還提供了專門(mén)注冊(cè)bean的接口:BeanDefinitionRegistryPostProcessor。

        該接口的方法postProcessBeanDefinitionRegistry上有這樣一段描述:修改應(yīng)用程序上下文的內(nèi)部bean定義注冊(cè)表標(biāo)準(zhǔn)初始化。所有常規(guī)bean定義都將被加載,但是還沒(méi)有bean被實(shí)例化。這允許進(jìn)一步添加在下一個(gè)后處理階段開(kāi)始之前定義bean。

        如果用這個(gè)接口來(lái)定義bean,我們要做的事情就變得非常簡(jiǎn)單了。只需定義一個(gè)類(lèi)實(shí)現(xiàn)BeanDefinitionRegistryPostProcessor接口。

        @Component
        public class MyRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
            @Override
            public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
                RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);
                registry.registerBeanDefinition("role", roleBeanDefinition);

                RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
                userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
                registry.registerBeanDefinition("user", userBeanDefinition);
            }

            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            }
        }

        重寫(xiě)postProcessBeanDefinitionRegistry方法,在該方法中能夠獲取BeanDefinitionRegistry對(duì)象,它負(fù)責(zé)bean的注冊(cè)工作。

        不過(guò)細(xì)心的朋友可能會(huì)發(fā)現(xiàn),里面還多了一個(gè)postProcessBeanFactory方法,沒(méi)有做任何實(shí)現(xiàn)。

        這個(gè)方法其實(shí)是它的父接口:BeanFactoryPostProcessor里的方法。

        在應(yīng)用程序上下文的標(biāo)準(zhǔn)bean工廠之后修改其內(nèi)部bean工廠初始化。所有bean定義都已加載,但沒(méi)有bean將被實(shí)例化。這允許重寫(xiě)或添加屬性甚至可以初始化bean。

        @Component
        public class MyPostProcessor implements BeanFactoryPostProcessor {

            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
                DefaultListableBeanFactory registry = (DefaultListableBeanFactory)beanFactory;
                RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);
                registry.registerBeanDefinition("role", roleBeanDefinition);

                RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
                userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
                registry.registerBeanDefinition("user", userBeanDefinition);
            }
        }

        既然這兩個(gè)接口都能注冊(cè)bean,那么他們有什么區(qū)別?

        • BeanDefinitionRegistryPostProcessor 更側(cè)重于bean的注冊(cè)
        • BeanFactoryPostProcessor 更側(cè)重于對(duì)已經(jīng)注冊(cè)的bean的屬性進(jìn)行修改,雖然也可以注冊(cè)bean。

        此時(shí),有些朋友可能會(huì)問(wèn):既然拿到BeanDefinitionRegistry對(duì)象就能注冊(cè)bean,那通過(guò)BeanFactoryAware的方式是不是也能注冊(cè)bean呢?

        從下面這張圖能夠看出DefaultListableBeanFactory就實(shí)現(xiàn)了BeanDefinitionRegistry接口。

        這樣一來(lái),我們?nèi)绻軌颢@取DefaultListableBeanFactory對(duì)象的實(shí)例,然后調(diào)用它的注冊(cè)方法,不就可以注冊(cè)bean了?

        說(shuō)時(shí)遲那時(shí)快,定義一個(gè)類(lèi)實(shí)現(xiàn)BeanFactoryAware接口:

        @Component
        public class BeanFactoryRegistry implements BeanFactoryAware {
            @Override
            public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
                DefaultListableBeanFactory registry = (DefaultListableBeanFactory) beanFactory;
                RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class);
                registry.registerBeanDefinition("user", rootBeanDefinition);

                RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
                userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
                registry.registerBeanDefinition("user", userBeanDefinition);
            }
        }

        重寫(xiě)setBeanFactory方法,在該方法中能夠獲取BeanFactory對(duì)象,它能夠強(qiáng)制轉(zhuǎn)換成DefaultListableBeanFactory對(duì)象,然后通過(guò)該對(duì)象的實(shí)例注冊(cè)bean。

        當(dāng)你滿懷喜悅的運(yùn)行項(xiàng)目時(shí),發(fā)現(xiàn)竟然報(bào)錯(cuò)了:

        為什么會(huì)報(bào)錯(cuò)?

        spring中bean的創(chuàng)建過(guò)程順序大致如下:BeanFactoryAware接口是在bean創(chuàng)建成功,并且完成依賴注入之后,在真正初始化之前才被調(diào)用的。在這個(gè)時(shí)候去注冊(cè)bean意義不大,因?yàn)檫@個(gè)接口是給我們獲取bean的,并不建議去注冊(cè)bean,會(huì)引發(fā)很多問(wèn)題。

        此外,ApplicationContextRegistry和ApplicationListener接口也有類(lèi)似的問(wèn)題,我們可以用他們獲取bean,但不建議用它們注冊(cè)bean。

        往期熱門(mén)文章:

        1、IDEA的5大調(diào)試技巧,你會(huì)廢嗎?
        2、基于 SpringBoot + Vue 的前后端分離的考試系統(tǒng)
        3、國(guó)內(nèi)首家!攜程周三、周五可在家“躺平”:76%員工主動(dòng)報(bào)名 !網(wǎng)友:我酸了
        4、MySQL 大批量插入,如何過(guò)濾掉重復(fù)數(shù)據(jù)?
        5、Spring官方為什么建議構(gòu)造器注入?
        6、字節(jié)教育大裁員:有員工賠了一套首付款!
        7、14 個(gè)經(jīng)典的 Linux 終端命令行,這些工具堪稱神器!
        8、Java 8 失寵!開(kāi)發(fā)人員向 Java 11 轉(zhuǎn)移...
        9、18 款頂級(jí)代碼生成器平臺(tái)!
        10、為什么現(xiàn)在我特討厭 Safari 瀏覽器?

        瀏覽 16
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. se婷婷 | 天天黄色视频 | 成人免费ZN码婬片在线观看免费 | 欧美性受ⅩXXX猛交XXXXX国产 | 男同桌脱我内裤往里灌水小说 |