当前位置 : 主页 > 编程语言 > 其它开发 >

c++ web框架实现之静态反射实现

来源:互联网 收集:自由互联 发布时间:2022-05-21
0 前言 最近在写web框架,框架写好后,需要根据网络发来的请求,选择用户定义的servlet来处理请求。一个问题就是,我们框架写好后,是不知道用户定义了哪些处理请求的类的,怎么办
0 前言

最近在写web框架,框架写好后,需要根据网络发来的请求,选择用户定义的servlet来处理请求。一个问题就是,我们框架写好后,是不知道用户定义了哪些处理请求的类的,怎么办?
在java里有一个叫反射的机制,他允许我们通过传入类名来创建对象,这样我们就可以让用户在配置文件里(java可以用注解,不需要配置文件实现)声明处理url的类名。这样,当我们的框架收到网络请求后,就可以根据用户在配置文件的类名去生成对象从而调用处理请求的方法。遗憾的是,迄今为止,c++还不支持反射机制,
众所周知,c++是个造轮子的语言,没有条件可以创造条件。网上有很多实现方法,我选择了一个简单易懂的实现应用到项目,但是没有搞懂的代码不敢引入项目,就花了一点时间研究了一下代码,代码不长,但实现巧妙,由于是利用静态实现的,因此线程安全。
原文链接https://www.jianshu.com/p/9259609df791
代码主要包含3个部分,原文中是用宏来简化注册反射代码的,但不好理解,这里,我把宏展开来介绍,当然,引入代码时,可以直接用原文的代码

1. HttpServlet类

首先我们定义一个HttpServlet类,所有用户定义的处理网络请求类都必须继承这个类,并实现service方法,这样,通过多态机制,我们就能通过这个基类的指针去调用真正处理业务的子类
HttpServlet代码

class HttpServlet {
public:
    virtual void service() = 0;
    virtual ~HttpServlet() = default;
    // 调用用户自定义的代码
};

代码很简单,一个虚函数service,让继承的子类去实现业务处理,供我们调用。一个虚析构函数,保证在delete的时候能调用子类的虚构函数。

2. 两个业务实现的类 LoginServlet, IndexServlet

这两个类是业务的实现类,继承HttpServlet然后实现service方法, 代码如下

// 处理用户登陆信息
class LoginServlet : public HttpServlet {
public:
    void service() override {
        // 业务处理代码...
        cout << "LoginServlet" << endl;
    }
};

// 处理首页信息
class IndexServlet: public HttpServlet {
public:
    void service() override {
        // 业务处理代码...
        cout << "IndexServlet" << endl;
    }
};

OK,基本框架就是这样,当发生网络请求时,我们生成这两个类的对象,调用service方法,来完成网络请求。回到原来的问题,在不改变HttpService的情况下,我们怎么生成与之对应的处理类呢。
我们希望HttpServlet类中 调用用户自定义的代码是这样的

class HttpServlet {
public:
    virtual void service() = 0;
    virtual ~HttpServlet() = default;

    // 调用用户自定义的代码
    HttpServlet *httpServlet = getClassByName("className");   // 通过类名获取对象
    httpServlet->service();                                   // 调用用户实现的代码
    delete httpServlet;                                       // 删除对象
};

现在问题就在 getClassByName()怎么实现了

3 引入反射

很容易想到,创建一个map,保存类名和类的映射,这样,我们就能通过类名得到类对象了,引发几个问题:

  1. 什么时候创建这个字典?
  2. 怎么保存对象?

首先是第一个问题:最好的实现是在所有代码执行之前建立好映射,但这不太容易实现的,在编译期间用不了map这样高级数据结构。那么,我们退而求其次,在我们服务器启动之前创建,好像可以实现,那么我们怎么保证呢?静态变量,对,c++编译器保证静态变量的初始化在main函数开始之前。问题解决,我们用静态变量的方式在man函数执行之前建立好映射关系。我们创建一个类静态对象,这样就可以把注册写到构造函数里,当程序运行时,编译器帮我们构造对象,调用构造函数,从而注册反射,LoginServlet扩充

class LoginServlet : public HttpServlet {
public:
    void service() override {
        cout << "LoginServlet" << endl;
    }

    LoginServlet() = default;

private:
    static LoginServlet LoginServlet_;
    explicit LoginServlet(const string &registName) {
        auto &reflect = Reflect<HttpServlet>::getReflect();
        reflect.addReflect(registName, regist);
    }

    static HttpServlet *regist() {
        return new LoginServlet;
    }
};

LoginServlet LoginServlet::LoginServlet_("login");

现在我们类扩充了:IndexServlet就不贴了,他也有一份相同的代码,现在介绍一下为什么增加这些代码

  1. LoginServlet() 由于我们定义了一个有参构造函数,所以需要显式定义无参构造
  2. static LoginServlet LoginServlet_; 静态对象,编译器会在main函数调用之前为我们构造对象,调用构造函数,我们就在这个构造函数里完成注册
  3. explicit LoginServlet(const string &registName) 这个就是我们执行映射的构造函数,main函数之前该构造函数就会执行,里面有个模板类Reflect<HttpServlet>我们还没定义,马上介绍
  4. static HttpServlet *regist() 就一条语句,创建定义的类并返回指针,当我们需要创建对象的时候调用这个函数,所以我们只需要保存这个函数的地址就能随时随地创建对象,在explicit LoginServlet(const string &registName) 函数里,我们就是保存这个函数的地址,从而能在需要的时候调用这个函数,完成对象调用。reflect.addFlect(registName, regist);第一个参数是注册的名字,我们通过这个注册的名字生成对象,第二个参数就是该函数地址。
4 Reflect类

为了保证通用性,我们使用模板类实现,先贴代码再逐个介绍

template<typename T>
class Reflect {
private:
    using LoginType = T *(*)();                   
    unordered_map<string, LoginType> classInfoMap;  

public:
    void addReflect(const string &className, LoginType classType) {
        classInfoMap[className] = classType;
    }

    T *get(const string &className) {
        return classInfoMap[className]();
    }

    static Reflect<T> &getReflect() {
        static Reflect<T> reflect;
        return reflect;
    }
};

ok 先贴代码,然后逐条介绍

  1. using LoginType = T *(*)(); 给函数指针起个别名,其实就是上面说的regist函数
  2. unordered_map<string, LoginType> classInfoMap; 保存映射信息的字典
  3. void addReflect(const string &className, LoginType classType) 添加映射,其实就是插入一条数据到map里
  4. T *get(const string &className) 通过类名获取对象,怎么做到的呢 classInfoMap[className]获取regist函数,这个函数上面提过是可以返回对象,那么classInfoMap[className]()加()就调用了这个函数,所以就返回了这个对象的父类指针
  5. static Reflect<T> &getReflect() 单例模式,保证整个程序同一个模板参数只存在一个对象

关键的代码就这几行,只是要理解代码运行的过程,注册是在main函数之前完成的

5. 总结

虽然代码不长,但理解起来还是有点难度的,这种方式实现简单,但有个缺点就是,静态变量的生存周期是初始化到程序结束,也就是说我们注册用到的静态类会在程序的整个周期都存在
这个url和类名的映射关系,我们可以让用户写到配置文件里,这样我们来一个请求后,根据请求位置获取处理该业务的类名,然后根据类名创建对象处理业务
整体流程就是:请求资源位置 -> 获取类名 -> 创建对象 -> 处理业务

6 整体代码
点击查看代码
#include <iostream>
#include <string>
#include <unordered_map>

using namespace std;
template<typename T>
class Reflect {
private:
    using LoginType = T *(*)();
    unordered_map<string, LoginType> classInfoMap;

public:
    void addReflect(const string &className, LoginType classType) {
        classInfoMap[className] = classType;
    }

    T *get(const string &className) {
        return classInfoMap[className]();
    }

    static Reflect<T> &getReflect() {
        static Reflect<T> reflect;
        return reflect;
    }
};

class HttpServlet {
public:
    virtual void service() = 0;
    virtual ~HttpServlet() = default;
    // 调用用户自定义的代码
    static void process() {
        auto reflect = Reflect<HttpServlet>::getReflect();
        HttpServlet *httpServlet = reflect.get("login");        // 通过类名获取对象
        httpServlet->service();                         // 调用用户实现的代码
        delete httpServlet;                             // 删除对象
    }
};

class LoginServlet : public HttpServlet {
public:
    void service() override {
        cout << "用户自定义代码 LoginServlet 执行" << endl;
    }

    LoginServlet() = default;

private:
    explicit LoginServlet(const string &registName) {
        auto &reflect = Reflect<HttpServlet>::getReflect();
        reflect.addReflect(registName, regist);
    }

    static LoginServlet LoginServlet_;

    static HttpServlet *regist() {
        return new LoginServlet;
    }
};

LoginServlet LoginServlet::LoginServlet_("login");

int main() {
    HttpServlet::process();
}
7 运行结果

上一篇:netty系列之:netty对marshalling的支持
下一篇:没有了
网友评论