大廠常考的Spring面試題

程式設計師大彬發表於2022-01-05

準備了一個月的八股文,經歷了二十幾場面試之後,發現Spring很受面試官青睞。最近有空將Spring常見的面試題總結了一下,希望對大家有所幫助。
文章目錄:

  • Spring的優點
  • Spring 用到了哪些設計模式?
  • 什麼是AOP?
  • AOP有哪些實現方式?
  • JDK動態代理和CGLIB動態代理的區別?
  • Spring AOP相關術語
  • Spring通知有哪些型別?
  • 什麼是IOC?
  • IOC的優點是什麼?
  • 什麼是依賴注入?
  • IOC容器初始化過程?
  • Bean的生命週期
  • BeanFactory和FactoryBean的區別?
  • Bean注入容器有哪些方式?
  • Bean的作用域
  • Spring自動裝配的方式有哪些?
  • @Autowired和@Resource的區別?
  • @Qualifier 註解有什麼作用
  • @Bean和@Component有什麼區別?
  • @Component、@Controller、@Repositor和@Service 的區別?
  • Spring 事務實現方式有哪些?
  • 有哪些事務傳播行為?
  • Spring怎麼解決迴圈依賴的問題?
  • Spring啟動過程
  • Spring 的單例 Bean 是否有執行緒安全問題?

Spring的優點

  • 輕量,基本版本大約2MB。
  • 通過控制反轉和依賴注入實現鬆耦合
  • 支援面向切面的程式設計,並且把應用業務邏輯和系統服務分開。
  • 通過切面和模板減少樣板式程式碼。
  • 方便整合各種優秀框架。內部提供了對各種優秀框架的直接支援(如:Hibernate、MyBatis等)。
  • 方便程式的測試。Spring支援Junit4,新增註解便可以測試Spring程式。

Spring 用到了哪些設計模式?

1、簡單工廠模式:BeanFactory就是簡單工廠模式的體現,根據傳入一個唯一標識來獲得 Bean 物件。

@Override
public Object getBean(String name) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(name);
}

2、工廠方法模式:FactoryBean就是典型的工廠方法模式。spring在使用getBean()呼叫獲得該bean時,會自動呼叫該bean的getObject()方法。每個 Bean 都會對應一個 FactoryBean,如 SqlSessionFactory 對應 SqlSessionFactoryBean

3、單例模式:一個類僅有一個例項,提供一個訪問它的全域性訪問點。Spring 建立 Bean 例項預設是單例的。

4、介面卡模式:SpringMVC中的介面卡HandlerAdatper。由於應用會有多個Controller實現,如果需要直接呼叫Controller方法,那麼需要先判斷是由哪一個Controller處理請求,然後呼叫相應的方法。當增加新的 Controller,需要修改原來的邏輯,違反了開閉原則(對修改關閉,對擴充套件開放)。

為此,Spring提供了一個介面卡介面,每一種 Controller 對應一種 HandlerAdapter 實現類,當請求過來,SpringMVC會呼叫getHandler()獲取相應的Controller,然後獲取該Controller對應的 HandlerAdapter,最後呼叫HandlerAdapterhandle()方法處理請求,實際上呼叫的是Controller的handleRequest()。每次新增新的 Controller 時,只需要增加一個介面卡類就可以,無需修改原有的邏輯。

常用的處理器介面卡:SimpleControllerHandlerAdapterHttpRequestHandlerAdapterAnnotationMethodHandlerAdapter

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

public class HttpRequestHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {//handler是被適配的物件,這裡使用的是物件的介面卡模式
        return (handler instanceof HttpRequestHandler);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }
}

5、代理模式:spring 的 aop 使用了動態代理,有兩種方式JdkDynamicAopProxyCglib2AopProxy

6、觀察者模式:spring 中 observer 模式常用的地方是 listener 的實現,如ApplicationListener

7、模板模式: Spring 中 jdbcTemplatehibernateTemplate 等,就使用到了模板模式。

什麼是AOP?

面向切面程式設計,作為物件導向的一種補充,將公共邏輯(事務管理、日誌、快取等)封裝成切面,跟業務程式碼進行分離,可以減少系統的重複程式碼和降低模組之間的耦合度。切面就是那些與業務無關,但所有業務模組都會呼叫的公共邏輯。

AOP有哪些實現方式?

AOP有兩種實現方式:靜態代理和動態代理。

靜態代理

靜態代理:代理類在編譯階段生成,在編譯階段將通知織入Java位元組碼中,也稱編譯時增強。AspectJ使用的是靜態代理。

缺點:代理物件需要與目標物件實現一樣的介面,並且實現介面的方法,會有冗餘程式碼。同時,一旦介面增加方法,目標物件與代理物件都要維護。

動態代理

動態代理:代理類在程式執行時建立,AOP框架不會去修改位元組碼,而是在記憶體中臨時生成一個代理物件,在執行期間對業務方法進行增強,不會生成新類。

JDK動態代理和CGLIB動態代理的區別?

Spring AOP中的動態代理主要有兩種方式:JDK動態代理和CGLIB動態代理。

JDK動態代理

如果目標類實現了介面,Spring AOP會選擇使用JDK動態代理目標類。代理類根據目標類實現的介面動態生成,不需要自己編寫,生成的動態代理類和目標類都實現相同的介面。JDK動態代理的核心是InvocationHandler介面和Proxy類。

缺點:目標類必須有實現的介面。如果某個類沒有實現介面,那麼這個類就不能用JDK動態代理。

CGLIB來動態代理

通過繼承實現。如果目標類沒有實現介面,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library)可以在執行時動態生成類的位元組碼,動態建立目標類的子類物件,在子類物件中增強目標類。

CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那麼它是無法使用CGLIB做動態代理的。

優點:目標類不需要實現特定的介面,更加靈活。

什麼時候採用哪種動態代理?

  1. 如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP
  2. 如果目標物件實現了介面,可以強制使用CGLIB實現AOP
  3. 如果目標物件沒有實現了介面,必須採用CGLIB庫

兩者的區別

  1. jdk動態代理使用jdk中的類Proxy來建立代理物件,它使用反射技術來實現,不需要匯入其他依賴。cglib需要引入相關依賴:asm.jar,它使用位元組碼增強技術來實現。
  2. 當目標類實現了介面的時候Spring Aop預設使用jdk動態代理方式來增強方法,沒有實現介面的時候使用cglib動態代理方式增強方法。

Spring AOP相關術語

(1)切面(Aspect):切面是通知和切點的結合。通知和切點共同定義了切面的全部內容。

(2)連線點(Join point):指方法,在Spring AOP中,一個連線點總是代表一個方法的執行。連線點是在應用執行過程中能夠插入切面的一個點。這個點可以是呼叫方法時、丟擲異常時、甚至修改一個欄位時。切面程式碼可以利用這些點插入到應用的正常流程之中,並新增新的行為。

(3)通知(Advice):在AOP術語中,切面的工作被稱為通知。

(4)切入點(Pointcut):切點的定義會匹配通知所要織入的一個或多個連線點。我們通常使用明確的類和方法名稱,或是利用正規表示式定義所匹配的類和方法名稱來指定這些切點。

(5)引入(Introduction):引入允許我們向現有類新增新方法或屬性。

(6)目標物件(Target Object): 被一個或者多個切面(aspect)所通知(advise)的物件。它通常是一個代理物件。

(7)織入(Weaving):織入是把切面應用到目標物件並建立新的代理物件的過程。在目標物件的生命週期裡有以下時間點可以進行織入:

  • 編譯期:切面在目標類編譯時被織入。AspectJ的織入編譯器是以這種方式織入切面的。
  • 類載入期:切面在目標類載入到JVM時被織入。需要特殊的類載入器,它可以在目標類被引入應用之前增強該目標類的位元組碼。AspectJ5的載入時織入就支援以這種方式織入切面。
  • 執行期:切面在應用執行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會為目標物件動態地建立一個代理物件。SpringAOP就是以這種方式織入切面。

Spring通知有哪些型別?

在AOP術語中,切面的工作被稱為通知。通知實際上是程式執行時要通過Spring AOP框架來觸發的程式碼段。

Spring切面可以應用5種型別的通知:

  1. 前置通知(Before):在目標方法被呼叫之前呼叫通知功能;
  2. 後置通知(After):在目標方法完成之後呼叫通知,此時不會關心方法的輸出是什麼;
  3. 返回通知(After-returning ):在目標方法成功執行之後呼叫通知;
  4. 異常通知(After-throwing):在目標方法丟擲異常後呼叫通知;
  5. 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的邏輯。

什麼是IOC?

IOC:控制反轉,由Spring容器管理bean的整個生命週期。通過反射實現對其他物件的控制,包括初始化、建立、銷燬等,解放手動建立物件的過程,同時降低類之間的耦合度。

IOC的好處:降低了類之間的耦合,物件建立和初始化交給Spring容器管理,在需要的時候只需向容器進行申請。

IOC的優點是什麼?

  • IOC 和依賴注入降低了應用的程式碼量。
  • 鬆耦合。
  • 支援載入服務時的餓漢式初始化和懶載入。

什麼是依賴注入?

在Spring建立物件的過程中,把物件依賴的屬性注入到物件中。依賴注入主要有兩種方式:構造器注入和屬性注入。

IOC容器初始化過程?

ioc 容器初始化過程:BeanDefinition 的資源定位、解析和註冊。

  1. 從XML中讀取配置檔案。
  2. 將bean標籤解析成 BeanDefinition,如解析 property 元素, 並注入到 BeanDefinition 例項中。
  3. 將 BeanDefinition 註冊到容器 BeanDefinitionMap 中。
  4. BeanFactory 根據 BeanDefinition 的定義資訊建立例項化和初始化 bean。

單例bean的初始化以及依賴注入一般都在容器初始化階段進行,只有懶載入(lazy-init為true)的單例bean是在應用第一次呼叫getBean()時進行初始化和依賴注入。

// AbstractApplicationContext
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

多例bean 在容器啟動時不例項化,即使設定 lazy-init 為 false 也沒用,只有呼叫了getBean()才進行例項化。

loadBeanDefinitions採用了模板模式,具體載入 BeanDefinition 的邏輯由各個子類完成。

Bean的生命週期

1.對Bean進行例項化

2.依賴注入

3.如果Bean實現了BeanNameAware介面,Spring將呼叫setBeanName(),設定 Bean的 id(xml檔案中bean標籤的id)

4.如果Bean實現了BeanFactoryAware介面,Spring將呼叫setBeanFactory()

5.如果Bean實現了ApplicationContextAware介面,Spring容器將呼叫setApplicationContext()

6.如果存在BeanPostProcessor,Spring將呼叫它們的postProcessBeforeInitialization(預初始化)方法,在Bean初始化前對其進行處理

7.如果Bean實現了InitializingBean介面,Spring將呼叫它的afterPropertiesSet方法,然後呼叫xml定義的 init-method 方法,兩個方法作用類似,都是在初始化 bean 的時候執行

8.如果存在BeanPostProcessor,Spring將呼叫它們的postProcessAfterInitialization(後初始化)方法,在Bean初始化後對其進行處理

9.Bean初始化完成,供應用使用,直到應用被銷燬

10.如果Bean實現了DisposableBean介面,Spring將呼叫它的destory方法,然後呼叫在xml中定義的 destory-method方法,這兩個方法作用類似,都是在Bean例項銷燬前執行。

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

BeanFactory和FactoryBean的區別?

BeanFactory:管理Bean的容器,Spring中生成的Bean都是由這個介面的實現來管理的。

FactoryBean:通常是用來建立比較複雜的bean,一般的bean 直接用xml配置即可,但如果一個bean的建立過程中涉及到很多其他的bean 和複雜的邏輯,直接用xml配置比較麻煩,這時可以考慮用FactoryBean,可以隱藏例項化複雜Bean的細節。

當配置檔案中bean標籤的class屬性配置的實現類是FactoryBean時,通過 getBean()方法返回的不是FactoryBean本身,而是呼叫FactoryBean#getObject()方法所返回的物件,相當於FactoryBean#getObject()代理了getBean()方法。如果想得到FactoryBean必須使用 '&' + beanName 的方式獲取。

Mybatis 提供了 SqlSessionFactoryBean,可以簡化 SqlSessionFactory 的配置:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }
    
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    //複雜邏輯
  }
    
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }
}

在 xml 配置 SqlSessionFactoryBean:

<bean id="tradeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="trade" />
    <property name="mapperLocations" value="classpath*:mapper/trade/*Mapper.xml" />
    <property name="configLocation" value="classpath:mybatis-config.xml" />
    <property name="typeAliasesPackage" value="com.bytebeats.mybatis3.domain.trade" />
</bean>

Spring 將會在應用啟動時建立 SqlSessionFactory,並使用 sqlSessionFactory 這個名字儲存起來。

Bean注入容器有哪些方式?

將普通類交給Spring容器管理,通常有以下方法:

1、使用@Configuration@Bean註解

2、使用@Controller@Service@Repository@Component 註解標註該類,然後啟用@ComponentScan自動掃描

3、使用@Import 方法。使用@Import註解把bean匯入到當前容器中,程式碼如下:

//@SpringBootApplication
@ComponentScan
/*把用到的資源匯入到當前容器中*/
@Import({Dog.class, Cat.class})
public class App {
 
    public static void main(String[] args) throws Exception {
 
        ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        System.out.println(context.getBean(Dog.class));
        System.out.println(context.getBean(Cat.class));
        context.close();
    }
}

Bean的作用域

1、singleton:單例,Spring中的bean預設都是單例的。

2、prototype:每次請求都會建立一個新的bean例項。

3、request:每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP request內有效。

4、session:每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP session內有效。

5、global-session:全域性session作用域。

Spring自動裝配的方式有哪些?

Spring的自動裝配有三種模式:byType(根據型別),byName(根據名稱)、constructor(根據建構函式)。

byType

找到與依賴型別相同的bean注入到另外的bean中,這個過程需要藉助setter注入來完成,因此必須存在set方法,否則注入失敗。

當xml檔案中存在多個相同型別名稱不同的例項Bean時,Spring容器依賴注入仍然會失敗,因為存在多種適合的選項,Spring容器無法知道該注入那種,此時我們需要為Spring容器提供幫助,指定注入那個Bean例項。可以通過<bean>標籤的autowire-candidate設定為false來過濾那些不需要注入的例項Bean

<bean id="userDao"  class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- autowire-candidate="false" 過濾該型別 -->
<bean id="userDao2" autowire-candidate="false" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- byType 根據型別自動裝配userDao-->
<bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />

byName

將屬性名與bean名稱進行匹配,如果找到則注入依賴bean。

<bean id="userDao"  class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- byName 根據名稱自動裝配,找到UserServiceImpl名為 userDao屬性並注入-->
<bean id="userService" autowire="byName" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />

constructor

存在單個例項則優先按型別進行引數匹配(無論名稱是否匹配),當存在多個型別相同例項時,按名稱優先匹配,如果沒有找到對應名稱,則注入失敗,此時可以使用autowire-candidate=”false” 過濾來解決。

<!--只存在userDao2,userDao3 無法成功注入-->
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<bean id="userDao3" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />

@Autowired 可以傳遞了一個required=false的屬性,false指明當userDao例項存在就注入不存就忽略,如果為true,就必須注入,若userDao例項不存在,就丟擲異常。由於預設情況下@Autowired是按型別匹配的(byType),如果需要按名稱(byName)匹配的話,可以使用@Qualifier註解與@Autowired結合。

public class UserServiceImpl implements UserService {
    //標註成員變數
    @Autowired
    @Qualifier("userDao1")
    private UserDao userDao;   
 }

byName模式 xml 配置:

<!-- 根據@Qualifier("userDao1")自動識別 -->
<bean id="userDao1" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<bean id="userService" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />

@Resource,預設按 byName模式自動注入。@Resource有兩個中重要的屬性:name和type。Spring容器對於@Resource註解的name屬性解析為bean的名字,type屬性則解析為bean的型別。因此使用name屬性,則按byName模式的自動注入策略,如果使用type屬性則按 byType模式自動注入策略。倘若既不指定name也不指定type屬性,Spring容器將通過反射技術預設按byName模式注入。

@Resource(name=“userDao”)
private UserDao  userDao;//用於成員變數

//也可以用於set方法標註
@Resource(name=“userDao”)
public void setUserDao(UserDao userDao) {
   this.userDao= userDao;
}

上述兩種自動裝配的依賴注入並不適合簡單值型別,如int、boolean、long、String以及Enum等,對於這些型別,Spring容器也提供了@Value注入的方式。@Value接收一個String的值,該值指定了將要被注入到內建的java型別屬性值,Spring 容器會做好型別轉換。一般情況下@Value會與properties檔案結合使用。

jdbc.properties檔案如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&allowMultiQueries=true
jdbc.username=root
jdbc.password=root

利用註解@Value獲取jdbc.url和jdbc.username的值,實現如下:

public class UserServiceImpl implements UserService {
    //佔位符方式
    @Value("${jdbc.url}")
    private String url;
    //SpEL表達方式,其中代表xml配置檔案中的id值configProperties
    @Value("#{configProperties['jdbc.username']}")
    private String userName;

}

xml配置檔案:

    <!--基於佔位符方式 配置單個properties -->
    <!--<context:property-placeholder location="conf/jdbc.properties"/>-->
    <!--基於佔位符方式 配置多個properties -->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
        <property name="location" value="conf/jdbc.properties"/>
    </bean>

    <!--基於SpEL表示式 配置多個properties id值為configProperties 提供java程式碼中使用 -->
    <bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:/conf/jdbc.properties</value>
            </list>
        </property>
    </bean>

@Autowired和@Resource的區別?

@Autowired註解是按照型別(byType)裝配依賴物件的,但是存在多個型別⼀致的bean,⽆法通過byType注⼊時,就會再使⽤byName來注⼊,如果還是⽆法判斷注⼊哪個bean則會UnsatisfiedDependencyException。
@Resource會⾸先按照byName來裝配,如果找不到bean,會⾃動byType再找⼀次。

@Qualifier 註解有什麼作用

當需要建立多個相同型別的 bean 並希望僅使用屬性裝配其中一個 bean 時,可以使用@Qualifier 註解和 @Autowired 通過指定應該裝配哪個 bean 來消除歧義。

@Bean和@Component有什麼區別?

都是使用註解定義 Bean。@Bean 是使用 Java 程式碼裝配 Bean,@Component 是自動裝配 Bean。

@Component 註解用在類上,表明一個類會作為元件類,並告知Spring要為這個類建立bean,每個類對應一個 Bean。

@Bean 註解用在方法上,表示這個方法會返回一個 Bean。@Bean 需要在配置類中使用,即類上需要加上@Configuration註解。

@Component
public class Student {
    private String name = "lkm";
 
    public String getName() {
        return name;
    }
}

@Configuration
public class WebSocketConfig {
    @Bean
    public Student student(){
        return new Student();
    }
}

@Bean 註解更加靈活。當需要將第三方類裝配到 Spring 容器中,因為沒辦法原始碼上新增@Component註解,只能使用@Bean 註解的方式,當然也可以使用 xml 的方式。

@Component、@Controller、@Repositor和@Service 的區別?

@Component:最普通的元件,可以被注入到spring容器進行管理。

@Controller:將類標記為 Spring Web MVC 控制器。

@Service:將類標記為業務層元件。

@Repository:將類標記為資料訪問元件,即DAO元件。

Spring 事務實現方式有哪些?

事務就是一系列的操作原子執行。Spring事務機制主要包括宣告式事務和程式設計式事務。

  • 程式設計式事務:通過程式設計的方式管理事務,這種方式帶來了很大的靈活性,但很難維護。
  • 宣告式事務:將事務管理程式碼從業務方法中分離出來,通過aop進行封裝。Spring宣告式事務使得我們無需要去處理獲得連線、關閉連線、事務提交和回滾等這些操作。使用 @Transactional 註解開啟宣告式事務。

@Transactional相關屬性如下:

屬性型別描述
valueString可選的限定描述符,指定使用的事務管理器
propagationenum: Propagation可選的事務傳播行為設定
isolationenum: Isolation可選的事務隔離級別設定
readOnlyboolean讀寫或只讀事務,預設讀寫
timeoutint (in seconds granularity)事務超時時間設定
rollbackForClass物件陣列,必須繼承自Throwable導致事務回滾的異常類陣列
rollbackForClassName類名陣列,必須繼承自Throwable導致事務回滾的異常類名字陣列
noRollbackForClass物件陣列,必須繼承自Throwable不會導致事務回滾的異常類陣列
noRollbackForClassName類名陣列,必須繼承自Throwable不會導致事務回滾的異常類名字陣列

有哪些事務傳播行為?

在TransactionDefinition介面中定義了七個事務傳播行為:

  1. PROPAGATION_REQUIRED如果存在一個事務,則支援當前事務。如果沒有事務則開啟一個新的事務。如果巢狀呼叫的兩個方法都加了事務註解,並且執行在相同執行緒中,則這兩個方法使用相同的事務中。如果執行在不同執行緒中,則會開啟新的事務。
  2. PROPAGATION_SUPPORTS 如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行。
  3. PROPAGATION_MANDATORY 如果已經存在一個事務,支援當前事務。如果不存在事務,則丟擲異常IllegalTransactionStateException
  4. PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。需要使用JtaTransactionManager作為事務管理器。
  5. PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。需要使用JtaTransactionManager作為事務管理器。
  6. PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則丟擲異常。
  7. PROPAGATION_NESTED 如果一個活動的事務存在,則執行在一個巢狀的事務中。如果沒有活動事務, 則按PROPAGATION_REQUIRED 屬性執行。

PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區別:

使用PROPAGATION_REQUIRES_NEW時,內層事務與外層事務是兩個獨立的事務。一旦內層事務進行了提交後,外層事務不能對其進行回滾。兩個事務互不影響。

使用PROPAGATION_NESTED時,外層事務的回滾可以引起內層事務的回滾。而內層事務的異常並不會導致外層事務的回滾,它是一個真正的巢狀事務。

Spring怎麼解決迴圈依賴的問題?

構造器注入的迴圈依賴:Spring處理不了,直接丟擲BeanCurrentlylnCreationException異常。

單例模式下屬性注入的迴圈依賴:通過三級快取處理迴圈依賴。

非單例迴圈依賴:無法處理。

下面分析單例模式下屬性注入的迴圈依賴是怎麼處理的:

首先,Spring單例物件的初始化大略分為三步:

  1. createBeanInstance:例項化bean,使用構造方法建立物件,為物件分配記憶體。
  2. populateBean:進行依賴注入。
  3. initializeBean:初始化bean。

Spring為了解決單例的迴圈依賴問題,使用了三級快取:

singletonObjects:完成了初始化的單例物件map,bean name --> bean instance

earlySingletonObjects :完成例項化未初始化的單例物件map,bean name --> bean instance

singletonFactories : 單例物件工廠map,bean name --> ObjectFactory,單例物件例項化完成之後會加入singletonFactories。

在呼叫createBeanInstance進行例項化之後,會呼叫addSingletonFactory,將單例物件放到singletonFactories中。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

假如A依賴了B的例項物件,同時B也依賴A的例項物件。

  1. A首先完成了例項化,並且將自己新增到singletonFactories中
  2. 接著進行依賴注入,發現自己依賴物件B,此時就嘗試去get(B)
  3. 發現B還沒有被例項化,對B進行例項化
  4. 然後B在初始化的時候發現自己依賴了物件A,於是嘗試get(A),嘗試一級快取singletonObjects和二級快取earlySingletonObjects沒找到,嘗試三級快取singletonFactories,由於A初始化時將自己新增到了singletonFactories,所以B可以拿到A物件,然後將A從三級快取中移到二級快取中
  5. B拿到A物件後順利完成了初始化,然後將自己放入到一級快取singletonObjects中
  6. 此時返回A中,A此時能拿到B的物件順利完成自己的初始化

由此看出,屬性注入的迴圈依賴主要是通過將例項化完成的bean新增到singletonFactories來實現的。而使用構造器依賴注入的bean在例項化的時候會進行依賴注入,不會被新增到singletonFactories中。比如A和B都是通過構造器依賴注入,A在呼叫構造器進行例項化的時候,發現自己依賴B,B沒有被例項化,就會對B進行例項化,此時A未例項化完成,不會被新增到singtonFactories。而B依賴於A,B會去三級快取尋找A物件,發現不存在,於是又會例項化A,A例項化了兩次,從而導致拋異常。

總結:1、利用快取識別已經遍歷過的節點; 2、利用Java引用,先提前設定物件地址,後完善物件。

Spring啟動過程

  1. 讀取web.xml檔案。
  2. 建立 ServletContext,為 ioc 容器提供宿主環境。
  3. 觸發容器初始化事件,呼叫 contextLoaderListener.contextInitialized()方法,在這個方法會初始化一個應用上下文WebApplicationContext,即 Spring 的 ioc 容器。ioc 容器初始化完成之後,會被儲存到 ServletContext 中。
  4. 初始化web.xml中配置的Servlet。如DispatcherServlet,用於匹配、處理每個servlet請求。

Spring 的單例 Bean 是否有執行緒安全問題?

當多個使用者同時請求一個服務時,容器會給每一個請求分配一個執行緒,這時多個執行緒會併發執行該請求對應的業務邏輯,如果業務邏輯有對單例狀態的修改(體現為此單例的成員屬性),則必須考慮執行緒安全問題。

若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,那麼不會有執行緒安全問題;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則就可能影響執行緒安全。

無狀態bean和有狀態bean

  • 有例項變數的bean,可以儲存資料,是非執行緒安全的。
  • 沒有例項變數的物件。不能儲存資料,是執行緒安全的。

在Spring中無狀態的Bean適合用單例模式,這樣可以共享例項提高效能。有狀態的Bean在多執行緒環境下不安全,一般用Prototype模式或者使用ThreadLocal解決執行緒安全問題。

有用的話,點個贊,支援一下~

相關文章