Spring源碼學習

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)以下幾個點:

  1. 從AppConfig類中獲取ComponentScan注解的參數(shù),這個參數(shù)就是表示我要到哪些包下面找類;
  2. 根據(jù)1步驟找到的絕對路徑,掃路徑下的所有文件,包含Component注解的類全部撈出來解析;
  3. 如果Component有參數(shù),就以參數(shù)為Bean名稱,沒有就小寫類名首字母。解析類的特征存儲進BeanDefinition對象封裝起來,并且存放到BeanDefinitionMap中,方便后續(xù)處理和創(chuàng)建Bean;
  4. 把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注解的類。


流程圖.jpg

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ù)式接口中,存放在三級緩存里面;

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

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

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