工厂设计模式+反射+XML配置文件实战演练
通过Java的多态性,我们能知道,当父类引用可以指向子类对象从而调用父类中子类的重写方法,以及接口引用可以指向实现类对象从而调用实现类对象的重写方法。但在项目中,我发现代码直接使用dao层接口引用就能使用实现类的方法,为了了解其中的过程,我查阅了相关资料,了解到这个是Spring的IoC容器的作用。
Spring的IoC即Inversion of Control,意思就是控制反转控制反转指的就是将对象的创建权反转给(交给)了Spring,其作用是实现了程序的解耦合。也可这样解释:获取对象的方式变了,对象创建的控制权不是"使用者",而是"框架"或者"容器"。用更通俗的话来说,IoC就是指对象的创建,并不是在代码中用new操作new出来的,而是通过Spring进行配置创建的。
Spring的IoC的底层实现原理是工厂设计模式+反射+XML配置文件。即先写一个接口,再写该接口的一个实现类,通过解析XML配置文件获取该实现类的配置属性,在工厂类中使用反射机制得到实现类相应的对象。
以下为IoC底层实现原理的代码实现:
先写一个接口
public interface BaseInterface {
void say(String words);
void bark();
void fly();
}
再写一个接口的实现类
public class BaseImpl implements BaseInterface {
@Override
public void say(String words) {
System.out.println(words);
}
@Override
public void bark() {
System.out.println("barking");
}
@Override
public void fly() {
System.out.println("I'm flying");
}
}
若在一般的多态实现上,我们可以通过接口引用调用实现类对象来使用实现类的方法
public class InterfaceTest {
public static void main(String[] args) {
BaseInterface baseInterface = new BaseImpl();
baseInterface.bark();
baseInterface.say("HelloWorld");
baseInterface.fly();
}
}
但为了松耦合,我们采用工厂设计模式
public class InterfaceTest {
public static void main(String[] args) {
BaseInterface baseInterface = (BaseImpl) BeanFactory.getConfig("./src/com/jmcico/interfacedemo/FactoryConfig.xml");
baseInterface.bark();
baseInterface.say("HelloWorld");
baseInterface.fly();
}
}
所谓工厂设计模式,简单来说就是使用工厂类来生成对象
一般情况下的工厂类的实现:
public class BeanFactory {
public static Object getConfig(final String path) {
BaseImpl base = new BaseImpl();
return base;
}
}
但这样就产生了一个新问题:工厂类与实现类的耦合度太高。为了松耦合,工厂类采用XML配置文件来获取包名类名,通过反射机制来生成对象。
public class BeanFactory {
public static Object getConfig(final String path) {
try {
// 1.使用SAX解析得到配置文件内容
List<ConfigInstance> configs = XmlParseUtils.getConfig(path);
String id = configs.get(0).getId();
String url = configs.get(0).getPath();
// 2.使用反射机制获得对象
BaseImpl base = (BaseImpl)Class.forName(url).newInstance();
return base;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
关于XML文件的解析,我使用的是SAX解析方式,常用的还有DOM解析方式。相比DOM,SAX是采用边读边解析的方式,这样可以不用一次性将整个文件读取到内存中,能提高性能;但也有缺陷,这个方式只能应用在读取XML文件上,不能对XML文件进行修改。
SAX解析的过程大体分为4步:
- 得到xml文件对应的资源,可以是xml的输入流,文件和url
- 得到SAX解析工厂(SAXParserFactory)
- 由SAX解析工厂生成SAX解析器
- 传入输入流和handler给解析器,调用parse()解析
public class XmlParseUtils {
public static List<ConfigInstance> getConfig(String path)
throws ParserConfigurationException, SAXException, IOException {
XmlParseHandler handler = new XmlParseHandler();
// 1.创建SAX解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
// 2.得到解析器
SAXParser parser = factory.newSAXParser();
// 3.得到解读器
XMLReader reader = parser.getXMLReader();
// 4.设置内容处理器
reader.setContentHandler(handler);
// 5.读取xml的文档内容
reader.parse(path);
return handler.getConfigList();
}
}
所要解析的FactoryConfig.xml文件内容:
<bean id="BaseImpl" class="com.jmcico.interfacedemo.BaseImpl" />
对应的,为了方便读取相应的数据,我写了一个配置信息ConfigInstance类来存储读取的XML文件内容
public class ConfigInstance {
private String id;
private String path;
public void setId(String id) {
this.id = id;
}
public void setPath(String path) {
this.path = path;
}
public String getId() {
return id;
}
public String getPath() {
return path;
}
}
要解析XML文件,就需要写一个解析XML文件的XmlParseHandler类,这个类可以继承DefaultHandler或者实现ContentHandler接口来实现。这个类是解析XML文件的核心所在。本实战采用的是继承DefaultHandler类,先把代码贴出来:
public class XmlParseHandler extends DefaultHandler {
private List<ConfigInstance> instances;
private String currentTag; // 记录当前解析的节点名称
private ConfigInstance instance; // 记录当前的base实现对象
private final static String BASE_TAG = "bean";
/**
* @Author jmcico
* @Date 2019/9/27 21:00
* @Description //TODO:文件解释开始时调用
* @Param
* @return
**/
@Override
public void startDocument () throws SAXException {
super.startDocument();
System.out.println("startDocument");
instances = new ArrayList<ConfigInstance>();
}
/**
* @Author jmcico
* @Date 2019/9/27
* @Description //TODO:文档解析结束后调用
* @Param
* @return
**/
@Override
public void endDocument () throws SAXException {
super.endDocument();
System.out.println("endDocument");
}
/**
* @Author jmcico
* @Date 2019/9/27 21:04
* @Description //TODO:节点解析开始时调用
* @Param uri: xml文档的命名空间
* localName: 标签的名字
* qName: 带命名空间的标签的名字 <bean id="BaseImpl"/> 中的bean就是qName
* attributes: 标签的属性集
* @Example: <bean id="BaseInterface" class="com.jmcico.interfacedemo.BaseImpl"/>
* 此处bean为localName,id和class均为attributes中的元素
* @return
**/
@Override
public void startElement (String uri, String localName, String qName, Attributes attributes)
throws SAXException {
super.startElement(uri, localName, qName, attributes);
System.out.println("startElement " + qName);
if (BASE_TAG.equals(qName)) {
instance = new ConfigInstance();
for (int i = 0; i < attributes.getLength(); i++) {
System.out.println("attribute_name: " + attributes.getLocalName(i) +
"\tattribute_value: " + attributes.getValue(i));
if ("id".equals(attributes.getLocalName(i))) {
instance.setId(attributes.getValue(i));
}
else if ("class".equals(attributes.getLocalName(i))) {
instance.setPath(attributes.getValue(i));
}
}
}
currentTag = qName;
}
/**
* @Author jmcico
* @Date 2019/9/27 21:16
* @Description //TODO:节点解析结束后调用
* @Param uri, localName, qName
* @return
**/
@Override
public void endElement (String uri, String localName, String qName)
throws SAXException {
super.endElement(uri, localName, qName);
System.out.println("endElement " + localName);
if (BASE_TAG.equals(qName)) {
instances.add(instance);
instance = null;
}
currentTag = null;
}
/**
* @Author jmcico
* @Date 2019/9/27 21:19
* @Description //TODO:解析标签的内容时调用
* @Param ch[]: 当前读取到的TextNode(文本节点)的字节数组
* start: 字节开始的位置,为0则读取全部
* length: 当前TextNode的长度
* @Example: <name>Jason</name> 该方法是在解析name标签下的Jason时使用
* @return
**/
@Override
public void characters (char ch[], int start, int length)
throws SAXException {
super.characters(ch, start, length);
System.out.println("characters");
}
public List<ConfigInstance> getConfigList() {
return instances;
}
}
这里主要重写了5个方法,实际上用到的只有4个方法,可根据实际需求去重写DefaultHandler的方法。基本上常用的是代码中提到的5个方法。
虽然是在一定程度上减少了使用new来生成对象,但是还并不能完全实现直接用引用来调用实现类的方法,可能还有没考虑到的东西,现在还在学习相关内容。若思路上有问题,还望大家指正,谢谢。