一、簡化Bean的注冊
如果每次注冊一個Bean,都要像上節一樣,手動寫PropertyValues相關的代碼,那太復雜了,我們希望讀取XML文件,自動注冊Bean,這樣對于使用者,甚至不知道有BeanDefinition的存在
二、統一處理資源文件
新建資源接口,Spring對所有的資源文件,統一處理
- 一個資源,最重要的就是拿到輸入流,拿到輸入流就可以讀取文件
public interface Resource {InputStream getInputStream() throws IOException;
}
提供三個資源實現類,分別讀取不同類型的文件,這就是策略模式
類路徑下的文件(最常用)
public class ClassPathResource implements Resource{private final String path;private final ClassLoader classLoader;public ClassPathResource(String path) {this(path, null);}public ClassPathResource(String path, ClassLoader classLoader) {Assert.notNull(path, "Path must not be null");this.path = path;this.classLoader = classLoader != null ? classLoader : ClassUtil.getClassLoader();}@Overridepublic InputStream getInputStream() throws IOException {InputStream is = classLoader.getResourceAsStream(path);if (is == null) {throw new FileNotFoundException(path + " cannot be opened because it does not exist");}return is;}
}
文件系統下的文件
public class FileSystemResource implements Resource{private File file;public FileSystemResource(File file) {this.file = file;}@Overridepublic InputStream getInputStream() throws IOException {return Files.newInputStream(file.toPath());}
}
網絡文件
public class UrlResource implements Resource{private final URL url;public UrlResource(URL url) {Assert.notNull(url,"URL must not be null");this.url = url;}@Overridepublic InputStream getInputStream() throws IOException {URLConnection con = url.openConnection();return con.getInputStream();}
}
資源加載器接口,簡化資源類的使用,自動根據路徑選擇合適的加載類
- 這又屬于工廠方法設計模式
/*** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/1/2 22:16* @Version 1.0*/
public interface ResourceLoader {String CLASSPATH_URL_PREFIX = "classpath:";Resource getResource(String location);
}
資源加載器接口實現
- 根據路徑前綴,默認就是使用classpath策略
/*** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/1/2 22:18* @Version 1.0*/
public class DefaultResourceLoader implements ResourceLoader{@Overridepublic 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(new File(location));}}}
}
三、從文件中讀取Bean
定義BeanDefinitionReader接口,從文件中讀取BeanDefinition,并且注冊到Bean工廠,這里有三要素
- 資源文件
- Bean工廠
- 讀取BeanDefinition的邏輯(單個資源,多個資源,位置字符串)
/*** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/1/2 22:26* @Version 1.0*/
public interface BeanDefinitionReader {BeanDefinitionRegistry getRegistry();ResourceLoader getResourceLoader();void loadBeanDefinitions(Resource resource);void loadBeanDefinitions(Resource... resources);void loadBeanDefinitions(String location);
}
用抽象類AbstractBeanDefinitionReader實現接口,模板方法設計模式
- Bean工廠和資源加載器都是確定的,抽象類直接實現
- 只有加載BeanDefinition是不確定的邏輯,交給具體的策略子類實現
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {private final BeanDefinitionRegistry registry;private final ResourceLoader resourceLoader;public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {this.registry = registry;this.resourceLoader = resourceLoader;}public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {this(registry, new DefaultResourceLoader());}@Overridepublic BeanDefinitionRegistry getRegistry() {return registry;}@Overridepublic ResourceLoader getResourceLoader() {return resourceLoader;}}
XmlBeanDefinitionReader做具體實現,策略模式
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {super(registry, resourceLoader);}public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {super(registry);}@Overridepublic void loadBeanDefinitions(Resource resource) {try {InputStream is = resource.getInputStream();doLoadBeanDefinitions(is);} catch (IOException e) {throw new RuntimeException("IOException parsing XML document from " + resource, e);}}@Overridepublic void loadBeanDefinitions(Resource... resources) {for (Resource resource : resources) {loadBeanDefinitions(resource);}}@Overridepublic void loadBeanDefinitions(String location) {ResourceLoader resourceLoader = getResourceLoader();Resource resource = resourceLoader.getResource(location);loadBeanDefinitions(resource);}/*** 真正解析XMl文件的方法** @param inputStream*/private void doLoadBeanDefinitions(InputStream inputStream) {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 = null;try {clazz = Class.forName(className);} catch (ClassNotFoundException e) {throw new RuntimeException("不存在的類名" + className);}// 優先級 id > name,此處是Bean自己的id和nameString beanName = StrUtil.isNotEmpty(id) ? id : name;if (StrUtil.isEmpty(beanName)) {beanName = StrUtil.lowerFirst(clazz.getSimpleName());}// 定義BeanBeanDefinition 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;// 解析標簽:propertyElement 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 RuntimeException("Duplicate beanName[" + beanName + "] is not allowed");}// 注冊 BeanDefinitiongetRegistry().registerBeanDefinition(beanName, beanDefinition);}}
}
BeanDefinitionReader接口、資源接口,層次結構圖
四、測試
新建Person類
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Person {private String name;private int age;private Cat cat;
}
新建Cat類
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Cat {private String name;private int weight;
}
編寫一個spring.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="cat" class="cn.shopifymall.springframework.test.bean.Cat"><property name="name" value="tomcat"/><property name="weight" value="2000"/></bean><bean id="person" class="cn.shopifymall.springframework.test.bean.Person"><property name="name" value="10001"/><property name="age" value="18"/><property name="cat" ref="cat"/></bean></beans>
新建測試類
public class ApiTest {@Testpublic void testGetBeanFromXml() {// 1.初始化 BeanFactoryDefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 2. 讀取配置文件&注冊BeanXmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);reader.loadBeanDefinitions("classpath:spring.xml");// 3. 獲取Bean對象調用方法Person person = (Person) beanFactory.getBean("person");System.out.println("person:" + person);}
}
控制臺輸出
person:Person(name=10001, age=18, cat=Cat(name=tomcat, weight=2000))
五、總結
- 通過引入spring.xml配置文件,我們就可以簡化Bean的注冊
- 用戶只需要編寫一個xml文件,由XmlBeanDefinitionReader自動解析xml文件,生成BeanDefinition并注冊到BeanFactory