1. 程式人生 > 其它 >Spring Bean生命週期以及其中的一些底層原理

Spring Bean生命週期以及其中的一些底層原理

Bean建立的生命週期

UserService.class
--> 推斷構造方法
> 1. 判斷使用哪一個構造方法
> 2. 判斷構造方法有沒有入參
> 1. 沒有入參
> 2. 有入參
先ByType,再ByName
嘗試去找,先找Type,看看找到了幾個
1. 找到一個,那麼就是這個
2. 找到多個,那麼就去找名字
1. 找到名字,那麼就是這個
2. 找不到名字,就丟擲異常
3. 一個都沒有找到,那麼就嘗試去建立
這裡可能會出現迴圈依賴問題
--> 普通物件
--> 依賴注入(注入加入了@autowired註解的屬性)
1. 找到加了Autowired註解的屬性
2. 給加了Autowired註解的屬性去賦值
也是先ByType,再ByName

--> 初始化前(執行a方法,方法加了@PostConstruct註解)
1. 找到加了@PostConstruct註解的方法
2. 執行這個方法
--> 初始化 (實現 InitializingBean介面 重寫afterPropertiesSet方法)
> 具體底層實現就是 強轉為InitializingBean,然後呼叫afterPropertiesSet方法
--> 初始化後(AOP)
> 動態代理,產生了代理物件
代理物件沒有進行依賴注入,執行了切面邏輯之後,再回到切面的方法去執行該方法
cglib: 產生一個代理類,子類會繼承之前的那個類。產生一個代理物件, 然後給Target賦值
UserServiceProxy物件 extends UserService代理物件 -->UserServiceProxy代理物件.target = 普通物件
代理物件.test()。

class UserServiceProxy extends UserService {
                        // 這裡必須要繼承,不然需要強制轉換,不太好呀
    UserService target;

   public void test() {
      1. 執行切面邏輯// @Before("execution(public void com.zbz.service.UserService.tes())")
      2. target.test(); 就是執行UserService普通物件的test(),這裡是經過依賴注入的。
   }
}

---> 放入map(單例Bean)
> 如果發生了AOP,那麼放入單例池的就是代理物件
> 如果沒有傳送AOP,那麼放入單例池的就是普通物件
-->> Bean物件


  1. 依賴注入: 給前面生成出來的物件,給物件當中的加了autowired的屬性去賦值

    1. 找到加了autowired註解的屬性
    2. 給加了autowired註解的屬性賦值
    1. 用無參的構造方法弄出一個物件
    2. 拿到這個物件的所有的fields
    3. 遍歷這些fields, 判斷是否有autowired.class的註解
    4. 給當前物件的這個屬性去賦值
    for (Field field: xxx.getClass().getDeclaredFields()) {
        if (filed.isAnnotationPresent(Autowired.class)) {
            field.set(xxx, ???)
        }
    }
    
  2. 單例池底層原理

    存著一個又一個的單例bean, 從map拿到的物件叫做bean物件。
    只要這個物件放入了單例池,就可以說這個物件是bean物件。

  3. PostConstruct底層原理
    假設某個Bean,當中有個屬性叫做User user, 但是他沒有加autowired註解,但是我們希望在我們拿到這個bean的時候
    ,這個user屬性值是有值的(我們要從資料庫裡面去查詢呀,所以只有我們程式設計師自己才知道),那該怎麼辦呢?
    那麼這個a方法就是特殊的,那麼如何在執行初始化之前 去執行 a方法呢,我們去給a方法戴個帽子。

    去找這個物件的所有方法,看哪個方法上面加了@PostConstruct註解的。

    
    public class UserService {
        
        @Autowired
        private OrderService orderService;
    
        private User admin;
        
        @PostConstruct
        public void a() {
            // mysql --> 查詢管理員資訊 --> User物件物件 --> setUser
        }
        
        public void test() {
            System.out.println(orderService);
        }
    
    }
    
    for (Method method: xxx.getClass().getDeclaredMethods()) {
        if (method.isAnnotaionPresent(PostConstruct.class)) {
            method.invoke(xxx, null);
        }
    }
    
  4. 初始化底層原理程式碼實現

     public class UserService 1. implements InitializingBean{
            
            @Autowired
            private OrderService orderService;
        
            private User admin;
           
           2. 
           @Override
           public void afterPropertiesSet() throws Exception {
               // mysql --> 查詢管理員資訊 --> User物件物件 --> setUser
           }
    
            public void test() {
                System.out.println(orderService);
            }
        
        }  
    
  5. 推斷構造方法底層原理, 可能出現迴圈依賴問題
    如果一個java類,裡面有多個構造方法,那麼會看有沒有無參構造方法:
    有, 呼叫無參構造方法
    沒有,會報錯!
    如果只有一個構造方法,那麼就使用這個構造方法呀!
    如果想讓spring知道呼叫那個構造方法。那麼給方法加上@Autowired即可!

    @Component
    public class UserService {
       private OrderService orderService;
       
       public UserService(OrderService xxxService) { 
           this.orderService = xxxService;
       }
    }
    

    問題來了,getBean("userService")的時候,orderService有沒有值?
    答案是有的!
    Spring 會給我們去找 那個OrderService物件,然後放進這個方法裡面!!!
    如果是單例Bean去單例池去找,看有沒有orderService的Bean物件:
    前提: 是一個Bean呀!
    有, 直接傳
    沒有,去建立 --- 這裡可能會出現迴圈依賴問題!!!
    ```java
    @Component
    public class OrderService {
    private UserService userService;

         public OrderService(UserService userService) {
             this.userService = userService;
         }
     }
     ```
    

    如果是多例Bean, 那麼直接建立!

    那麼應該如何去找?

    1. 根據名字去找不是那麼合適, (OrderService xxxService)
      因為是形參,我隨便扔一個名字,找不到不就白找了,所以要根據型別去找
      Map<beanName, Bean物件>
    2. 根據型別去找可能會出現問題
      <k1, v1> <k2, v2> 這裡v1和v2兩個Bean物件是一個型別!
      1. 先找同類型的,找到了一堆,然後看個數,如果只有1個,那麼就是這個了,否則
      2. 在這一堆同類型的去找名字,哪個名字相同就用哪個!
        先ByType再ByName!!!!!!!!!!!!!!!!!!!!!!!!!!!
        名字也找不到就會丟擲異常啦!
      @Bean 
      public OrderService orderService1() {
          return new OrderService();
       }
      @Bean  
      public OrderService orderService2() {
          return new OrderService();
      }
      
      @Component
        public class UserService {
           private OrderService orderService;
           public UserService() {}
           public UserService(OrderService orderService) {
               this.orderService = orderService;
           }
        }
    

    問題來了,getBean("userService")的時候,orderService有沒有值?
    答案是沒有的! spring使用了無引數的構造方法!

            @Component
            public class UserService {
               private OrderService orderService;
               public UserService(OrderService orderService) {
                   this.orderService = orderService;
               }
               public UserService(OrderService orderService, OrderService orderService2) {
                   this.orderService = orderService;
               }
            }
    

    問題來了,getBean("userService")的時候,orderService有沒有值?
    這個時候會報錯啦!!!Spring也不知道用哪一個構造方法了!

  6. AOP底層實現原理
    應用:

@ComponentScan("com.zbz")
@EnableAspectJAutoProxy
public class AppConfig {

}
@Aspect
@Component
public class XXXAspect {
  @Before("execution(public void com.zbz.service.UserService.test()")
  public void xxxBefore(JoinPoint joinPoint) {
                          joinPoint.getTarget()可以拿到普通物件
                          joinpPoint.getThis()可以拿到代理物件
      System.out.println("before");
  }
}

底層原理:

  1. 首先是Bean的生命週期:
    推斷構造方法->產生普通物件->執行依賴注入->初始化前->初始化->初始化後(AOP)->放入單例池->getBean物件去使用
  2. AOP是怎麼做的呢?
1. UserServiceProxy產生一個代理類,繼承普通物件的類(UserService)。
2. 產生一個代理類物件UserService代理物件
3. 給UserService代理物件的target屬性賦值
    this.target = 普通物件 (前面產生的普通物件)
  1. Spring事務底層實現原理
@ComponentScan("com.zbz")
@EnableAspectJAutoProxy
//4. 開啟事務
@Configure
@EnableTransactionManagement
public class AppConfig {
  // 1. 配置mybatis
  @Bean
  public JdbcTemplate JdbcTemplate() {
      return new JdbcTemplate(dataSource());
  }
  // 2. 配置一個事務管理器
  @Bean 
  public PlatformTransactionManager transactionManager() {
    DataSourceTransactionManager transactionManager = new DataSourceTranscationManager();
    transactionManager.setDataSource(dataSource());
    return transactionManager;
  }
  // 3. 配置一個dataSource
  @Bean 
  public DataSource dataSource() {
    DriveManagerDataSource dataSource = new DriveManagerDataSource();
    dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/xxx?characterEncoding=utf-8&&useSSL=false");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
    return dataSource;
  }
}
@Component
public class UserService {
  @Autowired
  private JdbcTemplate JdbcTemplate;

  @Transcational
  public void test() {
    JdbcTemplate.execute("insert into t1 values(1,1,1,1,'1'");
    throw new NullPointerException();
  }
}

加了@transactional標籤,會產生一個代理物件去執行這個test()方法,但是這裡沒有回滾,因為少了一個註解@Configure。
加了@Configure註解後,執行失敗進行回滾了。
也產生了一個cglib代理物件。

class UserServiceProxy extends UserService {
                        // 這裡必須要繼承,不然需要強制轉換,不太好呀
    UserService target;

   public void test() {
      Spring事務切面邏輯
      執行的方法上面有@Trancational 註解才能夠開啟事務

      開啟事務
      1. 事務管理器新建一個數據庫連線conn
         為什麼不用本來類持有的jdbcTemplate資料庫連線? 
         因為預設的conn.autocommit = true
         所以spring只有自己去建立一個數據庫連線
      2. 修改conn.autocommit = false


      3. target.test(); 就是執行UserService普通物件的test(),這裡是經過依賴注入的。
      4. 這裡jdcbTemplate 需要拿到之前建立的資料庫連線 | sql1 sql2

      5. 如果沒有拋異常 : conn.commit()
         如果丟擲異常 : conn.rollback() 
   }
}
  1. Spring 事務失效原理
@Component
public class UserService {
  @Autowired
  private JdbcTemplate JdbcTemplate;

  @Transcational
  public void test() {
    jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1'");
    a(); 執行這個方法,是UserService普通物件來執行的,
  }


  @Transcational(propagation = Propagation.NEVER) NEVER: 如果有一個事務存在,那麼就丟擲一個異常
  public void a() {
     JdbcTemplate.execute("insert into t1 values(2,1,1,1,'1'");
  }
}

為什麼這裡沒有丟擲異常? 相當於@Transcational(propagation = Propagation.NEVER失效)
test()方法的@Transactional()註解是有用的,因為getBean("UserService")拿到的是UserService的代理物件,切面過程執行完以後
讓target(指向普通物件)去執行普通物件的target方法,普通物件是不會開啟事務的。

關鍵是要弄清楚: 執行這個方法的物件是 代理物件! 還是 普通物件! 所以下面的@Transcational沒用了

那麼如果想讓下面的@Transcation有用,怎麼辦呢?

  1. 弄一個新類,把這個方法弄到新的類當中去,然後autowire一個代理物件過來執行就可以了
@Autowired 
private UserServiceBase userServiceBase; // 這裡弄過來的是代理方法啊!

public void test() {
    jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1'");
    userServiceBase.a();
}
  1. 不拆類,自己注入自己
    這裡也相當於產生了一個迴圈依賴! 但是spring幫我們進行處理了!
@Autowired 
private UserService userService; // 這裡弄過來的是代理方法啊!

public void test() {
    jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1'");
    userService.a();
}
  1. @Configuration 底層原理
    配置事務@EnableTransactionManagement的時候,不加@Configuration註解,那麼執行方法出錯的時候不會進行回滾,為什麼?
    通過ThreadLocal進行實現的,因為這些操作都是在一個執行緒中實現的。

回到事務的實現

開啟事務
1. 事務管理器新建一個數據庫連線conn 
2. conn.autocommit = false ThreadLocal<Map<DataSource, conn>>

target.test();  普通物件.test() jdbcTemplate 不加@Configuration 就拿不到上面的conn sql1 | sql2

conn.commit() conn.rollback()

ThreadLocal<Map<DataSource, conn>>存放的是map key:DataSource, value: conn
因為jdbcTemplate和transactionManager都會依賴於dataSource()

jdbcTemplate 和transactionManager所持有的dataSource是不同的

如果jdbcTemplate從ThreadLocal裡面拿不到資料庫連線,那麼它就會自己去建立資料庫連線,這裡的commit是true的。

@Configuration AOP @Lazy 都是使用了動態代理技術,是平級的

@Configuration: 讓jdbcTemplate和transactionManager所持有的 dataSource()物件是同一個物件
這裡又和代理物件相關
@Configuration -> 產生了代理物件
這裡使用了super機制
AppConfig 代理物件執行 jdbcTemplate 和transactionManager方法

對dataSource()方法 增加了切面 代理邏輯:
看Spring容器中是否有DataSource bean,有就返回, 沒有就建立

 class AppConfigProxy extends AppConfig {
    public void jdbcTemplate() {

      // 代理邏輯
      super.jdbcTemplate(); 
    }


       // 2. 配置一個事務管理器
    @Bean 
    public PlatformTransactionManager transactionManager() {
      DataSourceTransactionManager transactionManager = new DataSourceTranscationManager();
      transactionManager.setDataSource(dataSource());
      return transactionManager;
    }
    // 3. 配置一個dataSource
    @Bean 
    public DataSource dataSource() {

      先看Spring容器中是否有DataSource Bean


      DriveManagerDataSource dataSource = new DriveManagerDataSource();
      dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/xxx?characterEncoding=utf-8&&useSSL=false");
      dataSource.setUsername("root");
      dataSource.setPassword("root");
      return dataSource;
    }
}

參考:https://www.bilibili.com/video/BV1tR4y1F75R?p=21&spm_id_from=pageDriver