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物件
-
依賴注入: 給前面生成出來的物件,給物件當中的加了autowired的屬性去賦值
- 找到加了autowired註解的屬性
- 給加了autowired註解的屬性賦值
1. 用無參的構造方法弄出一個物件 2. 拿到這個物件的所有的fields 3. 遍歷這些fields, 判斷是否有autowired.class的註解 4. 給當前物件的這個屬性去賦值 for (Field field: xxx.getClass().getDeclaredFields()) { if (filed.isAnnotationPresent(Autowired.class)) { field.set(xxx, ???) } }
-
單例池底層原理
存著一個又一個的單例bean, 從map拿到的物件叫做bean物件。
只要這個物件放入了單例池,就可以說這個物件是bean物件。 -
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); } }
-
初始化底層原理程式碼實現
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); } }
-
推斷構造方法底層原理, 可能出現迴圈依賴問題
如果一個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, 那麼直接建立!
那麼應該如何去找?
- 根據名字去找不是那麼合適, (OrderService xxxService)
因為是形參,我隨便扔一個名字,找不到不就白找了,所以要根據型別去找
Map<beanName, Bean物件> - 根據型別去找可能會出現問題
<k1, v1> <k2, v2> 這裡v1和v2兩個Bean物件是一個型別!- 先找同類型的,找到了一堆,然後看個數,如果只有1個,那麼就是這個了,否則
- 在這一堆同類型的去找名字,哪個名字相同就用哪個!
先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也不知道用哪一個構造方法了! - 根據名字去找不是那麼合適, (OrderService xxxService)
-
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");
}
}
底層原理:
- 首先是Bean的生命週期:
推斷構造方法->產生普通物件->執行依賴注入->初始化前->初始化->初始化後(AOP)->放入單例池->getBean物件去使用 - AOP是怎麼做的呢?
1. UserServiceProxy產生一個代理類,繼承普通物件的類(UserService)。
2. 產生一個代理類物件UserService代理物件
3. 給UserService代理物件的target屬性賦值
this.target = 普通物件 (前面產生的普通物件)
- 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()
}
}
- 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有用,怎麼辦呢?
- 弄一個新類,把這個方法弄到新的類當中去,然後autowire一個代理物件過來執行就可以了
@Autowired
private UserServiceBase userServiceBase; // 這裡弄過來的是代理方法啊!
public void test() {
jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1'");
userServiceBase.a();
}
- 不拆類,自己注入自己
這裡也相當於產生了一個迴圈依賴! 但是spring幫我們進行處理了!
@Autowired
private UserService userService; // 這裡弄過來的是代理方法啊!
public void test() {
jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1'");
userService.a();
}
- @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