preface
Through the previous article, we completed the initialization of the bean container, but we can observe that the steps of completing one container initialization are very cumbersome. It's better to inject only a few beans. Once there is a scenario where dozens or hundreds of beans need to be injected, so that users need to add beans one by one through code, I think this scheme is insulting.
This demand
In this regard, we want to make our framework more user-friendly. This article hopes to enable users to inject the required bean s into our container through simple configuration.
Design ideas
Extension point analysis
For this framework extension, we need to complete the transformation of various configuration file information into bean definition objects, and then complete bean injection.
Therefore, we need to determine whether we need to create new class objects and code extension schemes. From the perspective of demand analysis, we need to complete the following steps:
1. Get the configuration file 2. Get profile information 3. Save to bean In the definition class 4. Inject into the container
It can be seen that our fourth step is completed. We only need to complete the first three steps. In terms of the first three steps, considering the variety of configuration files, only one object is not enough. We need to define an interface Resource to make sure that each Resource object needs to do the behavior. For Resource objects, file objects need to return file information. Everyone's files may be in various formats. We need to unify the form, In order to standardize the operation, we think that the file stream is the most common return format. Finally, it is determined that the Resource class needs a method InputStream getInputStream() throws IOException;
Now that we have determined that each configuration file will return a file stream, we can load various resources. Because we define all resources as resources, we can define an interface for uniform specification. This class is named ResourceLoader and determine the behavior of Resource loading, At this time, we consider that no matter what kind of resources are loaded, I need to pass a path to the Resource loader, so we can determine the unified loading behavior of the Resource loader Resource getResource(String location); Finally, use DefaultResourceLoader to load different files.
After the resource loading is completed, we need to read the resource information, store the information in the bean definition class object, and finally inject it into the container. We need to define a bean definition loader. Loading the bean definition object will convert the resource information in the resource loader into a bean definition object and then save it to the container. Due to the amount of code, it is not convenient to express. The author expounds the bean definition loader interface definition in the form of comments
package cn.shark.springframework.beans.factory.support; import cn.shark.springframework.beans.BeansException; import cn.shark.springframework.beans.factory.core.io.Resource; import cn.shark.springframework.beans.factory.core.io.ResourceLoader; public interface BeanDefinitionReader { BeanDefinitionRegistry getRegistry(); ResourceLoader getResourceLoader(); void loadBeanDefinitions(Resource resource) throws BeansException; /** * * The following two interfaces are required for expansion, * Subsequent bean definitions may obtain bean definition information through file paths or multiple resource objects * So write these two interface definitions * */ void loadBeanDefinitions(Resource... resource) throws BeansException; void loadBeanDefinitions(String location) throws BeansException; }
After completing the bean definition of the loader interface definition, we need an abstract class AbstractBeanDefinitionReader to register the Bean component outside the non interface function. That is, all bean defines initialization of the common component information of the loader, and then implements the specific function to the concrete implementation of class management, which is the essence of the spring framework design.
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{ private final BeanDefinitionRegistry registry; private ResourceLoader resourceLoader; public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { this(registry,new DefaultResourceLoader()); } public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) { this.registry = registry; this.resourceLoader = resourceLoader; } @Override public BeanDefinitionRegistry getRegistry() { return registry; } @Override public ResourceLoader getResourceLoader() { return resourceLoader; } }
Class diagram
Code structure
├─src │ ├─main │ │ ├─java │ │ │ └─cn │ │ │ └─shark │ │ │ └─springframework │ │ │ └─beans │ │ │ └─factory │ │ │ ├─config │ │ │ ├─core │ │ │ │ └─io │ │ │ ├─support │ │ │ ├─util │ │ │ └─xml │ │ └─resources │ └─test │ ├─java │ │ └─cn │ │ └─shark │ │ └─springframework │ │ └─bean │ └─resources └─target ├─classes │ ├─cn │ │ └─shark │ │ └─springframework │ │ └─beans │ │ └─factory │ │ ├─config │ │ ├─core │ │ │ └─io │ │ ├─support │ │ ├─util │ │ └─xml │ └─META-INF ├─generated-sources │ └─annotations ├─generated-test-sources │ └─test-annotations └─test-classes ├─cn │ └─shark │ └─springframework │ └─bean └─META-INF
Code example
Resource
package cn.shark.springframework.beans.factory.core.io; import java.io.IOException; import java.io.InputStream; public interface Resource { InputStream getInputStream() throws IOException; }
Resource
package cn.shark.springframework.beans.factory.core.io; import java.io.IOException; import java.io.InputStream; public interface Resource { InputStream getInputStream() throws IOException; }
ClassPathResource
package cn.shark.springframework.beans.factory.core.io; import cn.hutool.core.lang.Assert; import cn.shark.springframework.beans.factory.util.ClassUtils; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; 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() throws IOException { InputStream is = classLoader.getResourceAsStream(path); if (is == null) { throw new FileNotFoundException( this.path + " cannot be opened because it does not exist"); } return is; } }
FileSystemResource
package cn.shark.springframework.beans.factory.core.io; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class FileSystemResource implements Resource { private final String path; private final File file; public FileSystemResource(String path, File file) { this.path = path; this.file = file; } public FileSystemResource(String path) { this.path = path; this.file = new File(path); } @Override public InputStream getInputStream() throws IOException { return new FileInputStream(this.file); } public String getPath() { return path; } }
UrlResource
package cn.shark.springframework.beans.factory.core.io; import cn.hutool.core.lang.Assert; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; public class UrlResource implements Resource { private final URL url; public UrlResource(URL url) { Assert.notNull(url, "url must be not null"); this.url = url; } @Override public InputStream getInputStream() throws IOException { URLConnection con = this.url.openConnection(); try { return con.getInputStream(); } catch (IOException ex) { if (con instanceof HttpURLConnection) { ((HttpURLConnection) con).disconnect(); } throw ex; } } }
ResourceLoader
package cn.shark.springframework.beans.factory.core.io; public interface ResourceLoader { String CLASSPATH_URL_PREFIX = "classpath:"; Resource getResource(String location); }
DefaultResourceLoader
package cn.shark.springframework.beans.factory.core.io; import cn.hutool.core.lang.Assert; import java.net.MalformedURLException; import java.net.URL; public class DefaultResourceLoader implements ResourceLoader { @Override public Resource getResource(String location) { Assert.notNull(location, "Location must be not 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 new FileSystemResource(location); } } } }
BeanDefinitionReader
package cn.shark.springframework.beans.factory.support; import cn.shark.springframework.beans.BeansException; import cn.shark.springframework.beans.factory.core.io.Resource; import cn.shark.springframework.beans.factory.core.io.ResourceLoader; public interface BeanDefinitionReader { BeanDefinitionRegistry getRegistry(); ResourceLoader getResourceLoader(); void loadBeanDefinitions(Resource resource) throws BeansException; /** * * The following two interfaces are required for expansion, * Subsequent bean definitions may obtain bean definition information through file paths or multiple resource objects * So write these two interface definitions * */ void loadBeanDefinitions(Resource... resource) throws BeansException; void loadBeanDefinitions(String location) throws BeansException; }
AbstractBeanDefinitionReader
package cn.shark.springframework.beans.factory.support; import cn.shark.springframework.beans.factory.core.io.DefaultResourceLoader; import cn.shark.springframework.beans.factory.core.io.ResourceLoader; public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{ private final BeanDefinitionRegistry registry; private ResourceLoader resourceLoader; public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { this(registry,new DefaultResourceLoader()); } public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) { this.registry = registry; this.resourceLoader = resourceLoader; } @Override public BeanDefinitionRegistry getRegistry() { return registry; } @Override public ResourceLoader getResourceLoader() { return resourceLoader; } }
XmlBeanDefinitionReader
package cn.shark.springframework.beans.factory.xml; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.XmlUtil; import cn.shark.springframework.beans.BeansException; import cn.shark.springframework.beans.PropertyValue; import cn.shark.springframework.beans.factory.config.BeanDefinition; import cn.shark.springframework.beans.factory.config.BeanReference; import cn.shark.springframework.beans.factory.core.io.Resource; import cn.shark.springframework.beans.factory.core.io.ResourceLoader; import cn.shark.springframework.beans.factory.support.AbstractBeanDefinitionReader; import cn.shark.springframework.beans.factory.support.BeanDefinitionRegistry; import com.sun.deploy.util.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import java.io.IOException; import java.io.InputStream; 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++) { if (!((childNodes.item(i)) instanceof Element)) { continue; } if (!("bean".equals(childNodes.item(i).getNodeName()))) { continue; } Element bean = (Element) childNodes.item(i); String id = bean.getAttribute("id"); String name = bean.getAttribute("name"); String className = bean.getAttribute("class"); Class<?> clazz = Class.forName(className); String beanName = StrUtil.isNotEmpty(id) ? id : name; if (StrUtil.isEmpty(beanName)) { beanName = StrUtil.lowerFirst(clazz.getSimpleName()); } BeanDefinition beanDefinition = new BeanDefinition(clazz); 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; } Element property = (Element) bean.getChildNodes().item(j); String attrName = property.getAttribute("name"); String attrValue = property.getAttribute("value"); String attrRef = property.getAttribute("ref"); Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue; PropertyValue propertyValue = new PropertyValue(attrName, value); beanDefinition.getPropertyValues().addPropertyValue(propertyValue); } if (getRegistry().containsBeanDefinition(beanName)) { throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed"); } getRegistry().registerBeanDefinition(beanName, beanDefinition); } } }
test
Create spring.xml under resource
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="userDao" class="cn.shark.springframework.bean.UserDao"/> <bean id="userService" class="cn.shark.springframework.bean.UserService"> <property name="uId" value="10001"/> <property name="userDao" ref="userDao"/> </bean> </beans>
Test code
package cn.shark.springframework; import cn.shark.springframework.bean.UserDao; import cn.shark.springframework.bean.UserService; import cn.shark.springframework.beans.PropertyValue; import cn.shark.springframework.beans.PropertyValues; import cn.shark.springframework.beans.factory.config.BeanDefinition; import cn.shark.springframework.beans.factory.config.BeanReference; import cn.shark.springframework.beans.factory.support.DefaultListableBeanFactory; import cn.shark.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.junit.Test; public class ApiTest { @Test public void test() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerBeanDefinition("userDao", new BeanDefinition(UserDao.class)); PropertyValues propertyValues = new PropertyValues(); propertyValues.addPropertyValue(new PropertyValue("uId", "1")); propertyValues.addPropertyValue(new PropertyValue("userDao", new BeanReference("userDao"))); BeanDefinition beanDefinition = new BeanDefinition(UserService.class, propertyValues); beanFactory.registerBeanDefinition("userService", beanDefinition); UserService userService = (UserService) beanFactory.getBean("userService"); userService.queryUserInfo(); } @Test public void test_xml() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader beanDefinitionReader= new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.loadBeanDefinitions("classpath:spring.xml"); UserService userService= (UserService) beanFactory.getBean("userService"); userService.queryUserInfo(); } }
summary
It can be seen that standardized project design, through a large number of single duty principle interface decoupling and aggregation association relation combination object, guarantees the demand expansion without changing the original code logic, which is the essence of spring framework design.
reference
UML Foundation (with drawing tutorial)
Chapter 6 of Spring hand column: Swallow mountains and rivers, design and implement resource loader, parse and register Bean objects from Spring.xml