QT IOC容器-使用教程

原创
2020/07/29 11:39
阅读数 879

前言

    其实这个库的大致使用方法在githubgitee上的README(已经开始陆陆续续搬到wiki中)里大致都有介绍,或者从测试代码中也能找到,这里只是举几个例子来说明一下用法。
    关于IOC(控制反转)到底是什么可以自行百度一下,由于我这个库本身就是抄的Spring Boot,所以实现原理更偏向于DI(依赖注入)。而我对IOC和DI的区分理解为:IOC为一个技术理念,而DI为IOC的一种实现。
    我目前使用的QT版本为5.15,其他版本就没有测试过。MINGW和MSVC均可用。
    以下测试代码在上面git仓库中的IocTest都能找到。

正文

1. XML注入

  1. 由于还没有去研究怎么将动态库做成QT的模块,所以这里就像加载普通动态库一样将本库加载进去就行(最新版已经可以使用QT +=的方式加载,但仍然存在些许问题,具体参见wiki)。
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../bin/ -lMcIoc
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../bin/ -lMcIocd

INCLUDEPATH += $$PWD/../McIoc/include
DEPENDPATH += $$PWD/../McIoc/include
  1. 为了能更清楚的体现所有功能和注意点,这里定义两个接口和一个实现类以及一个引用类:
        创建头文件C.h,引入#include <McIoc/McGlobal.h>,并声明接口IA、IB和实现类C以及引用类R:
#pragma once
#include <McIoc/McGlobal.h>
class R : public QObject
{
    Q_OBJECT
    MC_DECL_INIT(R)
    Q_PROPERTY(QString text READ text WRITE setText);
public:
    Q_INVOKABLE R(){}
    
    QString text() const noexcept;
    void setText(const QString &val) noexcept;
    
public slots:
    void slot_recv() noexcept;
    
private:
    QString m_text;
};
MC_DECL_METATYPE(R);

class IA
{
public:
    virtual ~IA() = default;            //!< C++中超类析构函数必须是virtual
    
    virtual void a() noexcept = 0;
};
MC_DECL_METATYPE(IA);                   //!< 这里必须使用该宏声明,否则无法从C转换到该接口。

class IB : public IA
{
    MC_TYPELIST(IA);             //!< 由于本接口有一个父接口,并且可能存在从IB转换到IA,所以这里需要使用这个宏保存父接口
public:
};
MC_DECL_METATYPE(IB);

class C : public QObject, public IB
{
    Q_OBJECT
    MC_DECL_INIT(C)                     //!< 这个宏主要用来实现一个类似于java静态代码块的功能。这里只是声明,真正实现在cpp中
    //! 同理,由于C实现至IB接口,并且可能转换到IB,所以这里需要使用该宏。
    //! 这里不需要额外指定QOBject,容器会自动指定。但如果C继承至其他类,比如QWidget,那么需要先使用MC_DECL_POINTER声明QWidget,再使用MC_TYPELIST(QWidget, IB),
    //! 当然,如果不需要从C转换到QWidget,也就不需要额外声明QWidget
    MC_TYPELIST(IB)
    Q_PROPERTY(QString text READ text WRITE setText)    //!< 使用getter和setter形式
    Q_PROPERTY(RPtr r MEMBER m_r)                       //!< 如果外界并不需要使用对象r,则可以直接使用MEMBER形式。具体请查阅QT官方文档
    Q_PROPERTY(QList<QString> texts MEMBER m_texts)
    Q_PROPERTY(QVector<RPtr> rs MEMBER m_rs)
    typedef QMap<QString, QString> StringMap;           //!< 由于QMap在Q_PROPERTY宏中有错误提示,所以这里先重定义一下
    Q_PROPERTY(StringMap mtexts MEMBER m_mtexts)
    typedef QHash<QString, RPtr> RHash; 
    Q_PROPERTY(RHash hrs MEMBER m_hrs)
public:
    Q_INVOKABLE C(){}
    
    void a() noexcept override;
    
    QString text() const noexcept;
    void setText(const QString &val) noexcept;
    
    /*!
     * \brief start
     * 
     * 被MC_BEAN_START宏标记的函数将会在C被构造后,属性未被注入前调用,即m_r等所有属性都是默认值
     */
    Q_INVOKABLE
    MC_BEAN_START
    void start() noexcept;
    
    /*!
     * \brief finished
     * 
     * 当所有属性都注入完成后调用
     */
    Q_INVOKABLE
    MC_BEAN_FINISHED
    void finished() noexcept;
    
    /*!
     * \brief threadFinished
     * 
     * 如果调用过本对象的moveToThread函数移动过生存线程,则移动之后调用此函数,否则不调用
     */
    Q_INVOKABLE
    MC_THREAD_FINISHED
    void threadFinished() noexcept;
    
signals:
    void signal_send();
    
private:
    QString m_text;                     //!< 普通字符串
    RPtr m_r;                           //!< 对象
    QList<QString> m_texts;             //!< 字符串列表
    QVector<RPtr> m_rs;                 //!< 对象数组
    QMap<QString, QString> m_mtexts;    //!< 字符串映射表
    QHash<QString, RPtr> m_hrs;         //!< 对象哈希表
};
MC_DECL_METATYPE(C);

    创建cpp文件C.cpp:

#include "C.h"

#include <QThread>
#include <QDebug>

MC_INIT(R)
MC_REGISTER_BEAN_FACTORY(R);   //!< 注册IOC
MC_INIT_END

QString R::text() const noexcept
{
    return m_text;
}

void R::setText(const QString &val) noexcept
{
    m_text = val;
}

void R::slot_recv() noexcept
{
    qDebug() << "r slot recv";
}

MC_INIT(C)
MC_REGISTER_BEAN_FACTORY(C);   //!< 注册IOC
MC_REGISTER_CONTAINER_CONVERTER(QList<QString>);    //!< 容器需要额外注册,只需注册一次即可到处使用,此宏多次调用只生效一次
MC_REGISTER_LIST_CONVERTER(QVector<RPtr>);  //!< 和MC_REGISTER_CONTAINER_CONVERTER效果一样
MC_REGISTER_MAP_CONVERTER(StringMap);       //!< 重定义之后需要使用重定义之后的类型
MC_REGISTER_CONTAINER_CONVERTER(RHash);     //!< 和MC_REGISTER_MAP_CONVERTER效果一样
//!< 可以做更多事情,此代码块中的功能将在main函数之前被调用,以后可能会改成在QCoreApplication构造时调用
//!< 所以建议其他正常操作都放在QCoreApplication构造后
MC_INIT_END

void C::a() noexcept
{
    qDebug() << "m_text:" << m_text
             << "m_r:" << m_r << m_r->text()
             << "m_texts:" << m_texts
             << "m_rs:" << m_rs
             << "m_mtexts:" << m_mtexts
             << "m_hrs:" << m_hrs
             << "obj thread:" << thread()
             << "cur thread:" << QThread::currentThread();
    emit signal_send();
}

QString C::text() const noexcept
{
    return m_text;
}

void C::setText(const QString &val) noexcept
{
    m_text = val;
}

void C::start() noexcept
{
    qDebug() << "start";
}

void C::finished() noexcept
{
    qDebug() << "finished";
}

void C::threadFinished() noexcept
{
    qDebug() << "thread finished";
}

    编写xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <!-- name为IOC中使用过的对象名,class为对象类型 -->
    <bean name="r" class="R">
        <property name="text" value="test r" />
    </bean>
    <bean name="c" class="C">
        <connect sender=" this">
            <signal>signal_send()</signal>
            <receiver name="r"></receiver>
            <slot name="slot_recv()"></slot>
            <ConnectionType>DirectConnection | UniqueConnection</ConnectionType>
        </connect>
        <property name="text">
            <value>test c</value>
        </property>
        <property name="r" ref="r"></property>
        <property name="texts">
            <list>
                <value>停封</value>
                <value>薄纸</value>
                <value>关系</value>
            </list>
        </property>
        <property name="rs">
            <!-- 都是用list标签 -->
            <list>
                <!-- 可以引入不同的对象,这里测试引入同一个对象 -->
                <ref bean="r"></ref>
                <ref>r</ref>
                <ref>r</ref>
            </list>
        </property>
        <property name="mtexts">
            <map>
                <entry key="jack" value="杰克"></entry>
                <entry>
                    <key><value>rose</value></key>
                    <value>肉丝</value>
                </entry>
            </map>
        </property>
        <property name="hrs">
            <!-- 同list -->
            <map>
                <entry>
                    <key><value>jack</value></key>
                    <value><ref>r</ref></value>
                </entry>
                <entry>
                    <key><value>rose</value></key>
                    <value><ref>r</ref></value>
                </entry>
            </map>
        </property>
    </bean>
</beans>

    这里我直接在main函数中使用:

#include <QCoreApplication>
#include <QThread>
#include <QDebug>

#include <McIoc/ApplicationContext/impl/McLocalPathApplicationContext.h>

#include "C.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    auto appContext = McLocalPathApplicationContextPtr::create(R"(E:\Code\QtCreator\McIocBoot\IocTest\myspring.xml)");
    QThread *t = new QThread(&a);
    qDebug() << "t:" << t;
    auto ia = appContext->getBean<IA>("c", t);
    ia->a();
    return a.exec();
}

    运行上面的代码应该就能看到效果:
    如果配置的时候注入了不存在的属性,容器将会调用setProperty函数注入动态属性。比如上面类C如果没有属性r,而XML中为C配置了r,那么该属性将作为C的实例对象的动态属性的形式存在,只能通过QObject::property函数获取。
    此容器不仅可以注入普通的类,还可以注入插件。将<bean name="r" class="R"></bean>中的class改为plugin,并指定一个插件路径即可。同时可以在list标签中使用plugins属性并指定一个插件所在的文件夹来同时注入多个插件:

<list plugins="./plugin">
    <ref bean="r"></ref>
</list>

如上,只要r和./plugin文件夹下的插件实现至同一个接口即可。


2. 声明式注入

    算了这玩意儿单开一章好了


其他

    其实这个库主要实现的目的就是依照IOC原理将对象的使用和对象的生成相分离,当然这里对象的生成不仅仅只是new一下,而是包括其他属性的注入。比如XML注入中对象C依赖了对象R(这里为了简化直接使用了实际对象,按照原则这里应该使用的是一个接口),而对于C来说R是什么它是不感知的,如果去掉这个容器,我们正常写代码步骤就是new完C之后将R通过setter设置进去,而这个容器就是帮你将R设置进去而已。
    对象与对象间的关系通过XML文件来表示,IOC容器从XML中读取这个关系,并生成一个或多个对象返回给你。这看起来和工厂模式非常类似,也是隐藏了构造细节,所以我也认为这是工厂模式的 一种实现形式。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部