JavaEE中的MVC(二)Xml配置实现IOC控制反转

注:这是一个“重复造轮子”的过程,本篇介绍如何通过XML配置文件实现IOC。

需求:

以往在我们写服务器的时候,业务逻辑层(Biz,有些地方叫Service)通常会有个获取Dao的需求,通常情况是从DaoFactory中调用Get方法,获取所需的Dao。而现在我想改变一下想法,按照IOC这种思路,主动地给Biz注入对应的Dao。

这里写图片描述

Xml文件读取

Xml配置

Hibernate的配置文件似乎太多了,先写一个更加简单的Xml文件,取名为Student.xml,这个Student.xml没什么意义,先测试一下,将一个Xml文件解析一下,在控制台原原本本地输出,主要就通过Document对象的方法进行解析 。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <Student id="001">
        <name>Tom</name>
        <age>31</age>
    </Student>
    <Student id="002">
        <name>Jom</name>
        <age>15</age>
    </Student>
</beans>

解析Xml文件

Java代码,运行结果就不贴出来了,就是完完整整地把上面的Xml配置内容在控制台输出来,大家可以注意一下我们可能用到的方法。

public class XmlLoad {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        InputStream inputStream = XmlLoad.class.getResourceAsStream("/Student.xml");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(inputStream);

        Element root = document.getDocumentElement();
        printNode(root);
    }

    private static void printNode(Node node) {
        short nodeType = node.getNodeType();
        switch (nodeType) {
        case Node.TEXT_NODE:
            System.out.print(node.getNodeValue());
            break;

        case Node.ELEMENT_NODE:
            Element element = (Element) node;
            String tagName = element.getTagName();
            System.out.print("<");
            System.out.print(tagName);

            NamedNodeMap attrs = element.getAttributes();
            for (int i = 0; i < attrs.getLength(); i++) {
                Attr attr = (Attr) attrs.item(i);
                printNode(attr);
            }
            System.out.print(">");

            NodeList childNodeList = element.getChildNodes();
            for (int i = 0; i < childNodeList.getLength(); i++) {
                Node childNode = childNodeList.item(i);
                printNode(childNode);
            }

            System.out.print("</" + tagName + ">");
            break;

        case Node.ATTRIBUTE_NODE:
            Attr attr = (Attr) node;
            String name = attr.getName();
            String value = attr.getValue();
            System.out.print(" " + name + "=" + value);
            break;

        default:
            break;
        }
    }
}

Xml配置文件转JavaBean(Set方式注入)

Xml配置

注:这里使用的是Set方法注入,其实也可以通过构造函数注入,直接通过字段强行注入也可以实现。

下面开始真正的实战,首先,写一个Xml文件,取名为beans.xml:

  • beans是最外层的标签;
  • 其中bean标签,class属性值“com.using.bean.Student“,指的是Student的全类名;
  • 然后property标签指明了各个属性值,比如:Student类的Id属性的值是12,这些都是和Javabean一一对应的。
<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean class="com.using.bean.Student">
        <property name="id" value="12a"></property>
        <property name="name" value="2a"></property>
        <property name="age" value="31a"></property>
    </bean>
    <bean class="com.using.bean.Student">
        <property name="id" value="12b"></property>
        <property name="name" value="2b"></property>
        <property name="age" value="31b"></property>
    </bean>
</beans>

然后是对应的Javabean,尤其是Set方法,一定要补齐,因为下面的Demo就是通过Set方法注入值的。

public class Student {
    private String id;
    private String name;
    private String age;
    //方法补齐
}`

解析思路:

其实思路就是解析Xml布局文件,然后读取属性值,最后通过Java反射机制,将配置的信息反射成一个对象,至于说效率问题,那肯定会比较低,想想就知道,代码都多写了好多行,还要加文件流的读取,能不慢么?不过无反射,不框架。

其中全类名”com.using.bean.Student“,干什么用?相信你们肯定用过,在写数据库连接的时候这样写:Class.forName(“com.mysql.jdbc.Driver”) ,它的作用是装载数据库驱动,而我们要通过它实现这样一个功能,通过字符串来创建实例,具体的使用方法如下:
Object obj=Class.forName(fullClassName).newInstance();

Set方式注入的关键代码如下,先找到Set方法的代码,然后通过invoke()方法注入:

    if (("set" + name.substring(0, 1).toUpperCase() + name.substring(1)).equals(methodName)) {
        try {
            //参数注入
            method.invoke(instance, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

解析Xml文件

public class ClassFormat {
    public static void main(String[] args) {
        parseBeanFile("/beans.xml");
    }

    public static void parseBeanFile(String beanPath) {
        try {
            InputStream inputStream = ClassFormat.class.getResourceAsStream(beanPath);
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(inputStream);

            NodeList stuNodeList = document.getElementsByTagName("bean");
            for (int i = 0; i < stuNodeList.getLength(); i++) {
                Element stuElement = (Element) stuNodeList.item(i);

                String className = stuElement.getAttribute("class");
                Object instance = createInstance(className);
                parseBeanNodes(instance, stuElement);

                System.out.println(instance.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据全类名反射出对象
     */
    public static Object createInstance(String fullClassName) {
        try {
            return Class.forName(fullClassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解析子节点
     */
    public static void parseBeanNodes(Object instance, Element stuElement) {
        NodeList propertyNodeList = stuElement.getChildNodes();
        for (int j = 0; j < propertyNodeList.getLength(); j++) {
            Node propertyNode = propertyNodeList.item(j);
            if (propertyNode.getNodeType() == Node.ELEMENT_NODE && "property".equals(propertyNode.getNodeName())) {
                Element propertyElement = (Element) propertyNode;

                String name = propertyElement.getAttribute("name");
                String value = propertyElement.getAttribute("value");
                //可以补充type属性,然后反射成Javabean的字段类型,逻辑会多一些

                setProperty(instance, name, value);
            }
        }
    }

    /**
     * Set方法注入
     */
    public static void setProperty(Object instance, String name, String value) {
        Class<?> clazz = instance.getClass();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (("set" + name.substring(0, 1).toUpperCase() + name.substring(1)).equals(methodName)) {
                try {
                    method.invoke(instance, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

控制台输出:
Student [id=12a, name=2a, age=31a]
Student [id=12b, name=2b, age=31b]

最终封装,往Biz中注入Dao

思路分析

  1. 通过DaoFactory这个类实现了Dao和Biz的初始化,这和上面实例化Student是一样的,初始化完成就放到beanMap中;
  2. 然后通过解析Xml文件,了解到Biz要引用哪个Dao;
  3. 配置文件中的name属性指名是哪个Set方法,ref属性指明是哪一个Dao;
  4. 最后通过反射机制,从Set方法注入Dao。

使用场景

这里只给出思路,具体使用,在之后的文章会用到。

在我们写后台的时候,比如说你有一张User表,你可能就会去写UserDao,而这个UserDao只需要一个实例就够了,没必要创建特别多个,你可能会把它写成单例,而且是很希望程序启动的时候就创建好。

这样就可以设计一个优先级别高的Servlet,然后在这个Servlet中去调用我们的DaoFactory,DaoFactory就开始解析Xml配置文件,实例化Dao和Biz,然后往Biz中注入Dao。这样Tomcat启动的时候,我们就有了Dao和Biz对象,而且后面就不要再重复创建这些对象了。

当然,Servlet调用Biz这个耦合我们还没解决,所以Servlet调用Biz的时候你还是得通过DaoFactory来获取Biz。

Dao类:

public class Dao {
    private String name="this is dao";

    public Dao() {
    }

    @Override
    public String toString() {
        return "Dao [ name :"+name + "]";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Biz类:

public class Biz {
    private Dao dao;

    public Biz() {
    }

    @Override
    public String toString() {
        return "Biz [dao=" + dao.toString() + "]";
    }

    public Dao getDao() {
        return dao;
    }

    public void setDao(Dao dao) {
        this.dao = dao;
    }
}

设计Xml配置文件


<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="Dao" class="com.using.bean.Dao"/>
    <bean id="Biz" class="com.using.bean.Biz">
        <property name="Dao" ref="Dao" />
    </bean>
</beans>

DaoFactory类设计

真正实现IOC注入的Java类,虽然取名叫DaoFactory,但是它也实现了Biz的初始化,命名有些不规范o(∩_∩)o

public class DaoFatory {
    private static Map<String, Object> beanMap = new HashMap<>();

    public static void main(String[] args) {
        System.out.println(beanMap.toString());
    }

    static {
        parseBeanFile("/beans.xml");
    }

    /**
     * 根据路径解析
     */
    public static void parseBeanFile(String beanPath) {
        try {
            InputStream inputStream = DaoFatory.class.getResourceAsStream(beanPath);
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(inputStream);

            //第一次遍历,初始化所有的对象,包括Dao和Biz
            NodeList beanNodeList = document.getElementsByTagName("bean");
            for (int i = 0; i < beanNodeList.getLength(); i++) {
                Node bean = beanNodeList.item(i);
                parseBeanNodes(bean);
            }

            //第一次遍历,往初始化好的Biz注入Dao
            for (int i = 0; i < beanNodeList.getLength(); i++) {
                Node bean = beanNodeList.item(i);
                parsePropertyNodes(bean);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析property
     */
    public static void parsePropertyNodes(Node property) {
        if(property!=null&&property.getNodeType()==Node.ELEMENT_NODE){
            Element element=(Element) property;
            String id = element.getAttribute("id");
            Object bean=beanMap.get(id);

            NodeList childNodeList=property.getChildNodes(); 
            for (int i = 0; i < childNodeList.getLength(); i++) {
                Node childNode=childNodeList.item(i);
                if(childNode!=null&&childNode.getNodeType()==Node.ELEMENT_NODE
                        &&"property".equals(childNode.getNodeName())){

                    Element childElement=(Element) childNode;
                    String childName = childElement.getAttribute("name");
                    String childRef = childElement.getAttribute("ref");

                    Object refBean=beanMap.get(childRef);

                    setProperty(bean, refBean, childName);

                }
            }
        }
    }

    /**
     * Set参数注入
     */
    public static void setProperty(Object bean,Object refBean,String childName) {
        Class<?> clazz = bean.getClass();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (("set" + childName.substring(0, 1).toUpperCase() + childName.substring(1)).equals(methodName)) {
                try {
                    method.invoke(bean, refBean);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 解析Node
     */
    public static void parseBeanNodes(Node node) {
        Element element = (Element) node;
        String idName = element.getAttribute("id");
        String className = element.getAttribute("class");
        beanMap.put(idName, createInstance(className));
    }

    /**
     * 根据全类名反射成对象
     */
    public static Object createInstance(String fullClassName) {
        try {
            return Class.forName(fullClassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

控制台输出:

{Biz=Biz [dao=Dao [ name :this is dao]], Dao=Dao [ name :this is dao]}

文章写到这,就已经打到了我想要的效果了,不过单独使用IOC思想,确实看着好奇怪,直接new一个Dao,再往Biz里面Set不是很简单嘛?现在看也确实如此,不过也不着急,之后的文章会不停地使用这里的代码

posted on   疯狂的妞妞  阅读(200)  评论(0编辑  收藏  举报

(评论功能已被禁用)
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示