大道至简,衍化至繁。然而复杂的事情简单化是智慧的力量。
涉及知识点
-
xml解析
-
反射
先列出两个java bean的定义:
package xmlbean;
/** XML初始化类 */
public class Car {
private String info = "made in China";
public void showInfo() {
System.out.println(this + ":" + info);
}
}
package xmlbean;
/** XML初始化类 */
public class Train {
private String info;
public Train(String initInfo) {
this.info = initInfo;
}
public void showInfo() {
System.out.println(this + ":" + info);
}
}
上面两个bean用来测试xml配置方式初始化,其中都有一个showinfo()方法,Car类有默认无参构造函数,Train类只有一个带参构造函数。
接下来在看一下我们xml配置文件,定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这里模拟spring 定义bean相关的配置文件 -->
<beans>
<bean id="car" class="xmlbean.Car" />
<bean id="train" class="xmlbean.Train">
<constructor-arg arg="开往未来的火车" type="java.lang.String" />
</bean>
</beans>
其中constructor-arg 节点用来定义我们构造函数的入参值,arg表示值,type表示值的类型,当然,所有的xml属性节点你都可以自己定义,但是在解析的时候需要做对应的约定。xml位置也截一个图:
然后就是我们一个极其简单的BeanFactory,代码如下:
package xmlbean;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* 超级简单的XMLBean工厂,目前使用的java自带的XML解析器,因为后面我们会拥抱注解抛弃xml
*
* @author 大灰狼
*/
public class SimpleBeanFactory {
// 类加载器
final static ClassLoader loader = SimpleBeanFactory.class.getClassLoader();
// bean映射缓存
Map<String, Object> beanCache = new HashMap<String, Object>();
// 初始化beanFactory
public static synchronized SimpleBeanFactory loadConf(String xmlFile) throws Exception {
SimpleBeanFactory factory = new SimpleBeanFactory();
InputStream xmlIn = SimpleBeanFactory.class.getResourceAsStream(xmlFile);
// 使用JDK自带的XML解析器
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.parse(xmlIn);
NodeList nodes = document.getElementsByTagName("bean");
// 遍历Bean节点并根据配置信息初始化bean
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
String id = attrs.getNamedItem("id").getNodeValue();
String classPath = attrs.getNamedItem("class").getNodeValue();
List<String[]> initPrarms = new ArrayList<String[]>();// 构造参数
// 校验是否配置有构造函数参数,自带的XML解析很繁琐
NodeList childNodes = node.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
Node cnode = childNodes.item(j);
if (cnode.getNodeType() != Node.ELEMENT_NODE) {// 自带JDK按照XML严格解析导致有换行或者空格也算一个TXT节点
continue;
}
String arg = cnode.getAttributes().getNamedItem("arg").getNodeValue();
String argType = cnode.getAttributes().getNamedItem("type").getNodeValue();
// 添加构造器参数及参数类型
initPrarms.add(new String[] { arg, argType });
}
// 根据class全路径加载类
Class<?> cls = loader.loadClass(classPath);
// 判断是否有构造函数参数,并根据构造函参数初始化类
if (initPrarms.isEmpty()) {
Object inst = cls.getConstructor().newInstance();
factory.beanCache.put(id, inst);
} else {
// 获取参数类型顺序
List<Class<?>> argTypes = new ArrayList<Class<?>>();
List<String> arg = new ArrayList<String>();
initPrarms.forEach(argInfo -> {
try {
arg.add(argInfo[0]);
argTypes.add(loader.loadClass(argInfo[1]));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
});
Object inst = cls.getConstructor(argTypes.toArray(new Class<?>[0])).newInstance(arg.toArray());
factory.beanCache.put(id, inst);
}
}
return factory;
}
// 根据名称获取Bean实例
public Object getBean(String beanName) {
return beanCache.get(beanName);
}
}
BeanFactory定义了两个方法,一个是static的loadConf(),该方法用来加载xml文件并初始化bean缓存,另外一个是getBean()方法,该方法用户指定名称的Bean实例。
接下来我们测试一下这个BeanFactory功能,代码如下:
package xmlbean;
/**
* 测试XML方式初始化Bean
*
* @author 大灰狼
*/
public class TestApp {
public static void main(String[] args) throws Exception {
SimpleBeanFactory factory = SimpleBeanFactory.loadConf("bean-conf.xml");
Car car = (Car) factory.getBean("car");
Train train = (Train) factory.getBean("train");
Train train1 = (Train) factory.getBean("train");
car.showInfo();
train.showInfo();
train1.showInfo();
}
}
运行结果如下 :
xmlbean.Car@5b6f7412:made in China
xmlbean.Train@27973e9b:开往未来的火车
xmlbean.Train@27973e9b:开往未来的火车
通过运行结果我们可以分析到两次调用factory.getBean("train")返回的对象是同一,因为打印出来的对象信息(xmlbean.Train@27973e9b)一致。所以我们这个BeanFactory是一个单例模式的工厂。在设计模式中还有一个叫原型模式。其实要实现起来很简单,有如下两种方法:
-
克隆
-
记录Bean配置类型,然后在调用getBean时候读取对应配置,如果是原型模式就反射生成一个对象。
以上内容就是本次xml配置初始化对象,下一次我们将摒弃xml,直接以注解的方式来实现BeanFactory