之前的章节都是通过代码注册bean,设置属性等操作,这一章是在Spring框架里是采用xml形式定义Bean对象,以及设置Bean的属性以及引用关系。
就是将以下的标红的框的代码移到xml文件去定义,然后通过加载xml文件,进行解析文件,再进行bean的注册和属性的填充。
按上面改造方式,我们需要加载xml的功能,所以就会出现资源加载器,那么我们根据文件的加载,我们要既能支持ClassPath,还有本地file,以及云文件(http)的加载。这是资源加载的操作。其次是解析xml的功能操作,解析完毕调用上几章的Bean注册以及属性填充操作即可。
1.工程项目
test单元测试:
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─spring
│ │ │ └─sourcecode
│ │ │ │ SourcecodeApplication.java
│ │ │ │
│ │ │ └─springframework
│ │ │ │ BeanDefinition.java
│ │ │ │ BeanFactory.java
│ │ │ │
│ │ │ ├─beans
│ │ │ │ │ BeansException.java
│ │ │ │ │ PropertyValue.java
│ │ │ │ │ PropertyValues.java
│ │ │ │ │
│ │ │ │ └─factory
│ │ │ │ │ BeanFactory.java
│ │ │ │ │
│ │ │ │ ├─config
│ │ │ │ │ BeanDefinition.java
│ │ │ │ │ BeanReference.java
│ │ │ │ │ SingletonBeanRegistry.java
│ │ │ │ │
│ │ │ │ ├─support
│ │ │ │ │ AbstractAutowireCapableBeanFactory.java
│ │ │ │ │ AbstractBeanDefinitionReader.java
│ │ │ │ │ AbstractBeanFactory.java
│ │ │ │ │ BeanDefinitionReader.java
│ │ │ │ │ BeanDefinitionRegistry.java
│ │ │ │ │ CglibSubclassingInstantiationStrategy.java
│ │ │ │ │ DefaultListableBeanFactory.java
│ │ │ │ │ DefaultSingletonBeanRegistry.java
│ │ │ │ │ InstantiationStrategy.java
│ │ │ │ │ SimpleInstantiationStrategy.java
│ │ │ │ │
│ │ │ │ └─xml
│ │ │ │ XmlBeanDefinitionReader.java
│ │ │ │
│ │ │ ├─core
│ │ │ │ └─io
│ │ │ │ ClassPathResource.java
│ │ │ │ DefaultResourceLoader.java
│ │ │ │ FileSystemResource.java
│ │ │ │ Resource.java
│ │ │ │ ResourceLoader.java
│ │ │ │ UrlResource.java
│ │ │ │
│ │ │ ├─testready
│ │ │ │ UserDao.java
│ │ │ │ UserService.java
│ │ │ │
│ │ │ └─util
│ │ │ ClassUtils.java
│ │ │
│ │ └─resources
│ │ application.properties
│ │ spring.xml
│ │
│ └─test
│ └─java
│ └─com
│ └─spring
│ └─sourcecode
│ │ SourcecodeApplicationTests.java
│ │
│ └─springframework
│ │ ApiTest.java
│ │ ApiTest03.java
│ │ ApiTest04.java
│ │ ApiTest05.java
│ │ ApiTest06.java
│ │
│ └─test
│ UserDao.java
│ UserService.java
2.UML类图
这一章节需要用到之前的章节,所以下面蓝色的类图是对应的需要更改或添加的内容。
这一章节主要讲黄色类图的内容,这部分内容就是加载资源xml,解析xml,最后注册到Bean里,咱们先来说一下资源加载进来然后转换文件流方式(留到最后进行文件解析)
2.1 资源加载器
Resource
Resource获取文件流的接口,需要在解析文件数据之前进行获取文件流的入口的目的。
ClassPathResource
Resource接口的实现,这个类为类路径文件流的处理,所以实现的都是处理classpath下的文件的处理,最后输出流。
FileSystemResource
Resource接口的实现,这个类是本地文件流的处理,所以实现的都是根据路径去获取一个file文件最后得到文件流。
UrlResource
Resource接口的实现,这个类是用来处理http请求方式得到的文件流,所以实现是传进来一个http地址的文件得到文件流的方式。
ResourceLoader
此接口是资源加载的接口,主要是将获取上面介绍的Resource
DefaultResourceLoader
是ResourceLoader实现接口,实现getResource()方法,其实是获取调用Resource的入口,通过参数来实例化不同的Resource的实现类,比如:传递的参数是类路径,那么就new ClassPathResource();
以上是加载文件的操作。
2.2 解析文件以及Bean注册
BeanDefinitionReader
此接口为加载资源解析bean的接口,所以定义了获取Bean注册方法,getRegistry(),也定义了获取Resource资源getResourceLoader(),用于在解析xml文件之前进行资源文件的获取。
定义了加载bean定义的方法,loadBeanDefinitions(),通过重载不同此方法用来根据不同的方式得到相同的Resource方式,最后进行根据资源进行解析。
AbstractBeanDefinitionReader
此抽象类继承BeanDefinitionReader,通过构造函数将BeanDefinitionRegistry和ResourceLoader获取到数据。
XmlBeanDefinitionReader
核心类,继承AbstractBeanDefinitionReader,实现其loadBeanDefinitions()方法,通过此方法获取Resource,并抽象出doLoadBeanDefinitions()方法,参数为Resource.getInputStream()获取文件流,根据文件流解析xml中的文件,获取Bean的Class类,Bean的属性,最后通过getRegistry()注册bean和属性的操作,
2.3 原有类修改点
BeanDefinitionRegistry
此类里添加了一个判断Bean是否已经存在在容器里的方法,containsBeanDefinition(String beanName);
DefaultListableBeanFactory
作为BeanDefinitionRegistry的实现类,那肯定是要实现查询map里是否存在此key为beanName的数据并返回true或false。
BeanFactory
此类里添加了获取bean返回实体方式的getBean方法();
AbstractBeanFactory
作为BeanFactory的实现类,将获取到的getBean转换为泛型实体。
3.代码实现
Resource:资源处理器,用来获取inputStream();
// Spring框架下core.io包主要用来处理资源加载流
// 资源加载接口
public interface Resource {
// 获取流的方法
InputStream getInputStream() throws IOException;
}
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) {
// spring的断言,如果空就不能往下走报错必须是不为空
Assert.notNull(path, "Path must not be null");
this.path = path;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
// 获取文件流返回
@Override
public InputStream getInputStream() throws IOException {
// 需要动态获取某个位置的文件可以获取文件的资源,可以采用classLoader.getResourceAsStream
InputStream is = classLoader.getResourceAsStream(path);
if (is == null) {
throw new FileNotFoundException(this.path + " cannot be opened because it does not exist");
}
return is;
}
}
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);
}
}
UrlResource:http文件流加载方式
// 通过http方式读取文件,我们可以把文件放入不同的服务器或者云上
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;
}
}
}
ResourceLoader:对于上面的资源的包装类,通过此类获取inputStream的入口
// 包装资源加载器
// 按照资源加载的不同方式,资源加载器可以把这些方式集中到统一的类服务下进行处理,外部用户只需要传递资源地址即可
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = "classpath:";
// 获取资源
Resource getResource(String location);
}
DefaultResourceLoader:实现ResourceLoader的方法,根据不同的参数获取不同的Resource
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 {
URL url = null;
try {
url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException e) {
return new FileSystemResource(location);
}
}
}
}
以上是对资源加载的类和接口全部内容,下面是获取资源和解析xml的数据
BeanDefinitionReader:
// bean定义读取接口
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
ResourceLoader getResourceLoader();
// getRegistry()与getResourceLoader()都是用于提供给这三个方法的工具,加载和注册
void loadBeanDefinitions(Resource resource);
void loadBeanDefinitions(Resource... resources);
void loadBeanDefinitions(String location);
}
AbstractBeanDefinitionReader:实现BeanDefinitionReader的获取BeanDefinitionRegistry和ResourceLoader
// bean定义读取的准备工作,定义成abstract就可以任意实现需要的类,不想实现的就可不实现
// 此类主要是获取BeanDefinitionRegistry和ResourceLoader
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;
}
}
XmlBeanDefinitionReader:继承AbstractBeanDefinitionReader类,最主要的方法为doLoadBeanDefinitions(),这个是通过获取Resource以后再次获取inputStream(),然后通过流得到Bean的id,name,以及属性等等信息的解析,解析完毕即可注册到beanDeffinition里,最后注册到容器里。
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
protected XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
super(registry, resourceLoader);
}
@Override
public void loadBeanDefinitions(Resource resource) {
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) {
for (Resource resource : resources) {
loadBeanDefinitions(resource);
}
}
@Override
public void loadBeanDefinitions(String location) {
ResourceLoader resourceLoader = getResourceLoader();
Resource resource = resourceLoader.getResource(location);
loadBeanDefinitions(resource);
}
// 核心方法-从xml中取出bean和属性信息进行bean的注册
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.forName装入类并对类做初始化
Class<?> clazz = Class.forName(classname);
// 优先级 id > name
String beanName = StrUtil.isNotEmpty(id) ? id : name;
if (StrUtil.isEmpty(beanName)) {
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
}
// 将解析出来的Bean放入BeanDefination的beanClass
BeanDefination beanDefination = new BeanDefination(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);
// 添加到属性集合里去
beanDefination.getPropertyValues().addPropertyValue(propertyValue);
}
if (getRegistry().containsBeanDefinition(beanName)) {
throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
}
// 注册beanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefination);
}
}
}
对于上节需要修改的内容
beanFactory:bean工厂添加getBean()返回值为实体方法
public interface BeanFactory {
Object getBean(String name) throws BeansException;
// 04章节-添加,可以传参数
Object getBean(String name,Object... args);
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
}
AbstractBeanFactory:在此类里实现以下一个方法即可,其他不用动。
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return (T) getBean(name);
}
BeanDefinitionRegistry:这个接口需要添加是否在容器中包含Bean对象
public interface BeanDefinitionRegistry {
/**
* 向注册表中注册 BeanDefinition
*
* @param beanName
* @param beanDefinition
*/
void registerBeanDefinition(String beanName, BeanDefination beanDefinition);
/**
* 判断是否包含指定名称的BeanDefinition
* @param beanName
* @return
*/
boolean containsBeanDefinition(String beanName);
}
DefaultListableBeanFactory:此类添加此方法即可,其他不用动
@Override
public boolean containsBeanDefinition(String beanName) {
return beanDefinationMap.containsKey(beanName);
}
4.测试
测试前准备
UserDao类
public class UserDao {
private static Map<String, String> hashMap = new HashMap<>();
static {
hashMap.put("10001", "hahaha");
hashMap.put("10002", "ss");
hashMap.put("10003", "sio");
}
public String queryUserName(String uId) {
return hashMap.get(uId);
}
}
UserService类
public class UserService {
private String uId;
private UserDao userDao;
public void queryUserInfo() {
System.out.println("查询用户信息:" + userDao.queryUserName(uId));
}
public String getuId() {
return uId;
}
public void setuId(String uId) {
this.uId = uId;
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
单元测试
public class ApiTest06 {
//
@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);
userService.queryUserInfo();
}
}
测试