前言
其实这个库的大致使用方法在github和gitee上的README(已经开始陆陆续续搬到wiki中)里大致都有介绍,或者从测试代码中也能找到,这里只是举几个例子来说明一下用法。
关于IOC(控制反转)到底是什么可以自行百度一下,由于我这个库本身就是抄的Spring Boot,所以实现原理更偏向于DI(依赖注入)。而我对IOC和DI的区分理解为:IOC为一个技术理念,而DI为IOC的一种实现。
我目前使用的QT版本为5.15,其他版本就没有测试过。MINGW和MSVC均可用。
以下测试代码在上面git仓库中的IocTest都能找到。
正文
1. XML注入
- 由于还没有去研究怎么将动态库做成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
- 为了能更清楚的体现所有功能和注意点,这里定义两个接口和一个实现类以及一个引用类:
创建头文件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中读取这个关系,并生成一个或多个对象返回给你。这看起来和工厂模式非常类似,也是隐藏了构造细节,所以我也认为这是工厂模式的 一种实现形式。