1. 程式人生 > >spring原始碼分析:resource資源定位一

spring原始碼分析:resource資源定位一

最近回過頭來,再次看spring原始碼,以前很多次都是隨意的一看,但是有了以前的基礎現在理解起來容易很多了,於是這次想要分析原始碼的過程中,想要始終帶著幾個疑問去看原始碼

1.spring原始碼這樣寫的好處?
2.spring原始碼使用了哪些設計模式?
3.自己該如何利用他的思想運用到自己平時的程式碼中?

自己水平有限,可能出現理解出錯的地方,希望看到的朋友指出一下。


首先來看幾個設計模式的定義:

1.策略者模式:定義演算法族,分別封裝起來,讓他們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。

這個設計模式說的就是:
1.1 封裝變化:我們要找出一個物件變和不變的,將變的東西抽象出來。

1.2 多用組合,少用繼承:組合表示的是有一個的意思,而繼承和介面實現都是是一個的意思。
單純繼承的話可能會存在某些子類不需要父類的方法,修改父類的方法,程式碼重複,當然可以嘗試用抽象類,把方法抽象出來。
介面存在的問題是,程式碼不能複用。
組合的好處,是將對像擁有某個介面,可以通過傳入不同的實現來實現不同方法。

1.3 針對介面程式設計,不針對實現程式設計:相信這個大家比較熟悉了,如果針對實現程式設計,可能需求修改你就要改實現了,介面的話只需要再寫一個類再實現即可。
那是不是都要寫介面呢?個人認為思想一定要有,具體得看業務要求。

2.裝飾者模式:動態的將責任附加到物件上。想要擴充套件功能,裝飾者提供有別於繼承的另一種選擇。

這裡有個思想:對擴充套件開發,對修改關閉。
我們設計的軟體對外是可拓展的,這就是用介面的好處。spring框架設計的那麼優秀,這一點很重要,我們可以對介面實現自己的實現,傳入自己的實現帶來自定義的效果。
我們不需要改需求就改實現。擴充套件就行。

物件想要擴充套件功能,繼承當然是一個不錯的選擇。但有時候繼承並不是很好。例如

public class Room(){
    public int count(){
        return 5;
    }
}

現在想輸出,”房間有5人”。來看看繼承

public class Room2 extend Room(){
    public
void say(){ System.out.print("房間有"+count()+"人"); } }

來看看裝飾

public class RoomDecorate{
    public void say(Room room){
        System.out.print("房間有"+room.count()+"人");
    }
}

雖然繼承也能實現,但是繼承表示是一個的意思。有時我們只需擴充套件功能,而且裝飾的話,和上面的組合有點類似了,可以針對介面,有不同的輸出。

3.模板方法模式:在一個演算法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得不改變演算法的情況下重新定義演算法中的某些步驟。

public  abstract  class Demo {

    public void say(){
        System.out.println(count());
    }

    abstract int count();
}

account()是模板方法,繼承這個抽象類會有不同的實現,我們只用使用say()就可以了,而account()在我們具體類實現就可以了。
這裡又有一個原則:
別找我,我會找你:say()去找count(),具體的實現看具體類。

模板模式就可以抽出變化和不變化的,變化的交給具體實現。記住我們使用的時候不是使用acount()模板方法,而是使用say()。say會去找account()這才是模板模式的精髓。

我們始終記住,多用組合,針對介面程式設計,開放關閉,多型,變化的東西要抽象出來,記住這些思想。

帶著這些思想來看原始碼,go!


public class Cource {

    private Student student;

    public void say(){
        student.say();
    }


    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
}
@Configuration
public class JavaConfig {

    @Bean
    public Student getStudent(){
        return new Student();
    }

    @Bean(name = "cource")
    public Cource getCource(Student student){
        Cource cource = new Cource();
        cource.setStudent(student);
        return cource;
    }
}
public class Student {

    public void say(){
        System.out.println("i am student");
    }
}
public class StudentTest {


    @Test
    public void test(){
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans/demo04/spring-demo04.xml");
        Cource cource = (Cource) applicationContext.getBean("cource");
        cource.say();
    }

    @Test
    public void test2(){
        ApplicationContext app = new FileSystemXmlApplicationContext("beans/demo04/spring-demo05.xml");
        Cource cource = (Cource) app.getBean("cource");
        cource.say();
    }

    @Test
    public void test3(){
        ApplicationContext app = new AnnotationConfigApplicationContext(JavaConfig.class);
        Cource cource = (Cource) app.getBean("cource");
        cource.say();
    }
}

spring-demo04.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <bean id="student" class="com.share1024.beans.demo04.Student">
    </bean>

    <bean id="cource" class="com.share1024.beans.demo04.Cource">
        <property name="student" ref="student"/>
    </bean>


</beans>

spring-demo05.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.share1024.beans.demo04"></context:component-scan>

</beans>

首先來看ClassPathXmlApplicationContext物件

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext 
public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
        implements BeanNameAware, InitializingBean
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext 
public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext, DisposableBean 



public class DefaultResourceLoader implements ResourceLoader

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver 
public interface ListableBeanFactory extends BeanFactory
public interface HierarchicalBeanFactory extends BeanFactory

ClassPathXmlApplicationContext是一個ResourceLoader物件,同時它也是BeanFactory物件。

這裡設計兩個面試會考的考點:BeanFactory和ApplicationContext的區別

BeanFactory:只是包含了容器的基本功能。
ApplicationContext:實現了BeanFactory,容器的高階形態,增加許多其他功能,比如資源管理,獲取環境資訊等,如果說你能答出實現了BeanFactory,還有其他,那麼你這個題目應該過了。
BeanFactory和FactoryBean: BeanFactory是用來管理bean的,管理容器的,而FactoryBean是用來建立Bean的

這裡寫圖片描述

我們的分析路線就是

BeanFactory-->ApplicationContext-->ConfigurableApplicationContext
ResouceLoader-->DefaultResouceLoader

-->AbstractApplicationContext-->AbstractRefreshableApplicationContext-->
AbstractRefreshConfigApplicationContext --> AbstractXmlApplicationContext-->ClassPathXMlApplicationContext

先來看看BeanFactory

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;  
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
    boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    String[] getAliases(String name);

BeanFactory提供了對Bean進行管理的基本功能。 比如獲取bean,容器是否有指定名稱的bean等等。

關於&
例如 application.getBean(“cources”);是用來獲取cources物件的。
而 application.getBean(“&cources”);是用來獲取產生物件的BeanFactory;

if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
            throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
        }
public static boolean isFactoryDereference(String name) {
        return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
    }

如果獲取的物件不是FactoryBean 並且獲取的名稱以&開頭就會報錯。
就是用來區分獲取的是FactoryBean,還是FactoryBean產生的物件。

HierarchicalBeanFactory和ListableBeanFactory均是繼承了BeanFactory,拓展了不同的方法。

一個介面不能實現所有的方法,我們對介面抽出共同的或者基本的,作為最基本的一個介面,其他的可以通過繼承來擴充套件實現不同的。

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

ApplicationContext也是一個介面通過繼承和實現獲得了更多的能力。
ConfigurableApplicationContext介面繼承ApplicationContext,
最主要的方法是refresh(),資源的定位,解析,載入,依賴注入等都放在這裡。

public interface ResourceLoader {
        String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
    Resource getResource(String location);
    ClassLoader getClassLoader();
}

資源載入器,也就是我們的ClassPathXmlApplicationContext是一個ResourceLoader能夠擁有資源的管理能力。

public class DefaultResourceLoader implements ResourceLoader {
...
protected Resource getResourceByPath(String path) {
        return new ClassPathContextResource(path, getClassLoader());
    }


    /**
     * ClassPathResource that explicitly expresses a context-relative path
     * through implementing the ContextResource interface.
     */
    protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {

        public ClassPathContextResource(String path, ClassLoader classLoader) {
            super(path, classLoader);
        }

        @Override
        public String getPathWithinContext() {
            return getPath();
        }

        @Override
        public Resource createRelative(String relativePath) {
            String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
            return new ClassPathContextResource(pathToUse, getClassLoader());
        }
    }

}

DefaultResourceLoader實現了ResourceLoader也就是實現了介面方法,還有其他的自定義方法。

方法設定成protected:方法為什麼要設定成protected,大家都知道protected對於包外部class是私有的,也就是spring已經設定好了這幾個方法,如果我們要呼叫這些方法,只有通過繼承覆蓋來實現。

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext, DisposableBean

這裡出現抽象類了,出現抽象類我們第一個思維就是模板模式,抽象方法。
我們看

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            ....
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

refreshBeanFactory()是一個模板方法,子類來具體實現。
refreshBeanFactory是來進行spring資源定位,解析,Bean註冊的地方。
這個方法實現的有兩個一個是
AbstractRefreshableApplicationContext.refreshBeanFactory

protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

這個已經幫我們實現了。

另外一個GenericApplicationContext.refreshBeanFactory

protected final void refreshBeanFactory() throws IllegalStateException {
        if (!this.refreshed.compareAndSet(false, true)) {
            throw new IllegalStateException(
                    "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
        }
        this.beanFactory.setSerializationId(getId());
    }

沒有幫我實現去如果進行資源定位解析等等。
當然我們可以自定義

GenericApplicationContext ctx = new GenericApplicationContext();
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
ctx.refresh();

為什麼要設定這麼兩個呢?通過模板方法,我們有了多種選擇,我們可以採用spring給我提供預設的,也可以採用自定義的。spring不會強制我們使用

我們來看看
AbstractApplicationContenxt.getResourcePatternResolver

protected ResourcePatternResolver getResourcePatternResolver() {
        return new PathMatchingResourcePatternResolver(this);
    }

public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
        Assert.notNull(resourceLoader, "ResourceLoader must not be null");
        this.resourceLoader = resourceLoader;
    }
    }

this的作用,this代表本身,我們知道ClassPathXmlApplicationContext是一個ResourceLoader物件,上面已經說過了,ApplicationContext的不同實現可能會自定義並覆蓋掉ResourceLoader的某些方法。這裡傳入本身之後,在資源解析方面就會有自己個性化實現。仔細讀spring原始碼會發現很多地方都是這樣。

其實不難理解,PathMatchingResourcePatternResolver中的resourceLoader就像使用了策略者模式,傳入不同的resourceLoader實現。

策略者模式定義演算法族,分別封裝起來,讓他們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。

我們看spring原始碼的過程當中,發現它一個方法都不會很長,我們平時寫程式碼的時候也要這樣,共同的程式碼抽取出來,一個方法代表處理的一個內容。例如一個方法包含資料的初始化,資料處理,處理結束後的操作,比如釋放操作。可以分為三個方法,主方法放這三個方法,帶上註釋看起來就比較清晰。

我們再看
AbstractRefreshableApplicationContext抽象類繼承了AbstractApplicationContext
loadBeanDefinitions同樣是模板方法。交給子類來實現,然後refreshBeanFactory呼叫loadBeanDefinitions模板方法。

我們發現loadBeanDefinitions的實現方法
AbstractXmlApplicationContext.loadBeanDefinitions同樣也是保護型別。

設定成保護型別的意思,就是僅供內部使用,如果我們要使用只能覆蓋。我們平時在寫程式碼的時候,如果有些方法,目的是給包內部使用,又希望子類可以訪問,或者覆蓋。可以嘗試寫成保護型別。不一定要寫成共有型別。

接下來就是AbstractXmlApplicationContext和
ClassPathXmlApplicationContext。

我們重點看

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

super(parent)。也可以理解成裝飾者模式,傳入ApplicationContext物件,在使用parent的基礎上,加上自己個性化的內容。

從ClassPathXmlApplicationContext這條鏈來看。主要是合理的使用介面,採用組合的方式,模板方法。

多個介面繼承,可以根據不同的需求實現不同的介面,不用把所有方法放入一個介面中。
採用組合的方式,可以傳入不同的介面實現,實現不同的內容。
我們從上發現spring凡是能用介面的地方用介面,這樣我們可以拓展自定義的功能。
就拿refreshBeanFactory這個模板方法來說,既可以使用spring自己提供的實現,也可以自己實現,以後spring要拓展功能,自己也能實現,對外開放對內關閉。

本篇將的主要是ClassPathXmlApplicationContext的父類,如何繼承,為什麼要用介面,模板方法的好處,無非是把我們的繼承實現,多型運用得淋漓盡致。

在實際應用中,不會一開始就設計模式搞起,還是要理解業務的基礎上,再考慮,是否有必要。但是思想一定要有,程式碼也要如spring一樣寫的優美。

為什麼這篇不直接debug進入正題呢?是因為要理解ClassPathXmlApplicationContext本質究竟是什麼,不然後面進行多型,型別轉化的時候就會搞不清楚了。

下篇就講解如何實現資源定位,細細分析寫的好的程式碼,為什麼要這麼寫。


菜鳥不易,望有問題指出,共同進步