spring源码分析:resource资源定位一

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010399009/article/details/78489049

最近回过头来,再次看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本质究竟是什么,不然后面进行多态,类型转化的时候就会搞不清楚了。

下篇就讲解如何实现资源定位,细细分析写的好的代码,为什么要这么写。


菜鸟不易,望有问题指出,共同进步

猜你喜欢

转载自blog.csdn.net/u010399009/article/details/78489049