Spring用了挺久了,但是Spring源碼一直不得其精髓,后來在B站上找視頻,看到個手寫一個簡易的Spring,以此加深對Spring的理解。
Bean的生命周期
一個Spring容器的開始是從創(chuàng)建ApplicationContext開始的,然后通過ApplicationContext.getBean("beanName")創(chuàng)建一個實例Bean。
ApplicationContext applicationContext = new ApplicationContext(AppConfig.class);
System.out.println(applicationContext.getBean("testService"));
所以需要在ApplicationContext的構造方法里面實現(xiàn)以下幾個點:
- 從AppConfig類中獲取ComponentScan注解的參數(shù),這個參數(shù)就是表示我要到哪些包下面找類;
- 根據(jù)1步驟找到的絕對路徑,掃路徑下的所有文件,包含Component注解的類全部撈出來解析;
- 如果Component有參數(shù),就以參數(shù)為Bean名稱,沒有就小寫類名首字母。解析類的特征存儲進BeanDefinition對象封裝起來,并且存放到BeanDefinitionMap中,方便后續(xù)處理和創(chuàng)建Bean;
- 把BeanDefinitionMap里的所有標注為單例的BeanDefinition撈出來先創(chuàng)建好單例Bean,創(chuàng)建好單例Bean后存儲在單例池里面,這里的單例池用的還是ConcurrentHashMap
下面就是簡易的ApplicationContext實現(xiàn):
public ApplicationContext(Class className) {
this.className = className;
// 我們拿到配置類的名稱之后,就要去配置類里面拿信息,第一個要拿的,就是配置類可以掃哪些包,即獲取ComponentScan的信息
if (className.isAnnotationPresent(ComponentScan.class)) {
// 判斷一下有沒有ComponentScan的注解,有就拿出來
ComponentScan componentScan = (ComponentScan) className.getAnnotation(ComponentScan.class);
// 拿出配置的參數(shù)值,就是com.my.code.service這個值
String path = componentScan.value();
path = path.replace(".", "/");
// 根據(jù)相對路徑找絕對路徑
ClassLoader classLoader = ApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
File file = new File(resource.getFile());
if (file.isDirectory()) {
// 判斷一下是不是文件夾
File[] files = file.listFiles();
for (File f : files) {
String fileName = f.getAbsolutePath();
if (fileName.endsWith(".class")) {
// 判斷這個類是不是有Component注解,用反射;先將絕對路徑換成相對路徑
String classPathName = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
classPathName = classPathName.replace("\\", ".");
try {
Class<?> clazz = classLoader.loadClass(classPathName);
// 獲取到類之后,判斷是否有Component注解
if (clazz.isAnnotationPresent(Component.class)) {
Component component = clazz.getAnnotation(Component.class);
String beanName = component.value();
if (beanName.equals("")) {
// 如果不寫Component的參數(shù),默認是類名的首字母小寫
beanName = Introspector.decapitalize(clazz.getSimpleName());
}
// 有這個注解,我們不直接創(chuàng)建Bean,而是先創(chuàng)建BeanDefinition
BeanDefinition beanDefinition = new BeanDefinition();
if (clazz.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
} else {
beanDefinition.setScope("singleton");
}
beanDefinition.setType(clazz);
beanDefinitionMap.put(beanName, beanDefinition);
}
} catch (ClassNotFoundException e) {
System.err.println("反射獲取.class文件出錯");
e.printStackTrace();
}
}
}
}
}
// 開始創(chuàng)建單例對象
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) {
Object bean = createBean(beanName, beanDefinition);
singletonObjectsMap.put(beanName, bean);
}
}
}
可以直接根據(jù)全限定類名從類加載器里面獲取類,然后用isAnnotationPresent方法來判斷是否有某個注解
以上就是完成了包掃描,加載好了Bean的相關信息,單例Bean也已經(jīng)創(chuàng)建好了。只需要調(diào)用getBean就能夠獲取了。這里只考慮是否是單例Bean而討論。實現(xiàn)思路還是很直接,拿BeanDefinitionMap里的信息,判斷是單例還是多例,單例就從單例池里取,沒有重新創(chuàng)建,有就拿出返回;多例就直接創(chuàng)建一個返回回去。這里的創(chuàng)建Bean有點復雜,所以單獨拎出去寫一個新方法。getBean實現(xiàn)如下:
public Object getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new NullPointerException();
} else {
String scope = beanDefinition.getScope();
if (scope.equals("singleton")) {
Object bean = singletonObjectsMap.get(beanName);
if (bean == null) {
bean = createBean(beanName, beanDefinition);
assert bean != null;
singletonObjectsMap.put(beanName, bean);
}
return bean;
} else {
return createBean(beanName, beanDefinition);
}
}
}
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class<?> clazz = beanDefinition.getType();
try {
Object instance = clazz.getConstructor().newInstance();
// 對instance對象里面的屬性自動配置,找有Autowired注解的屬性
for (Field declaredField : clazz.getDeclaredFields()) {
if (declaredField.isAnnotationPresent(Autowired.class)) {
declaredField.setAccessible(true);
declaredField.set(instance, getBean(declaredField.getName()));
}
}
return instance;
} catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
這是創(chuàng)建Bean的實現(xiàn),里面涉及到Autowired的自動注入注解,還有一個循環(huán)依賴的解決。
AOP的實現(xiàn):先創(chuàng)建一個BeanPostProcessor,里面搞個后置處理器,在后置處理器里面創(chuàng)建一個代理類,替換原先的bean并且返回。這里JDK代理只能代理接口。
@Override
public Object postProcessAfterInitialization(String beanName, Object bean) {
// AOP的核心,就是在后置處理器里面創(chuàng)建了一個新的代理對象,這個代理對象不是原來的bean,而是在原有bean上加工過的對象
Object proxyInstance = Proxy.newProxyInstance(TestBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("加入操作XXXX");
return method.invoke(bean, args);
}
});
return proxyInstance;
}
AOP的本質(zhì)就是在創(chuàng)建這個Bean的時候,在后置處理器里面,創(chuàng)建一個同接口的實現(xiàn)類,這個實現(xiàn)類有自己加的新功能,也有原先的方法,所有的方法都被植入新操作,并以代理類代替了原先的實現(xiàn)類。
綜上我們可以總結如下的Bean的生命周期:
ApplicationContext的構造方法里面,開始掃@Component注解的類。

Spring事務底層原理
事務開啟會做兩件事,一件是Spring自己建立一個JDBC連接,然后就是關閉自動提交。如果將提交和連接交給jdbcTemplate,那么就會出現(xiàn)jdbctemplate執(zhí)行一個sql就提交一個,遇到異常拋出時,sql都已經(jīng)提交了,回滾不回來了。所以需要spring自己管理一個連接。
spring事務失效的原理
@Transactional
public void test(){
jdbcTemplate.execute("insert into t1 values(1,1,1)");
a();
}
@Transactional
public void a(){
jdbcTemplate.execute("insert into t1 values(2,2,2)");
}
當調(diào)用Spring里的Bean(testService)的test()方法時,test()的@Transactional是會生效的,但是test()里面的a()方法的事務注解是不會生效的。
原因:在產(chǎn)生代理對象替代原普通對象時,普通對象里面的自動注入的屬性在代理對象里面是沒有用的,也就是自動注入注不到代理對象里面,CGlib的解決方法是在代理對象里面加一個同類型的target對象,這個target就是上面提到的普通對象,也可以認為是被代理的對象。這個時候再執(zhí)行普通對象里面的test()方法和a()方法。于是問題就出現(xiàn)了:test()方法的事務注解是被解析過的并且在代理對象里是有事務的操作,所以不會失效,a()方法就不會了,他沒有被事務解析過,調(diào)用的時候是調(diào)的target這個普通對象,你寫的@Transactional他理都不理,等于沒寫。解決方法就是在這個類里面再自動注入自己,spring會幫忙解決循環(huán)依賴的問題。
三級緩存
一級緩存:單例池,存放已經(jīng)完成初始化后的完整Bean;
二級緩存:因為出現(xiàn)循環(huán)依賴,而不得不提前進行AOP產(chǎn)生代理對象,存放在二級緩存中;二級緩存是為了保證這個代理對象單例唯一;
三級緩存:如果二級緩存中沒有找到提前出現(xiàn)的代理對象,就會將執(zhí)行反射創(chuàng)建代理對象的操作封裝進函數(shù)式接口中,存放在三級緩存里面;