美文网首页spring5.0源码分析
1. spring5源码讲解-bean的加载过程(一)

1. spring5源码讲解-bean的加载过程(一)

作者: 太上老君007 | 来源:发表于2019-10-28 10:24 被阅读0次

    spring-core源码解析-(1)

    基本

    本部分从最基本的Spring开始。配置文件:

    <?xml version="1.0" encoding="UTF-8"?>    
    <beans>    
        <bean class="base.SimpleBean"></bean>
    </beans>
    

    启动代码:

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
        SimpleBean bean = context.getBean(SimpleBean.class);
        bean.send();
        context.close();
    }
    

    SimpleBean:

    public class SimpleBean {
        public void send() {
            System.out.println("I am send method from SimpleBean!");
        }
    }
    

    ClassPathXmlApplicationContext

    整个继承体系如下:

    ClassPathXmlApplicationContext继承体系

    ResourceLoader代表了加载资源的一种方式,正是策略模式的实现

    构造器源码:

    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) {
        super(parent);
        setConfigLocations(configLocations);
        //默认true
        if (refresh) {
            refresh();
        }
    }
    

    构造器

    首先看父类构造器,沿着继承体系一直向上调用,直到AbstractApplicationContext:

    public AbstractApplicationContext(ApplicationContext parent) {
        this();
        setParent(parent);
    }
    public AbstractApplicationContext() {
        this.resourcePatternResolver = getResourcePatternResolver();
    }
    

    getResourcePatternResolver:

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

    PathMatchingResourcePatternResolver支持Ant风格的路径解析。

    设置配置文件路径

    即AbstractRefreshableConfigApplicationContext.setConfigLocations:

    public void setConfigLocations(String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        } else {
            this.configLocations = null;
        }
    }
    

    resolvePath:

    protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
    }
    

    此方法的目的在于将占位符(placeholder)解析成实际的地址。比如可以这么写: new ClassPathXmlApplicationContext("classpath:config.xml");那么classpath:就是需要被解析的。

    getEnvironment方法来自于ConfigurableApplicationContext接口,源码很简单,如果为空就调用createEnvironment创建一个。AbstractApplicationContext.createEnvironment:

    protected ConfigurableEnvironment createEnvironment() {
        return new StandardEnvironment();
    }
    

    Environment接口

    StandardEnvironment继承体系:

    StandardEnvironment继承体系

    Environmen接口代表了当前应用所处的环境。从此接口的方法可以看出,其主要和profile、Property相关。

    Profile

    Spring Profile特性是从3.1开始的,其主要是为了解决这样一种问题: 线上环境和测试环境使用不同的配置或是数据库或是其它。有了Profile便可以在 不同环境之间无缝切换。Spring容器管理的所有bean都是和一个profile绑定在一起的。使用了Profile的配置文件示例:

    <beans profile="develop">  
        <context:property-placeholder location="classpath*:jdbc-develop.properties"/>  
    </beans>  
    <beans profile="production">  
        <context:property-placeholder location="classpath*:jdbc-production.properties"/>  
    </beans>  
    <beans profile="test">  
        <context:property-placeholder location="classpath*:jdbc-test.properties"/>  
    </beans>
    

    在启动代码中可以用如下代码设置活跃(当前使用的)Profile:

    context.getEnvironment().setActiveProfiles("dev");
    

    当然使用的方式还有很多(比如注解),参考:

    spring3.1 profile 配置不同的环境

    Spring Profiles example

    Property

    这里的Property指的是程序运行时的一些参数,引用注释:

    properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects,Maps, and so on.

    Environment构造器

    private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);
    public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
    }
    
    PropertySources接口

    继承体系:

    PropertySources继承体系

    此接口实际上是PropertySource的容器,默认的MutablePropertySources实现内部含有一个CopyOnWriteArrayList作为存储载体。

    StandardEnvironment.customizePropertySources:

    /** System environment property source name: {@value} */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    /** JVM system properties property source name: {@value} */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource
            (SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource
            (SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }
    
    PropertySource接口

    PropertySource接口代表了键值对的Property来源。继承体系:

    PropertySource继承体系

    AbstractEnvironment.getSystemProperties:

    @Override
    public Map<String, Object> getSystemProperties() {
        try {
            return (Map) System.getProperties();
        }
        catch (AccessControlException ex) {
            return (Map) new ReadOnlySystemAttributesMap() {
                @Override
                protected String getSystemAttribute(String attributeName) {
                    try {
                        return System.getProperty(attributeName);
                    }
                    catch (AccessControlException ex) {
                        if (logger.isInfoEnabled()) {
                            logger.info(format("Caught AccessControlException when accessing system " +
                                    "property [%s]; its value will be returned [null]. Reason: %s",
                                    attributeName, ex.getMessage()));
                        }
                        return null;
                    }
                }
            };
        }
    }
    

    这里的实现很有意思,如果安全管理器阻止获取全部的系统属性,那么会尝试获取单个属性的可能性,如果还不行就抛异常了。

    getSystemEnvironment方法也是一个套路,不过最终调用的是System.getenv,可以获取jvm和OS的一些版本信息。

    路径Placeholder处理

    AbstractEnvironment.resolveRequiredPlaceholders:

    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        //text即配置文件路径,比如classpath:config.xml
        return this.propertyResolver.resolveRequiredPlaceholders(text);
    }
    

    propertyResolver是一个PropertySourcesPropertyResolver对象:

    private final ConfigurablePropertyResolver propertyResolver =
                new PropertySourcesPropertyResolver(this.propertySources);
    
    PropertyResolver接口

    PropertySourcesPropertyResolver继承体系(排除Environment分支):

    PropertySourcesPropertyResolver继承关系

    此接口正是用来解析PropertyResource。

    解析

    AbstractPropertyResolver.resolveRequiredPlaceholders:

    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        if (this.strictHelper == null) {
            this.strictHelper = createPlaceholderHelper(false);
        }
        return doResolvePlaceholders(text, this.strictHelper);
    }
    
    private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
        //三个参数分别是${, }, :
        return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
            this.valueSeparator, ignoreUnresolvablePlaceholders);
    }
    

    doResolvePlaceholders:

    private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        //PlaceholderResolver接口依然是策略模式的体现
        return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
            @Override
            public String resolvePlaceholder(String placeholderName) {
                return getPropertyAsRawString(placeholderName);
            }
        });
    }
    

    其实代码执行到这里的时候还没有进行xml配置文件的解析,那么这里的解析placeHolder是什么意思呢,原因在于可以这么写:

    System.setProperty("spring", "classpath");
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("${spring}:config.xml");
    SimpleBean bean = context.getBean(SimpleBean.class);
    

    这样就可以正确解析。placeholder的替换其实就是字符串操作,这里只说一下正确的属性是怎么来的。实现的关键在于PropertySourcesPropertyResolver.getProperty:

    @Override
    protected String getPropertyAsRawString(String key) {
        return getProperty(key, String.class, false);
    }
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
            for (PropertySource<?> propertySource : this.propertySources) {
                Object value = propertySource.getProperty(key);
                return value;
            }
        }
        return null;
    }
    

    很明显了,就是从System.getProperty和System.getenv获取,但是由于环境变量是无法自定义的,所以其实此处只能通过System.setProperty指定。

    注意,classpath:XXX这种写法的classpath前缀到目前为止还没有被处理。

    关注公众号: 太上老君007
    spring源码分析第一时间通知你

    相关文章

      网友评论

        本文标题:1. spring5源码讲解-bean的加载过程(一)

        本文链接:https://www.haomeiwen.com/subject/covvvctx.html