Blog: bugstack.cn

Precipitation, share, grow, let yourself and others can gain something! 😄

One, foreword

Can you write code that meets the product plus requirements?

Connect, is able to connect, connect several times also line, even if it is a piece of if… Else! But what kind of completion can not necessarily, will not be an accident is not able to control.

When the accident happened, you said it was because I wrote if… Else more lead to bad code, but you moved the hand ah: you said the demand has to add, you said the boss let online, you said the contract are signed, I can’t move the brick code farmers, just heap code level demand, demand too much is not easy, I just move the brick level demand! Vassals refuse to accept, I will serve vassals with soldiers, you refuse to accept, I will fight you to accept!

But sometimes code doesn’t break because demand is piling up fast, or because it’s in a rush to launch. Because a few times, before the undertake product demand of a logic function design will not be too complicated, there will be no more urgent, even had transfer you do design, review, development time, if this time still not what might happen after evaluation to the demand, so lead to chaos in the code from the beginning has been planted, later can only become more and more disorderly!

Taking on requirements and doing them well is the result of understanding requirements, experience in product scenario development, and the ability to control code practices. Just like in your current development, what parts of your code are constantly changing, what parts are fixed, what parts are logical assembly, and what parts are core implementation. Now if your core common layer is wrapped in a business layer that changes frequently, it’s safe to say that your code is getting messy and may even be at risk for accidents!

In our implementation of the Spring framework, each chapter will continue to expand the functionality of the previous chapter, just as each product is adding requirements, so in the process of learning can be compared and referred to see what logic and technical details are used to realize the addition of each module. The learning of these contents will be very conducive to your future design and implementation, and specific development when you undertake product requirements, and the quality of the code will be higher and higher, more and more extensible and maintainable.

Second, the target

With a prototype of the Spring framework in place, we can now use unit tests to manually manipulate the definition, registration, and property population of Bean objects, and finally get object invocation methods. There is a problem, however, that if you actually use the Spring framework, it is unlikely that you will be able to create it manually, and it is best to simplify the creation process by using configuration files. You need to perform the following operations:

  • In the figure, we need to integrate steps 2, 3, and 4 into the Spring framework to instantiate the Bean object through the Spring configuration file.
  • Next we need to add operations to the existing Spring framework to read, parse, and register beans that address the Spring configuration.

Three, the design

Based on the requirements of this section, we need to add a resource parser to the existing prototype Of the Spring framework that reads the configuration of classpath, local files, and cloud files. This configuration is just like the spring.xml configuration used with Spring, which contains the Bean object’s description and attribute information. After reading the configuration file information, the next step is to parse the Bean description in the configuration file and register the Bean object into the Spring container. The overall design structure is as follows:

  • The resource loader is a relatively independent part of the IO implementation that resides in the Core package of the Spring framework and is used to process file information in Class, local, and cloud environments.
  • When resources can be loaded, then there’s parsing and registered Bean to the operation, in the Spring the partial implementation needs and DefaultListableBeanFactory core class together, because you all registered the resolved action, will define the Bean information into to this class.
  • Then design the implementation hierarchy of the interface at implementation time, including the read interface that we need to define the Bean definitionBeanDefinitionReaderAs well as the corresponding implementation class, in the implementation class to complete the Bean object parsing and registration.

Four, implementation,

1. Engineering structure

small-spring-step-05└ ─ ─ the SRC ├ ─ ─ the main │ └ ─ ─ Java │ └ ─ ─ cn. Bugstack. Springframework │ ├ ─ ─ beans │ │ ├ ─ ─ factory │ │ │ ├ ─ ─ factory │ │ │ │ ├ ─ ─ AutowireCapableBeanFactory. Java │ │ │ │ ├ ─ ─ BeanDefinition. Java │ │ │ │ ├ ─ ─ BeanReference. Java │ │ │ │ ├ ─ ─ ConfigurableBeanFactory. Java │ │ │ │ └ ─ ─ SingletonBeanRegistry. Java │ │ │ ├ ─ ─ support │ │ │ │ ├ ─ ─ AbstractAutowireCapableBeanFactory. Java │ │ │ │ ├ ─ ─ AbstractBeanDefinitionReader. Java │ │ │ │ ├ ─ ─ AbstractBeanFactory. Java │ │ │ │ ├ ─ ─ BeanDefinitionReader. Java │ │ │ │ ├ ─ ─ BeanDefinitionRegistry. Java │ │ │ │ ├ ─ ─ CglibSubclassingInstantiationStrategy. Java │ │ │ │ ├ ─ ─ DefaultListableBeanFactory. Java │ │ │ │ ├ ─ ─ DefaultSingletonBeanRegistry. Java │ │ │ │ ├ ─ ─ InstantiationStrategy. Java │ │ │ │ └ ─ ─ SimpleInstantiationStrategy. Java │ │ │ ├ ─ ─ support │ │ │ │ └ ─ ─ XmlBeanDefinitionReader. Java │ │ │ ├ ─ ─ the BeanFactory. Java │ │ │ ├ ─ ─ ConfigurableListableBeanFactory. Java │ │ │ ├ ─ ─ HierarchicalBeanFactory. Java │ │ │ └ ─ ─ ListableBeanFactory. Java │ │ ├ ─ ─ BeansException. Java │ │ ├ ─ ─ PropertyValue. Java │ │ └ ─ ─ PropertyValues. Java │ ├ ─ ─ core. IO │ │ ├ ─ ─ ClassPathResource. Java │ │ ├ ─ ─ DefaultResourceLoader. Java │ │ ├ ─ ─ FileSystemResource. Java │ │ ├ ─ ─ the Resource. The Java │ │ ├ ─ ─ ResourceLoader. Java │ │ └ ─ ─ UrlResource. Java │ └ ─ ─ utils │ └ ─ ─ ClassUtils. Java └ ─ ─ the test └ ─ ─ Java └ ─ ─ cn. Bugstack. Springframework. Test ├ ─ ─ a bean │ ├ ─ ─ UserDao. Java │ └ ─ ─ UserService. Java └ ─ ─ ApiTest. JavaCopy the code

Project source: public account “bugStack wormhole stack”, reply: Spring column, get the full source code

Spring Bean container resources load and use class relationships, as shown in Figure 6-3

  • In order to enable the definition, registration and initialization of beans to be handed over to the spring.xml configuration process, we need to implement two large chunks of content, namely: resource loader and XML resource processing class. The implementation process is mainly to interfaceResource,ResourceLoaderThe implementation of, and otherBeanDefinitionReaderInterfaces are the concrete use of resources, registering configuration information to the Spring container.
  • In the implementation of Resource loader includes, ClassPath, system files, cloud configuration files, these three parts are consistent with the design and implementation of Spring source code, and finally do specific calls in DefaultResourceLoader.
  • Interface: BeanDefinitionReader, abstract class: AbstractBeanDefinitionReader, implementation class: XmlBeanDefinitionReader, these three parts mainly handle reasonably clear operations for registered Bean containers after resource reads.An interface tube definition, an abstract class that handles the population of registered Bean components outside of the interface function, and finally an implementation class that only cares about the concrete business implementation

In addition, this chapter also refers to Spring source code and makes the relationship between the integration and implementation of corresponding interfaces. Although these interfaces are not very important at present, they will also play a role with the gradual improvement of the framework. As shown in figure 6-4

  • BeanFactory, the existing Bean factory interface for getting Bean objects, has been added to get beans by type:<T> T getBean(String name, Class<T> requiredType)
  • ListableBeanFactory, an interface that extends the Bean factory interface, has been addedgetBeansOfType,getBeanDefinitionNames()Method, and there are other extension methods in Spring source code.
  • HierarchicalBeanFactory, which in the Spring source provides a method to retrieve the parent BeanFactory class, is a HierarchicalBeanFactory subinterface.Sub-interface implemented by bean factories that can be part of a hierarchy.
  • AutowireCapableBeanFactory, is an automated processing Bean plant configuration interface, there was no case in the project implementation accordingly, follow-up gradually improve.
  • ConfigurableBeanFactory: Obtains a configuration interface for BeanPostProcessor, BeanClassLoader, and so on.
  • ConfigurableListableBeanFactory, provide analysis and modify the Bean and instantiate the operation interface in advance, but only a getBeanDefinition method at present.

2. Resource loading interface definition and implementation

cn.bugstack.springframework.core.io.Resource

public interface Resource {

    InputStream getInputStream(a) throws IOException;

}
Copy the code
  • Create the core. IO core package under the Spring framework, which is used to handle resource loading streams.
  • The Resource interface is defined to provide a method for retrieving the InputStream stream, followed by three different stream file operations: classPath, FileSystem, and URL

The ClassPath: cn. Bugstack. Springframework. Core. IO. ClassPathResource

public class ClassPathResource implements Resource {

    private final String path;

    private ClassLoader classLoader;

    public ClassPathResource(String path) {
        this(path, (ClassLoader) null);
    }

    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        this.path = path;
        this.classLoader = (classLoader ! =null ? classLoader : ClassUtils.getDefaultClassLoader());
    }

    @Override
    public InputStream getInputStream(a) throws IOException {
        InputStream is = classLoader.getResourceAsStream(path);
        if (is == null) {
            throw new FileNotFoundException(
                    this.path + " cannot be opened because it does not exist");
        }
        returnis; }}Copy the code
  • This part of the implementation is used to passClassLoaderread ClassPathThe specific reading process is mainly as follows:classLoader.getResourceAsStream(path)

FileSystem: cn. Bugstack. Springframework. Core. IO. FileSystemResource

public class FileSystemResource implements Resource {

    private final File file;

    private final String path;

    public FileSystemResource(File file) {
        this.file = file;
        this.path = file.getPath();
    }

    public FileSystemResource(String path) {
        this.file = new File(path);
        this.path = path;
    }

    @Override
    public InputStream getInputStream(a) throws IOException {
        return new FileInputStream(this.file);
    }

    public final String getPath(a) {
        return this.path; }}Copy the code
  • Read file information by specifying the file path, this part we are certainly very familiar with, often read some TXT, Excel file output to the console.

Url: cn. Bugstack. Springframework. Core. IO. UrlResource

public class UrlResource implements Resource{

    private final URL url;

    public UrlResource(URL url) {
        Assert.notNull(url,"URL must not be null");
        this.url = url;
    }

    @Override
    public InputStream getInputStream(a) throws IOException {
        URLConnection con = this.url.openConnection();
        try {
            return con.getInputStream();
        }
        catch (IOException ex){
            if (con instanceof HttpURLConnection){
                ((HttpURLConnection) con).disconnect();
            }
            throwex; }}}Copy the code
  • Read the cloud service files via HTTP, or put the configuration files on GitHub or Gitee.

3. Wrap the resource loader

According to the different methods of resource loading, the resource loader can concentrate these methods into a unified class service for processing, external users only need to pass the resource address, simplify the use.

Define the interface: cn bugstack. Springframework. Core. IO. ResourceLoader

public interface ResourceLoader {

    /** * Pseudo URL prefix for loading from the class path: "classpath:" */
    String CLASSPATH_URL_PREFIX = "classpath:";

    Resource getResource(String location);

}
Copy the code
  • Define the get resource interface, which passes the location address.

Implementing an interface: cn bugstack. Springframework. Core. IO. DefaultResourceLoader

public class DefaultResourceLoader implements ResourceLoader {

    @Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
        }
        else {
            try {
                URL url = new URL(location);
                return new UrlResource(url);
            } catch (MalformedURLException e) {
                return newFileSystemResource(location); }}}}Copy the code
  • In the realization of obtaining resources, three different types of resource processing methods are mainly wrapped, divided into: determine whether it is ClassPath, URL and file.
  • While the implementation of the DefaultResourceLoader class is simple, it is also a concrete result of design pattern conventions, such as not letting external callers know too much about the details, but only the results of specific calls.

4. Bean defines the read interface

cn.bugstack.springframework.beans.factory.support.BeanDefinitionReader

public interface BeanDefinitionReader {

    BeanDefinitionRegistry getRegistry(a);

    ResourceLoader getResourceLoader(a);

    void loadBeanDefinitions(Resource resource) throws BeansException;

    void loadBeanDefinitions(Resource... resources) throws BeansException;

    void loadBeanDefinitions(String location) throws BeansException;

}
Copy the code
  • This is aSimple interface for bean definition readers.There are several methods defined, including getRegistry(), getResourceLoader(), and three methods for loading Bean definitions.
  • Note here that getRegistry() and getResourceLoader() are tools provided for loading and registering the latter three methods, whose implementations are wrapped in abstract classes so as not to contaminate the concrete interface implementation methods.

5. Beans define abstract class implementations

cn.bugstack.springframework.beans.factory.support.AbstractBeanDefinitionReader

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    private final BeanDefinitionRegistry registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry, new DefaultResourceLoader());
    }

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        this.registry = registry;
        this.resourceLoader = resourceLoader;
    }

    @Override
    public BeanDefinitionRegistry getRegistry(a) {
        return registry;
    }

    @Override
    public ResourceLoader getResourceLoader(a) {
        returnresourceLoader; }}Copy the code
  • The abstract class implements the first two BeanDefinitionReader methods and provides constructors for external callers to inject Bean definitions into the class and pass them in.
  • This means that Bean information in the parsed XML file can be registered with the Spring container in the concrete implementation class of the interface BeanDefinitionReader.Previously, we used to use unit tests to register beans by calling BeanDefinitionRegistry. Now we can do this in XMl

6. Parse the XML processing Bean registry

cn.bugstack.springframework.beans.factory.xml.XmlBeanDefinitionReader

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        super(registry, resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try {
            try(InputStream inputStream = resource.getInputStream()) { doLoadBeanDefinitions(inputStream); }}catch (IOException | ClassNotFoundException e) {
            throw new BeansException("IOException parsing XML document from "+ resource, e); }}@Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for(Resource resource : resources) { loadBeanDefinitions(resource); }}@Override
    public void loadBeanDefinitions(String location) throws BeansException {
        ResourceLoader resourceLoader = getResourceLoader();
        Resource resource = resourceLoader.getResource(location);
        loadBeanDefinitions(resource);
    }

    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
        Document doc = XmlUtil.readXML(inputStream);
        Element root = doc.getDocumentElement();
        NodeList childNodes = root.getChildNodes();

        for (int i = 0; i < childNodes.getLength(); i++) {
            // Determine the element
            if(! (childNodes.item(i)instanceof Element)) continue;
            // Determine the object
            if (!"bean".equals(childNodes.item(i).getNodeName())) continue;
            
            // Parse the tag
            Element bean = (Element) childNodes.item(i);
            String id = bean.getAttribute("id");
            String name = bean.getAttribute("name");
            String className = bean.getAttribute("class");
            // Get Class to get the name of the ClassClass<? > clazz = Class.forName(className);// Priority id > name
            String beanName = StrUtil.isNotEmpty(id) ? id : name;
            if (StrUtil.isEmpty(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }

            / / define the Bean
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            // Read the property and populate it
            for (int j = 0; j < bean.getChildNodes().getLength(); j++) {
                if(! (bean.getChildNodes().item(j)instanceof Element)) continue;
                if (!"property".equals(bean.getChildNodes().item(j).getNodeName())) continue;
                // Parse the label: property
                Element property = (Element) bean.getChildNodes().item(j);
                String attrName = property.getAttribute("name");
                String attrValue = property.getAttribute("value");
                String attrRef = property.getAttribute("ref");
                // Get attribute value: import object, value object
                Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;
                // Create attribute information
                PropertyValue propertyValue = new PropertyValue(attrName, value);
                beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
            }
            if (getRegistry().containsBeanDefinition(beanName)) {
                throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
            }
            / / register BeanDefinitiongetRegistry().registerBeanDefinition(beanName, beanDefinition); }}}Copy the code

The XmlBeanDefinitionReader class, at its core, parses XML files, putting what we do in code into automatic registration by parsing XML.

  • The loadBeanDefinitions method handles resource loading with a new internal method:doLoadBeanDefinitions, which is responsible for parsing XML
  • In the doLoadBeanDefinitions method, XML is readXmlUtil.readXML(inputStream)And Element Element parsing. In the process of parsing through a loop operation, in order to obtain Bean configuration and configuration of ID, name, class, value, ref information.
  • Finally, the read configuration information is created into BeanDefinition and PropertyValue, and the complete Bean definition content is registered in the Bean container:getRegistry().registerBeanDefinition(beanName, beanDefinition)

Five, the test

1. Prepare

cn.bugstack.springframework.test.bean.UserDao

public class UserDao {

    private static Map<String, String> hashMap = new HashMap<>();

    static {
        hashMap.put("10001".Little Fuge.);
        hashMap.put("10002"."Eight glasses of water");
        hashMap.put("10003"."Will");
    }

    public String queryUserName(String uId) {
        returnhashMap.get(uId); }}Copy the code

cn.bugstack.springframework.test.bean.UserService

public class UserService {

    private String uId;

    private UserDao userDao;

    public void queryUserInfo(a) {
        return userDao.queryUserName(uId);
    }

    / /... get/set
}
Copy the code
  • Dao and Service are common development scenarios. Inject the UserDao into the UserService so that the Bean property dependencies are reflected.

2. Configuration file

important.properties

# Config File
system.key=OLpj9823dZ
Copy the code

spring.xml


      
<beans>

    <bean id="userDao" class="cn.bugstack.springframework.test.bean.UserDao"/>

    <bean id="userService" class="cn.bugstack.springframework.test.bean.UserService">
        <property name="uId" value="10001"/>
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>
Copy the code
  • There are two configuration files, one to test the resource loader, and spring.xml to test the overall Bean registration functionality.

3. Unit testing (resource loading)

case

private DefaultResourceLoader resourceLoader;      

@Before
public void init(a) {
    resourceLoader = new DefaultResourceLoader();
}   

@Test
public void test_classpath(a) throws IOException {
    Resource resource = resourceLoader.getResource("classpath:important.properties");
    InputStream inputStream = resource.getInputStream();
    String content = IoUtil.readUtf8(inputStream);
    System.out.println(content);
}   

@Test
public void test_file(a) throws IOException {
    Resource resource = resourceLoader.getResource("src/test/resources/important.properties");
    InputStream inputStream = resource.getInputStream();
    String content = IoUtil.readUtf8(inputStream);
    System.out.println(content);
}    

@Test
public void test_url(a) throws IOException {
    Resource resource = resourceLoader.getResource("https://github.com/fuzhengwei/small-spring/important.properties"
    InputStream inputStream = resource.getInputStream();
    String content = IoUtil.readUtf8(inputStream);
    System.out.println(content);
}
Copy the code

The test results

# Config File
system.key=OLpj9823dZ

Process finished with exit code 0
Copy the code
  • Test_classpath, test_file, and test_URL are used to test loading ClassPath, FileSystem, and Url files respectively.The URL file is on Github and may be slow to load

4. Unit testing (configuration file registration Bean)

case

@Test
public void test_xml(a) {
    1. Initialize the BeanFactory
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

    // 2. Read config file & register Bean
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    reader.loadBeanDefinitions("classpath:spring.xml");

    // 3. Get the Bean object to call the method
    UserService userService = beanFactory.getBean("userService", UserService.class);
    String result = userService.queryUserInfo();
    System.out.println("Test Results:" + result);
}
Copy the code

The test results

Process finished with exit code0
Copy the code
  • As you can see in the above test case, we left it up to us to manually register the Bean and configure the property informationnew XmlBeanDefinitionReader(beanFactory)Class to read spring.xml and pass the test.

Six, summarized

  • At this point, the engineering structure has become more and more like the Spring framework, with configuration files as the entrance to parse and register Bean information, and finally through the Bean factory to obtain beans and do the corresponding call operations.
  • As for the realization of each step in the case, Xiaofuge will try his best to refer to Spring source code interface definition, abstract class implementation, name specification, code structure, etc., to do the corresponding simplification. In this way, you can also learn Spring source code by class name or interface and whole structure, which makes learning much easier.
  • Read the absolute is not equal to will, you only move a hand from a small engineering frame structure, knock now and later constantly become larger, more, stronger, to really master the knowledge here. In addition, the function of each chapter will involve a lot of code design ideas, to seriously understand.Of course practice is the best way to learn!

Seven, series recommendation

  • Given a server, can you deploy your code online?
  • I wrote 200,000 lines of code before graduation, which made me a face bully in the eyes of my classmates!
  • Math, how close is it to a programmer?
  • A code review, almost failed the trial period!
  • Start with the mathematics knowledge, in-depth explanation of Java core technology 400 pages of Java