欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

工厂设计模式+反射+XML配置文件实战演练

程序员文章站 2024-01-20 16:52:04
...

通过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步:

  1. 得到xml文件对应的资源,可以是xml的输入流,文件和url
  2. 得到SAX解析工厂(SAXParserFactory) 
  3. 由SAX解析工厂生成SAX解析器
  4. 传入输入流和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来生成对象,但是还并不能完全实现直接用引用来调用实现类的方法,可能还有没考虑到的东西,现在还在学习相关内容。若思路上有问题,还望大家指正,谢谢。