The original address: zhuanlan.zhihu.com/p/99906250

We first understand the Spring, may be a return to our first source, the inside of the possible code affect your future code style and architectural style, but we need to break the shackles of our mind, let the Spring is no longer to be our code in the bible, but to make Spring become our architecture a right-hand man on the road, So you really understand how the framework works, so let’s take the mystery out of Spring by thoroughly analyzing all aspects of the source code.

Start with the most basic Spring initialization configuration file. Configuration file:

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

SimpleBean:

public static void main(String[] args) {
   ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
   SimpleBean bean = context.getBean(SimpleBean.class);
   bean.send();
   context.close();
}
public class SimpleBean {
   public void send() {
       System.out.println("I am send method from SimpleBean!"); }}Copy the code

Startup code:

 I am send method from SimpleBean!
Copy the code

In the code, by ClassPathXmlApplicationContext bean into the container, and then use the getBean method for bean instance object from the container.

ResourseLoader This is the class diagram of Spring loading beans. Here we can see that Spring uses the policy pattern to load instances of beans into containers. We can see in the loading method of Bean has two ClassPathXmlApplicationContect and FileSystemXmlApplicationContext, its action is loading the configuration file, the difference is we are using the file path of the difference between!

First we see the superclass constructor, we traced back to AbstractApplicationContext along the class diagram:

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

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

The path analysis PathMatchingResourcePatternResolver support Ant style. Set the configuration file path

Namely 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; }}Copy the code

GetEnvironment method from ConfigurableApplicationContext interface, source code is very simple, if is empty call createEnvironment create one. AbstractApplicationContext.createEnvironment:

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

The purpose of this method is to resolve the placeholder into the actual address. Such as so to write: new ClassPathXmlApplicationContext (” classpath: config. XML “); Then classpath: is what needs to be resolved.

protected ConfigurableEnvironment createEnvironment() {
   returnnew StandardEnvironment(); } the Environment interfaceCopy the code

Class diagram:

The Environmen interface represents the environment in which the current application operates. As can be seen from the method of the interface, it is mainly related to profile and Property.

Profile

The Spring Profile feature, which began with 3.1, was designed to address the problem of online environments and test environments using different configurations or databases or whatever. Profiles make it possible to seamlessly switch between environments. All beans managed by the Spring container are bound to a profile. Example of a Profile using a 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>
Copy the code

The active (currently used)Profile can be set in the startup code with the following code:

context.getEnvironment().setActiveProfiles(“dev”); We can also configure different profiles in the web.xml file

<context-param>  
    <param-name>spring.profiles.active</param-name>  
    <param-value>develop</param-value>  
</context-param>  
Copy the code

Property

Property here refers to some parameters of the program at runtime, to quote the comment:

properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, Ad-hoc Properties Objects,Maps, and so on. Environment constructor

    private final MutablePropertySources propertySources;
    public AbstractEnvironment() {
        this.propertySources = new MutablePropertySources(this.logger);
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources);
        if(this.logger.isDebugEnabled()) {
            this.logger.debug(String.format("Initialized %s with PropertySources %s", new Object[]{this.getClass().getSimpleName(), this.propertySources})); }}Copy the code

PropertySources interface

The class diagram

This interface is essentially a container for PropertySource, and the default MutablePropertiesSources implementation contains a CopyOnWriteArrayList as a carrier.

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()));
}
Copy the code

PropertySource interface

The PropertySource interface represents the Property source of the key-value pair. Inheritance system:

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()));
                  }
                   returnnull; }}}; }}Copy the code

The getSystemEnvironment method is also a routine, but ends up calling System.getenv to get some version information about the JVM and OS.

The implementation here is interesting. If the security manager prevents obtaining all system properties, the possibility of obtaining a single property is tried, and if not, an exception is thrown.

Path Placeholder processing

AbstractEnvironment.resolveRequiredPlaceholders: @ Override public String resolveRequiredPlaceholders (String text) throws IllegalArgumentException {/ / text configuration file path, Such as the classpath: config. XMLreturnthis.propertyResolver.resolveRequiredPlaceholders(text); } propertyResolver is a PropertySourcesPropertyResolver object: private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);  PropertyResolver interfaceCopy the code

PropertyResolver inheritance (excluding the Environment branch):

This interface is used to parse PropertyResource.

parsing

AbstractPropertyResolver.resolveRequiredPlaceholders:

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper); } PropertyPlaceholderHelper private PropertyPlaceholderHelper createPlaceholderHelper(boolean IgnoreUnresolvablePlaceholders) {/ / three parameters are respectivelyThe ${},To:return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
        this.valueSeparator, ignoreUnresolvablePlaceholders);
}
doResolvePlaceholders:

private String doResolvePlaceholders (String text, PropertyPlaceholderHelper helper) {/ / PlaceholderResolver interface still is a reflection of the strategy patternreturn helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
       @Override
       public String resolvePlaceholder(String placeholderName) {
           returngetPropertyAsRawString(placeholderName); }}); }Copy the code

PlaceHolder; placeHolder; placeHolder; placeHolder; placeHolder; placeHolder; placeHolder; placeHolder;

System.setProperty("spring"."classpath");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("${spring}:config.xml"); SimpleBean bean = context.getBean(SimpleBean.class); This will parse correctly. Placeholder substitutions are just string operations, so I'm just going to talk a little bit about where the right properties come from. The key to implementation is 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);returnvalue; }}return null;
}
Copy the code

Note that the classpath:XXX prefix has not been handled so far.

GetProperty and System.getenv are obvious, but since environment variables are not customizable, they can only be specified with System.setProperty.

refresh

Spring bean parsing is in this method, so it is presented separately.

AbstractApplicationContext.refresh:

    public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if(this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: "+ var9); } this.destroyBeans(); this.cancelRefresh(var9); throw var9; } finally { this.resetCommonCaches(); }}}Copy the code

prepareRefresh

protected void prepareRefresh() {
   this.startupDate = System.currentTimeMillis();
   this.closed.set(false);
   this.active.set(true);
   // Initialize any placeholder property sources inThe context environment // void implementation initPropertySources(); // Validate that all properties marked as required are resolvable // see ConfigurablePropertyResolver#setRequiredProperties
   getEnvironment().validateRequiredProperties();
   // Allow forthe collection of early ApplicationEvents, // to be published once the multicaster is available... this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>(); } attribute check AbstractEnvironment. ValidateRequiredProperties: @Override public void validateRequiredProperties() throws MissingRequiredPropertiesException { this.propertyResolver.validateRequiredProperties(); } AbstractPropertyResolver.validateRequiredProperties: @Override public voidvalidateRequiredProperties() {
    MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
    for (String key : this.requiredProperties) {
        if(this.getProperty(key) == null) { ex.addMissingRequiredProperty(key); }}if (!ex.getMissingRequiredProperties().isEmpty()) {
        throw ex;
    }
}
Copy the code

RequiredProperties is set using the setRequiredProperties method, stored in a list, and is empty by default, meaning no properties need to be verified.

The BeanFactory create

By obtainFreshBeanFactory call AbstractRefreshableApplicationContext. RefreshBeanFactory:

@override protected final void refreshBeanFactory() throws BeansException {// Destroy the previous one if it already existsif(hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } / / creates a DefaultListableBeanFactory object DefaultListableBeanFactory the beanFactory = createBeanFactory (); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; }}Copy the code

The BeanFactory interface

This interface is essentially a Bean container, and its inheritance is:

EanFactory custom

AbstractRefreshableApplicationContext. CustomizeBeanFactory method is used to give a subclass provides the opportunity for a free configuration, the default implementation: protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {if(this.allowBeanDefinitionOverriding ! = null) {// Defaultfalse, are not allowed to cover the beanFactory setAllowBeanDefinitionOverriding (enclosing allowBeanDefinitionOverriding); }if(this.allowCircularReferences ! = null) {// DefaultfalseCyclic references, not allowing the beanFactory. SetAllowCircularReferences (enclosing allowCircularReferences); }}Copy the code

The Bean is loaded

AbstractXmlApplicationContext loadBeanDefinitions, this is the core of bean loaded: @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { // Create a new XmlBeanDefinitionReaderfor the given BeanFactory.
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
   // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, InitBeanDefinitionReader (beanDefinitionReader); // then proceed with actually loading the bean definitions. loadBeanDefinitions(beanDefinitionReader); } EntityResolverCopy the code

Only some of the inheritance systems used are described here:

The EntityResolver interface is defined in org.xml.sax. DelegatingEntityResolver is used for schema and DTD resolution.

BeanDefinitionReader

Inheritance system:

Path Resolution (Ant)

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) {
   Resource[] configResources = getConfigResources();
   if(configResources ! = null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); //hereif(configLocations ! = null) { reader.loadBeanDefinitions(configLocations); } } AbstractBeanDefinitionReader.loadBeanDefinitions:Copy the code
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
   Assert.notNull(locations, "Location array must not be null");
   int counter = 0;
   for (String location : locations) {
       counter += loadBeanDefinitions(location);
  }
   return counter;
}
Copy the code

Then call:

Public int loadBeanDefinitions(String location, Set<Resource> actualResources) { ResourceLoader resourceLoader = getResourceLoader(); / / see ResourceLoader class diagram, ClassPathXmlApplicationContext implements this interfaceif(resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { Resource[] resources  = ((ResourcePatternResolver) resourceLoader).getResources(location); int loadCount = loadBeanDefinitions(resources);if(actualResources ! = null) {for(Resource resource : resources) { actualResources.add(resource); }}return loadCount;
      }
       catch (IOException ex) {
           throw new BeanDefinitionStoreException(
                   "Could not resolve bean definition resource pattern [" + location + "]", ex); }}else {
       // Can only load single resources by absolute URL.
       Resource resource = resourceLoader.getResource(location);
       int loadCount = loadBeanDefinitions(resource);
       if(actualResources ! = null) { actualResources.add(resource); }returnloadCount; }}Copy the code

The realization of the getResource in AbstractApplicationContext:

@override public Resource[] getResources(String locationPattern) throws IOException { PathMatchingResourcePatternResolver objectreturnthis.resourcePatternResolver.getResources(locationPattern); } PathMatchingResourcePatternResolver is part of the ResourceLoader inheritance system. @Override public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern,"Location pattern must not be null");
   //classpath:
   if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
       // a class path resource (multiple resources forSame Name possible) // Matcher is an AntPathMatcher objectif (getPathMatcher().isPattern(locationPattern
          .substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
           // a class path resource pattern
           return findPathMatchingResources(locationPattern);
      } else {
           // all class path resources with the given name
           returnfindAllClassPathResources(locationPattern .substring(CLASSPATH_ALL_URL_PREFIX.length())); }}else {
       // Only look for a pattern after a prefix here
       // (to not get fooled by a pattern symbol in a strange prefix).
       int prefixEnd = locationPattern.indexOf(":") + 1;
       if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
           // a file pattern
           return findPathMatchingResources(locationPattern);
      }
       else {
           // a single resource with the given name
           return new Resource[] {getResourceLoader().getResource(locationPattern)};
      }
  }
}
isPattern:
Copy the code

@Override public boolean isPattern(String path) { return (path.indexOf(‘*’) ! = -1 || path.indexOf(‘? ‘)! = 1); } You can see that the config file path supports the Ant style.

new ClassPathXmlApplicationContext(“con*.xml”); Loading configuration files

Entry method in AbstractBeanDefinitionReader 217 lines:

// Load Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); Int loadCount = loadBeanDefinitions(resources); Finally, call the loadBeanDefinitions method of XmlBeanDefinitionReader one by one:

@Override public int loadBeanDefinitions(Resource resource) { return loadBeanDefinitions(new EncodedResource(resource)); }

Resource is an interface that represents a Resource, and its class diagram: EncodedResource acts as a decorator pattern, adding character encoding (although null by default) to InputStreamSource. This gives us the opportunity to customize how XML configuration files are encoded. After that, the key source code is just two lines: public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { InputStream inputStream = encodedResource.getResource().getInputStream(); InputSource inputSource = new InputSource(inputStream);return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } InputSource is a class of org.xml.sax.doLoadBeanDefinitions:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {
   Document doc = doLoadDocument(inputSource, resource);
   return registerBeanDefinitions(doc, resource);
}
doLoadDocument:

protected Document doLoadDocument(InputSource inputSource, Resource resource) {
   returnthis.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); } NamespaceAware defaultfalseBecause checksum is configured by defaulttrue. The validation model is simply to determine whether XML files should be validated using XSD or DTD. If you forget, turn to Baidu. Spring determines which one to use by reading the XML file. DocumentLoader is a DefaultDocumentLoader object, and this class is the only implementation of the documentLoader interface. The getEntityResolver method returns a ResourceEntityResolver, as mentioned above. ErrorHandler is a SimpleSaxErrorHandler object. DefaultDocumentLoader.loadDocument: @Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler ErrorHandler, int validationMode, Boolean namespaceAware) {ErrorHandler, int validationMode, Boolean namespaceAware) { All load in a short period of time to memory DocumentBuilderFactory factory = createDocumentBuilderFactory (validationMode namespaceAware); DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);returnbuilder.parse(inputSource); } createDocumentBuilderFactory interesting:Copy the code
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware{
   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   factory.setNamespaceAware(namespaceAware);
   if(validationMode ! = XmlValidationModeDetector. VALIDATION_NONE) {/ / to this methodtrueValid only for DTDS, XSD (Schema) invalid Factory.setvalidating (true);
       if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
           // Enforce namespace aware forXSD... / / open XSD (schema) to support factory. SetNamespaceAware (true); SetAttribute (SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); }}return factory;
}
Copy the code