Author: Xiao Fu Ge
<br/>Blog: https://bugstack.cn
Precipitate, share, and grow, so that you and others can gain something! 😄
I. Introduction
Can the code you write meet the product and demand?
It can be connected, and it can be connected several times, even if...else of one class is okay! But it is not certain what the connection is completed, and whether an accident will occur is not controllable.
When the accident happened, you said that because I wrote too much if...else, the code was bad, but you did it first: The demand you said has to be increased,
you said that the boss let it go online,
you All the contracts mentioned are signed. I can't help the farmers who move the bricks, so I can use the pile of codes to level the demand. Too much demand is not easy to deal with, so I can use the bricks to level the demand! princes refuse to accept, I will serve the princes with soldiers. If you refuse, I will beat you to serve!
But sometimes the code is rotten, not because of the need to increase the speed, nor is it anxious to go online. Because often in the first few times of undertaking product requirements, the design of a functional logic will not be too complicated, and there will be no urgency. It will even set aside time for you to design, review, and develop. If you still can’t do this at this time Evaluate what may happen in the future into the requirements, then the chaos caused by the code has been buried from the beginning, and it can only become more and more chaotic in the future!
Accepting requirements and being able to do it well is the result of comprehensive factors such as understanding of requirements, experience in product scenario development, and ability to control code practices. Just like in the development you are doing now, which of your code changes frequently, which is fixed and common, which is responsible for logical assembly, and which is for core implementation. So now if your core shared layer has frequently changed business layer packaging, then it is certain that your code will become more and more messy, and it may even bury the risk of accidents!
In the Spring framework we have implemented, each chapter will be combined with the previous chapter to continue to expand the function, just like every product is adding requirements, then in the process of learning, you can link the comparison and reference, and take a look at each module What logic and technical details are used to achieve the addition. The learning of these contents will be very beneficial to your future design and implementation. When you undertake the specific development of the product requirements, the quality of the code will become higher and higher, and it will become more and more scalable and maintainable.
2. Goal
After completing the prototype of the Spring framework, we can now manually operate the definition, registration and attribute filling of the Bean object through unit testing, and finally obtain the object call method. But there is a problem here, that is, if you actually use this Spring framework, it is unlikely that users can create it manually, but it is best to simplify the creation process through configuration files. Need to complete the following operations:
- As shown in the figure, we need to integrate the steps: 2, 3, 4 into the Spring framework, and instantiate the Bean object through the Spring configuration file.
- Next, we need to add operations that can solve Spring configuration reading, parsing, and registering Beans in the existing Spring framework.
Three, design
According to the requirements background of this chapter, we need to add a resource parser to the existing Spring framework prototype, which is able to read the configuration content of the classpath, local files and cloud files. These configuration contents are like Spring.xml configured when using Spring, which will include the description and attribute information of the Bean object. After reading the configuration file information, the next step is to perform the registration operation after parsing the Bean description information in the configuration file, and register the Bean object in the Spring container. The overall design structure is as follows:
- The resource loader is a relatively independent part. It is located in the IO implementation content of the Spring framework core package and is mainly used to process file information in Class, local and cloud environments.
- When the resource can be loaded, the next step is to parse and register the Bean into Spring. This part of the implementation needs to be combined with the DefaultListableBeanFactory core class, because all your parsed registration actions will put the Bean definition information into this class in.
- Then, when implementing, design the implementation level relationship of the interface, including we need to define the read interface
BeanDefinitionReader
defined by the Bean and make the corresponding implementation class, and complete the analysis and registration of the Bean object in the implementation class.
Fourth, realize
1. Engineering structure
small-spring-step-05
└── src
├── 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
│ │ │ ├── BeanFactory.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── UserDao.java
│ └── UserService.java
└── ApiTest.java
project source code : public account "bugstack wormhole stack", reply: Spring column, get the complete source code
Spring Bean container resource loading and usage class relationship, as shown in Figure 6-3
- In this chapter, in order to hand over the definition, registration and initialization of Beans to Spring.xml configuration processing, then two major pieces of content need to be implemented, namely: resource loader and xml resource processing class. The implementation process is mainly based on the interface
Resource
,ResourceLoader
, and the otherBeanDefinitionReader
interface is the specific use of resources, the configuration information is registered in the Spring container. - The implementation of Resource's resource loader includes ClassPath, system files, and cloud configuration files. These three parts are consistent with the design and implementation in the Spring source code, and finally make specific calls in DefaultResourceLoader.
- Interface: BeanDefinitionReader, abstract class: AbstractBeanDefinitionReader, implementation class: XmlBeanDefinitionReader, these three parts mainly deal with the operation of the registered Bean container after the resource is read in a reasonable and clear manner. interface tube definition, abstract class handles the filling of registered Bean components outside of non-interface functions, and the final realization class can only care about the specific business realization
In addition, this chapter also refers to the Spring source code and makes the relationship between the integration and implementation of the corresponding interfaces. Although these interfaces are not very useful at present, they will also play a role as the framework is gradually improved. As shown in Figure 6-4
- BeanFactory, the existing Bean factory interface is used to obtain Bean objects, this time a new method for obtaining Bean by type is added:
<T> T getBean(String name, Class<T> requiredType)
- ListableBeanFactory is an interface that extends the Bean Factory interface. New
getBeansOfType
andgetBeanDefinitionNames()
been added. There are other extension methods in the Spring source code. - HierarchicalBeanFactory, in the Spring source code, it provides a method to obtain the parent class BeanFactory, which is a hierarchical sub-interface that extends the factory. Sub-interface implemented by bean factories that can be part of a hierarchy.
- AutowireCapableBeanFactory is an interface for automatically processing Bean factory configuration. At present, the corresponding implementation has not been done in the case project, and it will be gradually improved in the follow-up.
- ConfigurableBeanFactory can obtain a configurable interface of BeanPostProcessor, BeanClassLoader, etc.
- ConfigurableListableBeanFactory provides an interface for analyzing and modifying Beans and pre-instantiated operations, but currently there is only one getBeanDefinition method.
2. Resource loading interface definition and implementation
cn.bugstack.springframework.core.io.Resource
public interface Resource {
InputStream getInputStream() throws IOException;
}
- Create the core.io core package under the Spring framework, which is mainly used to handle the resource loading flow.
- Define the Resource interface, provide a method to obtain the InputStream stream, and then implement three different stream file operations: classPath, FileSystem, URL
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() 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;
}
}
- This part is achieved through a
ClassLoader
readingClassPath
the file information, specifically the reading process is mainly: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() throws IOException {
return new FileInputStream(this.file);
}
public final String getPath() {
return this.path;
}
}
- Reading file information by specifying the file path, this part is definitely familiar to everyone, and often read some txt, excel files and output them 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() throws IOException {
URLConnection con = this.url.openConnection();
try {
return con.getInputStream();
}
catch (IOException ex){
if (con instanceof HttpURLConnection){
((HttpURLConnection) con).disconnect();
}
throw ex;
}
}
}
- To read the files of the cloud service through HTTP, we can also put the configuration files on GitHub or Gitee.
3. Packaging Resource Loader
According to the different ways of resource loading, the resource loader can concentrate these methods under a unified class service for processing, and external users only need to pass the resource address, which simplifies the use.
defines 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);
}
- Define the resource acquisition interface and pass the location address inside.
implements 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 new FileSystemResource(location);
}
}
}
}
- In the realization of resource acquisition, three different types of resource processing methods are mainly packaged, which are divided into: judging whether it is ClassPath, URL, and file.
- Although the implementation process of the DefaultResourceLoader class is simple, it is also the specific result of the design pattern agreement. For example, the external caller will not know too many details here, but only care about the specific call result.
4. Bean definition read interface
cn.bugstack.springframework.beans.factory.support.BeanDefinitionReader
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
ResourceLoader getResourceLoader();
void loadBeanDefinitions(Resource resource) throws BeansException;
void loadBeanDefinitions(Resource... resources) throws BeansException;
void loadBeanDefinitions(String location) throws BeansException;
}
- This is a Simple interface for bean definition readers. actually defines several methods, including: getRegistry(), getResourceLoader(), and three methods for loading bean definitions.
- It should be noted here that getRegistry() and getResourceLoader() are tools used to provide the following three methods, loading and registering, and the implementation of these two methods will be packaged in abstract classes to avoid polluting specific interface implementation methods.
5. Bean definition abstract class implementation
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() {
return registry;
}
@Override
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
- The abstract class implements all the first two methods of the BeanDefinitionReader interface and provides a constructor for external callers to inject the Bean definition into the class and pass it in.
- In this way, in the concrete implementation class of the interface BeanDefinitionReader, the Bean information in the parsed XML file can be registered to the Spring container. Before, we used it through unit testing, calling BeanDefinitionRegistry to complete the registration of the Bean, and now it can be put into
6. Parse XML and handle Bean registration
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++) {
// 判断元素
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,方便获取类中的名称
Class<?> clazz = Class.forName(className);
// 优先级 id > name
String beanName = StrUtil.isNotEmpty(id) ? id : name;
if (StrUtil.isEmpty(beanName)) {
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
}
// 定义Bean
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;
// 解析标签:property
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");
}
// 注册 BeanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefinition);
}
}
}
The core content of the XmlBeanDefinitionReader class is the parsing of the XML file, which puts our original operation in the code into the way of automatic registration by parsing XML.
- The loadBeanDefinitions method handles resource loading. Here is a new internal method:
doLoadBeanDefinitions
, which is mainly responsible for parsing xml - In the doLoadBeanDefinitions method, it is mainly to read
XmlUtil.readXML(inputStream)
and parse the element. In the process of parsing, the Bean configuration and the id, name, class, value, and ref information in the configuration are obtained by looping operations. - Finally, the read configuration information is created into BeanDefinition and PropertyValue, and finally the complete Bean definition content is registered in the Bean container:
getRegistry().registerBeanDefinition(beanName, beanDefinition)
Five, test
1. Prepare in advance
cn.bugstack.springframework.test.bean.UserDao
public class UserDao {
private static Map<String, String> hashMap = new HashMap<>();
static {
hashMap.put("10001", "小傅哥");
hashMap.put("10002", "八杯水");
hashMap.put("10003", "阿毛");
}
public String queryUserName(String uId) {
return hashMap.get(uId);
}
}
cn.bugstack.springframework.test.bean.UserService
public class UserService {
private String uId;
private UserDao userDao;
public void queryUserInfo() {
return userDao.queryUserName(uId);
}
// ...get/set
}
- Dao and Service are the scenes we often use in our usual development. Inject UserDao into UserService, so that the dependency of Bean attributes can be reflected.
2. Configuration file
important.properties
# Config File
system.key=OLpj9823dZ
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>
- There are two configuration files, one is used to test the resource loader, and spring.xml is used to test the overall Bean registration function.
3. Unit testing (resource loading)
case
private DefaultResourceLoader resourceLoader;
@Before
public void init() {
resourceLoader = new DefaultResourceLoader();
}
@Test
public void test_classpath() 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() 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() 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);
}
test result
# Config File
system.key=OLpj9823dZ
Process finished with exit code 0
- These three methods: test_classpath, test_file, and test_url are used to test and load ClassPath, FileSystem, and Url files respectively. The URL file is on Github, and it may be slow to load
4. Unit testing (configuration file registration bean)
case
@Test
public void test_xml() {
// 1.初始化 BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 2. 读取配置文件&注册Bean
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions("classpath:spring.xml");
// 3. 获取Bean对象调用方法
UserService userService = beanFactory.getBean("userService", UserService.class);
String result = userService.queryUserInfo();
System.out.println("测试结果:" + result);
}
test result
测试结果:小傅哥
Process finished with exit code 0
- As you can see in the above test case, we handed over the content of Bean registration and configuration property information to the
new XmlBeanDefinitionReader(beanFactory)
class to read Spring.xml for processing, and passed the test verification.
Six, summary
- The project structure at this time has become more and more of the flavor of the Spring framework. The configuration file is used as the entrance to parse and register the Bean information, and finally the Bean is obtained through the Bean factory and the corresponding invocation operations are performed.
- Regarding the implementation of each step in the case, Brother Xiao Fu will refer to the interface definition, abstract class implementation, name specification, code structure, etc. of the Spring source code as much as possible, and make corresponding simplifications. In this way, you can learn the Spring source code through the class name or interface and the entire structure during the learning process, so that it is much easier to learn.
- After reading it, it is definitely not equal to knowing it. You can only really master the knowledge in it when you start from a small project frame structure and continue to grow bigger, more, and stronger now and in the future. In addition, the functional realization of each chapter will involve a lot of code design ideas, which should be carefully understood. Of course, practice is the best way to understand!
Seven, series recommendation
- gives you a server, can you deploy the code you write online?
- 160c349497154b I wrote 200,000 lines of code before graduation, which made me become a face
- Mathematics, how close is it to a programmer?
- A code review, almost unable to pass the trial period!
- with mathematics knowledge points, in-depth explanation of the core technology of Java 400 pages of Java Sutra
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。