前言
桃李春风一杯酒,江南夜雨十年灯。
前一节我们已经完成了Bean的属性注入。但是在测试的过程中,能很明显的感觉到,需要配置初始化很多的属性。spring 解决的问题就是 Bean 的管理问题,所以肯定不会让大家去这样操作滴。
为了解决这个问题,spring提供了 通过 xml文件配置的方式完成bean的注入。通过在xml中配置bean的基本信息,来解决bean的注入问题。简单了解下xml,我们看一下 GPT 的回答。(看起来很不错!)
XML是什么?:XML是一种可扩展标记语言(Extensible Markup Language),用于描述和传输数据。它是一种文本格式,类似于HTML,但被设计为更通用的标记语言,可用于表示任何结构化数据。XML的语法规则要求所有元素必须有一个开始标签和一个结束标签,并且标签必须成对出现。它的主要用途是在网络上传输数据,但也可以用于本地数据存储和处理。XML被广泛应用于Web服务、电子商务、数据库管理、配置文件等领域。
实现
资源解析、注册、加载
该功能作为 spring 的一个增强的功能,主要的话就是 两个模块 资源解析与注册 和 资源加载资源加载是一个独立的模块,通过 资源注册与解析,来与给spring提供服务
资源加载
定义 cn.anoxia.springframework.core.io.Resource 接口,提供统一闸口方法。
/**
* 资源加载接口
*/
public interface Resource {
InputStream getInputStream() throws IOException;
}
资源加载的方式通常有多中,不同的方式只需要 实现 getInputStream 方法,调用着不必关心内部的实现,实现隔离。这里使用 策略模式+工厂模式 去设计。(通常遇到这种有多种不同的处理方式、我们都可以考虑使用策略+工厂来设计代码结构)。这样设计的好处就是。
- 1、每一种不同的类型的
职责的单一的,只需要管理自己的处理逻辑。 - 2、如果要新增一种处理逻辑,只需要添加一个新的类就可以,不会相互污染。
- 3、用工厂可以很方便的管理起来所以的
实现类
我们主要使用的是三种资源加载的方式,classpath 下 的文件加载、系统文件加载、网络文件加载
ClassPathResource 文件加载器
加载 classpath 下的文件
/**
* @author huangle
* @date 2023/2/9 16:42
*/
public class ClassPathResource implements Resource{
private final String path;
private ClassLoader classLoader;
public ClassPathResource(String path) {
this(path, null);
}
public ClassPathResource(String path, ClassLoader classLoader) {
Assert.assertNotNull(path,"path not null!");
this.path = path;
this.classLoader = (classLoader != null ? classLoader : ClassUtil.getContextClassLoader());
}
@Override
public InputStream getInputStream() throws IOException {
InputStream inputStream = classLoader.getResourceAsStream(path);
if (inputStream == null) {
throw new FileNotFoundException(
this.path + " cannot be opened because it does not exist");
}
return inputStream;
}
}
SystemFileResource 加载器
通过文件,或者文件路径来加载系统文件
/**
* @author huangle
* @date 2023/2/9 16:42
*/
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 Files.newInputStream(file.toPath());
}
public final String getPath() {
return path;
}
}
UrlResource 加载器
加载网络上的资源
public class UrlResource implements Resource {
private final URL url;
public UrlResource(URL url) {
Assert.assertNotNull("url must not be null", url);
this.url = url;
}
@Override
public InputStream getInputStream() throws IOException {
URLConnection connection = this.url.openConnection();
try {
return connection.getInputStream();
} catch (IOException e) {
// 关闭链接-资源
if (connection instanceof HttpURLConnection) {
((HttpURLConnection) connection).disconnect();
}
throw e;
}
}
}
使用时,统一使用到的是 ResourceLoader资源包装器,这个接口提供获取资源的方式。
注:这里 ResourceLoader 担任的其实就是一个 工厂的职责。这是还抽了一层给下层去完成了。
/**
* 资源包装器
*/
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = "classpath:";
Resource getResource(String location);
}
默认的实现类 完成资源的 获取 DefaultResourceLoader。(工厂的具体处理逻辑)
/**
* 封装获取资源加载器,实现细节不暴露给外部,只暴露接口
* @author huangle
* @date 2023/2/10 10:50
*/
public class DefaultResourceLoader implements ResourceLoader {
@Override
public Resource getResource(String location) {
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 定义加载注册资源的方式。
/**
* bean 定义读取接口
* 这里需要注意 getRegistry()、getResourceLoader(),
* 都是用于提供给后面三个方法的工具,加载和注册,这两个方法的实现会包装到抽象类中
*/
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;
}
BeanDefinitionReader 接口其实相当于 定义了所有的处理逻辑(接口定义行为、规范),如果对 接口和抽象类不是很理解。可以主要 理解一下 他们两者主要的用途
抽象类通常用于定义一些通用的方法,而接口通常用于定义一些规范和约定,以便多个类实现它们。
AbstractBeanDefinitionReader抽象类统一实现公共的方法,后面的只需要继承他,就可以获得一些公共参数的使用。这里使用抽象类的思想,是因为抽象类可以选择 实现接口的方法,不用强制去实现全部方法。
/**
* @author huangle
* @date 2023/2/10 10:59
*/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{
private final BeanDefinitionRegistry beanDefinitionRegistry;
private ResourceLoader resourceLoader;
public AbstractBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
this(beanDefinitionRegistry,new DefaultResourceLoader());
}
public AbstractBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry, ResourceLoader resourceLoader) {
this.beanDefinitionRegistry = beanDefinitionRegistry;
this.resourceLoader = resourceLoader;
}
@Override
public BeanDefinitionRegistry getRegistry() {
return beanDefinitionRegistry;
}
@Override
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
XmlBeanDefinitionReader具体加载实现,通过解析 xml 文件,来完成bean的注入
/**
* @author huangle
* @date 2023/2/10 11:02
*/
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
super(beanDefinitionRegistry);
}
public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry, ResourceLoader resourceLoader) {
super(beanDefinitionRegistry, resourceLoader);
}
@Override
public void loadBeanDefinitions(Resource resource) throws BeansException {
try {
doLoadBeanDefinitions(resource.getInputStream());
}catch (Exception 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 Exception {
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;
}
// 解析bean标签
Element bean = (Element) childNodes.item(i);
String id = bean.getAttribute("id");
String name = bean.getAttribute("name");
String calssName = bean.getAttribute("class");
Class<?> clazz = Class.forName(calssName);
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;
}
// 设置属性
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");
}
// 注册bean
getRegistry().registerBeanDefinition(beanName,beanDefinition);
}
}
}
测试
这个时候,我们只需要定义 DefaultListableBeanFactory, XmlBeanDefinitionReader 和配置 spring.xml 文件,就可以完成bean的注入。
@Test
public void testXml() throws BeansException {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader definitionReader = new XmlBeanDefinitionReader(beanFactory);
definitionReader.loadBeanDefinitions("classpath:spring.xml");
UserService userService = (UserService) beanFactory.getBean("userService");
System.out.println(userService.say());
}
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userDao" class="cn.anoxia.springframework.beans.factory.support.UserDao"/>
<bean id="userService" class="cn.anoxia.springframework.beans.factory.support.UserService">
<property name="name" value="Anoxia"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>