文章目录
前言
通过上一篇文章,我们完成bean容器的初始化,但是我们可以观察到完成一次容器初始化步骤十分繁琐,要是仅仅注入几个bean还好。一旦出现需要注入几十个或者上百个bean的场景,使得用户需通过代码去一个个添加bean的方式,我认为这种方案简直是在侮辱人。
本次需求
对此,我们要让我们的框架更加人性化一点,我们本篇文章希望做到能够通过简单的配置的方式,就能让用户将需要的bean注入到我们的容器中。
设计思路
扩展点分析
对于本次框架扩展,我们需要完成将各种配置文件信息转化为bean定义对象,再完成bean的注入。
所以,我们需要确定是否需要建立新的类对象,以及代码扩展方案。从需求的分析来看,我们需要完成以下步骤:
1. 拿到配置文件
2. 获取配置文件信息
3. 存到bean定义类中
4. 注入容器中
可以看出我们第四步是完成了,我们只需完成前三步即可。就前三步而言,我们考虑到配置文件多种多样,所以仅仅一个对象是不够,我们需要定一个接口Resource确定一下每个资源对象都需要做到行为,对于Resource对象而言,文件对象都需要返回文件信息,大家文件按格式可能多种多样,我们需要进行统一形式,才能规范操作,所以我们认为文件流是最通用的返回格式,最终确定Resource类需要有一个方法InputStream getInputStream() throws IOException;
既然我们确定了每种配置文件都会返回文件流,那么我们就可以进行各种各样的资源加载了,因为我们将所有的资源都定义为Resource,那么我们资源加载就可以定义一个接口进行统一规范,这个类我们就命名为ResourceLoader,并确定资源加载的行为,这时候我们考虑到无论加载何种资源,我都需要传给资源加载器一个路径,那么我们就可以去确定这个资源加载器的统一加载行为Resource getResource(String location);最终再用DefaultResourceLoader完成不同文件的加载即可。
完成了资源加载,我们就需要完成资源信息的读取,以及将信息存到bean定义类对象中,最后再注入到容器中。我们我们需要定义一个bean定义加载器,这个bean定义对象加载会将资源加载器中的资源信息转化为bean定义对象再存到容器中。由于代码量的原因不方便表述,笔者就以注释的形式阐述bean定义加载器接口定义
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;
/**
*
* 以下两个接口是处于扩展需要,
* 后续bean定义可能会通过文件路径或者多个资源对象才能获取到bean定义信息
* 所以就编写这两个接口定义
*
*/
void loadBeanDefinitions(Resource... resource) throws BeansException;
void loadBeanDefinitions(String location) throws BeansException;
}
完成bean定义加载器接口定义后,我们需要一个抽象类AbstractBeanDefinitionReader非接口功能外的注册Bean组件填充,即所有bean定义加载器通用的组件信息初始化,然后将具体功能实现给具体实现类处理,这就是spring框架设计的思想精华
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;
}
}
类图
代码结构
├─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
代码示例
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;
/**
*
* 以下两个接口是处于扩展需要,
* 后续bean定义可能会通过文件路径或者多个资源对象才能获取到bean定义信息
* 所以就编写这两个接口定义
*
*/
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);
}
}
}
测试
resource下建立spring.xml
<?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>
测试代码
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();
}
}
总结
可以看出规范的项目设计,通过大量单一职责原则的接口解耦配合聚合关联关系组合对象,保证了需求扩展无需改动原有的代码逻辑,这就是spring框架设计的精华。
参考文献
UML基础(附绘制教程)
《Spring 手撸专栏》第 6 章:气吞山河,设计与实现资源加载器,从Spring.xml解析和注册Bean对象