2018-11-19 spring中bean的作用域與生命周期

在Spring中,那些組成應(yīng)用程序的主體及由Spring IoC容器所管理的對(duì)象,被稱(chēng)之為bean。簡(jiǎn)單地講,bean就是由IoC容器初始化、裝配及管理的對(duì)象,除此之外,bean就與應(yīng)用程序中的其他對(duì)象沒(méi)有什么區(qū)別了。而bean的定義以及bean相互間的依賴(lài)關(guān)系將通過(guò)配置元數(shù)據(jù)來(lái)描述。

Spring中的bean默認(rèn)都是單例的,這些單例Bean在多線程程序下如何保證線程安全呢?例如對(duì)于Web應(yīng)用來(lái)說(shuō),Web容器對(duì)于每個(gè)用戶(hù)請(qǐng)求都創(chuàng)建一個(gè)單獨(dú)的Sevlet線程來(lái)處理請(qǐng)求,引入Spring框架之后,每個(gè)Action都是單例的,那么對(duì)于Spring托管的單例Service Bean,如何保證其安全呢? Spring的單例是基于BeanFactory也就是Spring容器的,單例Bean在此容器內(nèi)只有一個(gè),Java的單例是基于JVM,每個(gè)JVM內(nèi)只有一個(gè)實(shí)例。

1、bean的作用域

創(chuàng)建一個(gè)bean定義,其實(shí)質(zhì)是用該bean定義對(duì)應(yīng)的類(lèi)來(lái)創(chuàng)建真正實(shí)例的“配方”。把bean定義看成一個(gè)配方很有意義,它與class很類(lèi)似,只根據(jù)一張“處方”就可以創(chuàng)建多個(gè)實(shí)例。不僅可以控制注入到對(duì)象中的各種依賴(lài)和配置值,還可以控制該對(duì)象的作用域。這樣可以靈活選擇所建對(duì)象的作用域,而不必在Java Class級(jí)定義作用域。Spring Framework支持五種作用域,分別闡述如下表。

五種作用域中,request、session和global session三種作用域僅在基于web的應(yīng)用中使用(不必關(guān)心你所采用的是什么web應(yīng)用框架),只能用在基于web的Spring ApplicationContext環(huán)境。

(1)當(dāng)一個(gè)bean的作用域?yàn)镾ingleton,那么Spring IoC容器中只會(huì)存在一個(gè)共享的bean實(shí)例,并且所有對(duì)bean的請(qǐng)求,只要id與該bean定義相匹配,則只會(huì)返回bean的同一實(shí)例。Singleton是單例類(lèi)型,就是在創(chuàng)建起容器時(shí)就同時(shí)自動(dòng)創(chuàng)建了一個(gè)bean的對(duì)象,不管你是否使用,他都存在了,每次獲取到的對(duì)象都是同一個(gè)對(duì)象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中將bean定義成singleton,可以這樣配置:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

1

(2)當(dāng)一個(gè)bean的作用域?yàn)镻rototype,表示一個(gè)bean定義對(duì)應(yīng)多個(gè)對(duì)象實(shí)例。Prototype作用域的bean會(huì)導(dǎo)致在每次對(duì)該bean請(qǐng)求(將其注入到另一個(gè)bean中,或者以程序的方式調(diào)用容器的getBean()方法)時(shí)都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例。Prototype是原型類(lèi)型,它在我們創(chuàng)建容器的時(shí)候并沒(méi)有實(shí)例化,而是當(dāng)我們獲取bean的時(shí)候才會(huì)去創(chuàng)建一個(gè)對(duì)象,而且我們每次獲取到的對(duì)象都不是同一個(gè)對(duì)象。根據(jù)經(jīng)驗(yàn),對(duì)有狀態(tài)的bean應(yīng)該使用prototype作用域,而對(duì)無(wú)狀態(tài)的bean則應(yīng)該使用singleton作用域。在XML中將bean定義成prototype,可以這樣配置:

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>

或者

<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

(3)當(dāng)一個(gè)bean的作用域?yàn)镽equest,表示在一次HTTP請(qǐng)求中,一個(gè)bean定義對(duì)應(yīng)一個(gè)實(shí)例;即每個(gè)HTTP請(qǐng)求都會(huì)有各自的bean實(shí)例,它們依據(jù)某個(gè)bean定義創(chuàng)建而成。該作用域僅在基于web的Spring ApplicationContext情形下有效??紤]下面bean定義:

<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

1

針對(duì)每次HTTP請(qǐng)求,Spring容器會(huì)根據(jù)loginAction bean的定義創(chuàng)建一個(gè)全新的LoginAction bean實(shí)例,且該loginAction bean實(shí)例僅在當(dāng)前HTTP request內(nèi)有效,因此可以根據(jù)需要放心的更改所建實(shí)例的內(nèi)部狀態(tài),而其他請(qǐng)求中根據(jù)loginAction bean定義創(chuàng)建的實(shí)例,將不會(huì)看到這些特定于某個(gè)請(qǐng)求的狀態(tài)變化。當(dāng)處理請(qǐng)求結(jié)束,request作用域的bean實(shí)例將被銷(xiāo)毀。

(4)當(dāng)一個(gè)bean的作用域?yàn)镾ession,表示在一個(gè)HTTP Session中,一個(gè)bean定義對(duì)應(yīng)一個(gè)實(shí)例。該作用域僅在基于web的Spring ApplicationContext情形下有效。考慮下面bean定義:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

1

針對(duì)某個(gè)HTTP Session,Spring容器會(huì)根據(jù)userPreferences bean定義創(chuàng)建一個(gè)全新的userPreferences bean實(shí)例,且該userPreferences bean僅在當(dāng)前HTTP Session內(nèi)有效。與request作用域一樣,可以根據(jù)需要放心的更改所創(chuàng)建實(shí)例的內(nèi)部狀態(tài),而別的HTTP Session中根據(jù)userPreferences創(chuàng)建的實(shí)例,將不會(huì)看到這些特定于某個(gè)HTTP Session的狀態(tài)變化。當(dāng)HTTP Session最終被廢棄的時(shí)候,在該HTTP Session作用域內(nèi)的bean也會(huì)被廢棄掉。

(5)當(dāng)一個(gè)bean的作用域?yàn)镚lobal Session,表示在一個(gè)全局的HTTP Session中,一個(gè)bean定義對(duì)應(yīng)一個(gè)實(shí)例。典型情況下,僅在使用portlet context的時(shí)候有效。該作用域僅在基于web的Spring ApplicationContext情形下有效。考慮下面bean定義:

<bean id="user" class="com.foo.Preferences "scope="globalSession"/>

1

global session作用域類(lèi)似于標(biāo)準(zhǔn)的HTTP Session作用域,不過(guò)僅僅在基于portlet的web應(yīng)用中才有意義。Portlet規(guī)范定義了全局Session的概念,它被所有構(gòu)成某個(gè)portlet web應(yīng)用的各種不同的portlet所共享。在global session作用域中定義的bean被限定于全局portlet Session的生命周期范圍內(nèi)。

2、bean的生命周期

Spring中bean的實(shí)例化過(guò)程(不好意思,我盜圖了):

與上圖類(lèi)似,bean的生命周期流程圖:

Bean實(shí)例生命周期的執(zhí)行過(guò)程如下:

Spring對(duì)bean進(jìn)行實(shí)例化,默認(rèn)bean是單例;

Spring對(duì)bean進(jìn)行依賴(lài)注入;

如果bean實(shí)現(xiàn)了BeanNameAware接口,spring將bean的id傳給setBeanName()方法;

如果bean實(shí)現(xiàn)了BeanFactoryAware接口,spring將調(diào)用setBeanFactory方法,將BeanFactory實(shí)例傳進(jìn)來(lái);

如果bean實(shí)現(xiàn)了ApplicationContextAware接口,它的setApplicationContext()方法將被調(diào)用,將應(yīng)用上下文的引用傳入到bean中;

如果bean實(shí)現(xiàn)了BeanPostProcessor接口,它的postProcessBeforeInitialization方法將被調(diào)用;

如果bean實(shí)現(xiàn)了InitializingBean接口,spring將調(diào)用它的afterPropertiesSet接口方法,類(lèi)似的如果bean使用了init-method屬性聲明了初始化方法,該方法也會(huì)被調(diào)用;

如果bean實(shí)現(xiàn)了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法將被調(diào)用;

此時(shí)bean已經(jīng)準(zhǔn)備就緒,可以被應(yīng)用程序使用了,他們將一直駐留在應(yīng)用上下文中,直到該應(yīng)用上下文被銷(xiāo)毀;

若bean實(shí)現(xiàn)了DisposableBean接口,spring將調(diào)用它的distroy()接口方法。同樣的,如果bean使用了destroy-method屬性聲明了銷(xiāo)毀方法,則該方法被調(diào)用;

其實(shí)很多時(shí)候我們并不會(huì)真的去實(shí)現(xiàn)上面說(shuō)描述的那些接口,那么下面我們就除去那些接口,針對(duì)bean的單例和非單例來(lái)描述下bean的生命周期:

2.1 單例管理的對(duì)象

當(dāng)scope=”singleton”,即默認(rèn)情況下,會(huì)在啟動(dòng)容器時(shí)(即實(shí)例化容器時(shí))時(shí)實(shí)例化。但我們可以指定Bean節(jié)點(diǎn)的lazy-init=”true”來(lái)延遲初始化bean,這時(shí)候,只有在第一次獲取bean時(shí)才會(huì)初始化bean,即第一次請(qǐng)求該bean時(shí)才初始化。如下配置:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/>

1

如果想對(duì)所有的默認(rèn)單例bean都應(yīng)用延遲初始化,可以在根節(jié)點(diǎn)beans設(shè)置default-lazy-init屬性為true,如下所示:

<beans default-lazy-init="true" …>

1

默認(rèn)情況下,Spring在讀取xml文件的時(shí)候,就會(huì)創(chuàng)建對(duì)象。在創(chuàng)建對(duì)象的時(shí)候先調(diào)用構(gòu)造器,然后調(diào)用init-method屬性值中所指定的方法。對(duì)象在被銷(xiāo)毀的時(shí)候,會(huì)調(diào)用destroy-method屬性值中所指定的方法(例如調(diào)用Container.destroy()方法的時(shí)候)。寫(xiě)一個(gè)測(cè)試類(lèi),代碼如下:

public class LifeBean {

private String name; 

public LifeBean(){ 

    System.out.println("LifeBean()構(gòu)造函數(shù)"); 

} 

public String getName() { 

    return name; 

} 

public void setName(String name) { 

    System.out.println("setName()"); 

    this.name = name; 

} 

public void init(){ 

    System.out.println("this is init of lifeBean"); 

} 

public void destory(){ 

    System.out.println("this is destory of lifeBean " + this); 

} 

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

life.xml配置如下:

<bean id="life_singleton" class="com.bean.LifeBean" scope="singleton"

        init-method="init" destroy-method="destory" lazy-init="true"/>

1

2

測(cè)試代碼如下:

public class LifeTest {

@Test

public void test() {

    AbstractApplicationContext container =

    new ClassPathXmlApplicationContext("life.xml");

    LifeBean life1 = (LifeBean)container.getBean("life");

    System.out.println(life1);

    container.close();

}

}

1

2

3

4

5

6

7

8

9

10

運(yùn)行結(jié)果如下:

LifeBean()構(gòu)造函數(shù)

this is init of lifeBean

com.bean.LifeBean@573f2bb1

……

this is destory of lifeBean com.bean.LifeBean@573f2bb1

1

2

3

4

5

2.2 非單例管理的對(duì)象

當(dāng)scope=”prototype”時(shí),容器也會(huì)延遲初始化bean,Spring讀取xml文件的時(shí)候,并不會(huì)立刻創(chuàng)建對(duì)象,而是在第一次請(qǐng)求該bean時(shí)才初始化(如調(diào)用getBean方法時(shí))。在第一次請(qǐng)求每一個(gè)prototype的bean時(shí),Spring容器都會(huì)調(diào)用其構(gòu)造器創(chuàng)建這個(gè)對(duì)象,然后調(diào)用init-method屬性值中所指定的方法。對(duì)象銷(xiāo)毀的時(shí)候,Spring容器不會(huì)幫我們調(diào)用任何方法,因?yàn)槭欠菃卫@個(gè)類(lèi)型的對(duì)象有很多個(gè),Spring容器一旦把這個(gè)對(duì)象交給你之后,就不再管理這個(gè)對(duì)象了。

為了測(cè)試prototype bean的生命周期life.xml配置如下:

<bean id="life_prototype" class="com.bean.LifeBean" scope="prototype" init-method="init" destroy-method="destory"/>

1

測(cè)試程序如下:

public class LifeTest {

@Test

public void test() {

    AbstractApplicationContext container = new ClassPathXmlApplicationContext("life.xml");

    LifeBean life1 = (LifeBean)container.getBean("life_singleton");

    System.out.println(life1);

    LifeBean life3 = (LifeBean)container.getBean("life_prototype");

    System.out.println(life3);

    container.close();

}

}

1

2

3

4

5

6

7

8

9

10

11

12

運(yùn)行結(jié)果如下:

LifeBean()構(gòu)造函數(shù)

this is init of lifeBean

com.bean.LifeBean@573f2bb1

LifeBean()構(gòu)造函數(shù)

this is init of lifeBean

com.bean.LifeBean@5ae9a829

……

this is destory of lifeBean com.bean.LifeBean@573f2bb1

1

2

3

4

5

6

7

8

可以發(fā)現(xiàn),對(duì)于作用域?yàn)閜rototype的bean,其destroy方法并沒(méi)有被調(diào)用。如果bean的scope設(shè)為prototype時(shí),當(dāng)容器關(guān)閉時(shí),destroy方法不會(huì)被調(diào)用。對(duì)于prototype作用域的bean,有一點(diǎn)非常重要,那就是Spring不能對(duì)一個(gè)prototype bean的整個(gè)生命周期負(fù)責(zé):容器在初始化、配置、裝飾或者是裝配完一個(gè)prototype實(shí)例后,將它交給客戶(hù)端,隨后就對(duì)該prototype實(shí)例不聞不問(wèn)了。不管何種作用域,容器都會(huì)調(diào)用所有對(duì)象的初始化生命周期回調(diào)方法。但對(duì)prototype而言,任何配置好的析構(gòu)生命周期回調(diào)方法都將不會(huì)被調(diào)用。清除prototype作用域的對(duì)象并釋放任何prototype bean所持有的昂貴資源,都是客戶(hù)端代碼的職責(zé)(讓Spring容器釋放被prototype作用域bean占用資源的一種可行方式是,通過(guò)使用bean的后置處理器,該處理器持有要被清除的bean的引用)。談及prototype作用域的bean時(shí),在某些方面你可以將Spring容器的角色看作是Java new操作的替代者,任何遲于該時(shí)間點(diǎn)的生命周期事宜都得交由客戶(hù)端來(lái)處理。

Spring容器可以管理singleton作用域下bean的生命周期,在此作用域下,Spring能夠精確地知道bean何時(shí)被創(chuàng)建,何時(shí)初始化完成,以及何時(shí)被銷(xiāo)毀。而對(duì)于prototype作用域的bean,Spring只負(fù)責(zé)創(chuàng)建,當(dāng)容器創(chuàng)建了bean的實(shí)例后,bean的實(shí)例就交給了客戶(hù)端的代碼管理,Spring容器將不再跟蹤其生命周期,并且不會(huì)管理那些被配置成prototype作用域的bean的生命周期。

2.3 引申

在學(xué)習(xí)Spring IoC過(guò)程中發(fā)現(xiàn),每次產(chǎn)生ApplicationContext工廠的方式是:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

1

這樣產(chǎn)生ApplicationContext就有一個(gè)弊端,每次訪問(wèn)加載bean的時(shí)候都會(huì)產(chǎn)生這個(gè)工廠,所以這里需要解決這個(gè)問(wèn)題。

ApplicationContext是一個(gè)接口,它繼承自BeanFactory接口,除了包含BeanFactory的所有功能之外,在國(guó)際化支持、資源訪問(wèn)(如URL和文件)、事件傳播等方面進(jìn)行了良好的支持。

解決問(wèn)題的方法很簡(jiǎn)單,在web容器啟動(dòng)的時(shí)候?qū)pplicationContext轉(zhuǎn)移到ServletContext中,因?yàn)樵趙eb應(yīng)用中所有的Servlet都共享一個(gè)ServletContext對(duì)象。那么我們就可以利用ServletContextListener去監(jiān)聽(tīng)ServletContext事件,當(dāng)web應(yīng)用啟動(dòng)的是時(shí)候,我們就將ApplicationContext裝載到ServletContext中。 Spring容器底層已經(jīng)為我們想到了這一點(diǎn),在spring-web-xxx-release.jar包中有一個(gè)已經(jīng)實(shí)現(xiàn)了ServletContextListener接口的類(lèi)ContextLoader,其源碼如下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

private ContextLoader contextLoader;

public ContextLoaderListener() {

}

public ContextLoaderListener(WebApplicationContext context) {

    super(context);

}

public void contextInitialized(ServletContextEvent event) {

    this.contextLoader = createContextLoader();

    if (this.contextLoader == null) {

        this.contextLoader = this;

    }

    this.contextLoader.initWebApplicationContext(event.getServletContext());

}

@Deprecated

protected ContextLoader createContextLoader() {

    return null;

}

@Deprecated

public ContextLoader getContextLoader() {

    return this.contextLoader;

}

public void contextDestroyed(ServletContextEvent event) {

    if (this.contextLoader != null) {

    this.contextLoader.closeWebApplicationContext(event.getServletContext());

    }

    ContextCleanupListener.cleanupAttributes(event.getServletContext());

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

這里就監(jiān)聽(tīng)到了servletContext的創(chuàng)建過(guò)程, 那么 這個(gè)類(lèi)又是如何將applicationContext裝入到serveletContext容器中的呢?

this.contextLoader.initWebApplicationContext(event.getServletContext())方法的具體實(shí)現(xiàn)中:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {

    throw new IllegalStateException(

            "Cannot initialize context because there is already a root application context present - " +

            "check whether you have multiple ContextLoader* definitions in your web.xml!");

}

Log logger = LogFactory.getLog(ContextLoader.class);

servletContext.log("Initializing Spring root WebApplicationContext");

if (logger.isInfoEnabled()) {

    logger.info("Root WebApplicationContext: initialization started");

}

long startTime = System.currentTimeMillis();

try {

      // Store context in local instance variable, to guarantee that

      // it is available on ServletContext shutdown.

    if (this.context == null) {

        this.context = createWebApplicationContext(servletContext);

    }

    if (this.context instanceof ConfigurableWebApplicationContext) {

        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;

        if (!cwac.isActive()) {

            // The context has not yet been refreshed -> provide services such as

            // setting the parent context, setting the application context id, etc

            if (cwac.getParent() == null) {

                // The context instance was injected without an explicit parent ->

                // determine parent for root web application context, if any.

                ApplicationContext parent = loadParentContext(servletContext);

                cwac.setParent(parent);

            }

            configureAndRefreshWebApplicationContext(cwac, servletContext);

        }

    }

    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

    ClassLoader ccl = Thread.currentThread().getContextClassLoader();

    if (ccl == ContextLoader.class.getClassLoader()) {

        currentContext = this.context;

    }

    else if (ccl != null) {

        currentContextPerThread.put(ccl, this.context);

    }

    if (logger.isDebugEnabled()) {

        logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +

                WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");

    }

    if (logger.isInfoEnabled()) {

        long elapsedTime = System.currentTimeMillis() - startTime;

        logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");

    }

    return this.context;

}

catch (RuntimeException ex) {

    logger.error("Context initialization failed", ex);

    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);

    throw ex;

}

catch (Error err) {

    logger.error("Context initialization failed", err);

    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);

    throw err;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

這里的重點(diǎn)是servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context),用key:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE value: this.context的形式將applicationContext裝載到servletContext中了。另外從上面的一些注釋我們可以看出: WEB-INF/applicationContext.xml, 如果我們項(xiàng)目中的配置文件不是這么一個(gè)路徑的話 那么我們使用ContextLoaderListener 就會(huì)出問(wèn)題, 所以我們還需要在web.xml中配置我們的applicationContext.xml配置文件的路徑。

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:applicationContext.xml</param-value>

</context-param>

1

2

3

4

5

6

7

8

剩下的就是在項(xiàng)目中開(kāi)始使用 servletContext中裝載的applicationContext對(duì)象了: 那么這里又有一個(gè)問(wèn)題,裝載時(shí)的key是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我們?cè)诖a中真的要使用這個(gè)嗎? 其實(shí)Spring為我們提供了一個(gè)工具類(lèi)WebApplicationContextUtils,接著我們先看下如何使用,然后再去看下這個(gè)工具類(lèi)的源碼:

WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());

1

接著來(lái)看下這個(gè)工具類(lèi)的源碼:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {

return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

}

1

2

3

這里就能很直觀清晰地看到 通過(guò)key值直接獲取到裝載到servletContext中的 applicationContext對(duì)象了。

ContextLoaderListener監(jiān)聽(tīng)器的作用就是啟動(dòng)Web容器時(shí),自動(dòng)裝配ApplicationContext的配置信息,因?yàn)樗鼘?shí)現(xiàn)了ServletContextListener這個(gè)接口,在web.xml配置這個(gè)監(jiān)聽(tīng)器,啟動(dòng)容器時(shí),就會(huì)默認(rèn)執(zhí)行它實(shí)現(xiàn)的方法。在ContextLoaderListener中關(guān)聯(lián)了ContextLoader這個(gè)類(lèi),整個(gè)加載配置過(guò)程由ContextLoader來(lái)完成。


作者:fuzhongmin05

來(lái)源:CSDN

原文:https://blog.csdn.net/fuzhongmin05/article/details/73389779

版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!

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

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

  • 河南眾聚電子商務(wù)有限公司 民營(yíng)20-99人互聯(lián)網(wǎng)、電子商務(wù)、計(jì)算機(jī)軟件、互聯(lián)網(wǎng)、電子商務(wù)、快速消費(fèi)品(食品、飲料、...
    雷爺_fefc閱讀 554評(píng)論 0 0
  • 1.1 spring IoC容器和beans的簡(jiǎn)介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器,...
    simoscode閱讀 6,844評(píng)論 2 22
  • 1.1 Spring IoC容器和bean簡(jiǎn)介 本章介紹了Spring Framework實(shí)現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,665評(píng)論 0 8
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • 文章作者:Tyan博客:noahsnail.com 3.5 Bean scopes When you create...
    SnailTyan閱讀 1,991評(píng)論 0 1

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