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 运行结果

c++ web框架实现之静态反射实现的更多相关文章

  1. Python基础篇【第3篇】: Python异常处理、反射、动态导入、利用反射的web框架

    异常处理 什么是异常? 异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行. 一般情况下,在Python无法正常处理程序时就会发生一个异常.异常是Python对象,表示一个错误.当P ...

  2. Node.js Web 开发框架大全《静态文件服务器篇》

    这篇文章与大家分享优秀的 Node.js 静态服务器模块.Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念.它的目标是帮助程序员构建高度可伸缩的应用程序,编写能 ...

  3. Python 之反射和普通方式对比(模拟Web框架)

    先模拟一个web页面的选择不同输出不同 vim day8-7.py #!/usr/bin/python # -*- coding:utf-8 -*- import home import accoun ...

  4. python之web框架(1):完成静态页面web服务器

    python的web框架(1) 1.首先写一个最简单的web服务器,只能给客户回应一个固定的hello world的页面. from socket import * from multiprocess ...

  5. [Python之路] 使用装饰器给Web框架添加路由功能(静态、动态、伪静态URL)

    一.观察以下代码 以下来自 Python实现简易HTTP服务器与MINI WEB框架(利用WSGI实现服务器与框架解耦) 中的mini_frame最后版本的代码: import time def in ...

  6. 06 返回静态文件的映射(函数/多线程)web框架

    06 返回静态文件的映射(函数/多线程)web框架 服务器server端python程序(函数版): import socket server = socket.socket() server.bin ...

  7. 05 返回静态文件的多线程web框架

    05 返回静态文件的多线程web框架 服务器server端python程序(多线程版): import socket from threading import Thread,currentThrea ...

  8. 04 返回静态文件的函数web框架

    04 返回静态文件的函数web框架 服务器server端python程序(函数版): import socket server = socket.socket() server.bind((" ...

  9. 03 返回静态文件的高级web框架

    03 返回静态文件的高级web框架 服务器server端python程序(高级版): import socket server=socket.socket() server.bind(("1 ...

随机推荐

  1. 编译器警告c4996

    由于编译器的原因(我用的是vs 2012),我们写程序时有时候会遇到编译器给出的警告,如: warning C4996: 'fopen': This function or variable may ...

  2. 圣诞节,把网站所有的js代码都压缩成圣诞树吧。

    本文分两章节,分别讲解如何使用js2image这个库生成可以运行的圣诞树代码 和 js2image的原理. github地址:https://github.com/xinyu198736/js2ima ...

  3. 小程序canvas文本换行生成图片

    一.图片透明及旋转 let ctx = wx.createCanvasContext('shareImg') ctx.drawImage('../../../' + res[0].path, 0, 0 ...

  4. web项目中视频的上传和展示

    思路: 上传:<form>表单提交视频-->后台使用字节流保存到本地. 展示:<video>标签展示: src属性发送请求 --> 使用字节流将视频绑定到响应并返回 ...

  5. Jar 包下载以及 maven jar 包配置

    学习内容: jar包下载是我们必须掌握的一个内容,不管是使用Maven项目还是其他项目,一般都需要引入外部的 jar 包 jar包下载 下载地址(打不开网址的直接百度搜索 maven reposito ...

  6. maven项目中各文件都没有报错,但是项目名称有红叉

             项目报错可以看到Problems(可以在Window--Show View--other--输入Problems找到) 一.可能原因     1.项目中各文件没有报错,但是项目名称中 ...

  7. 腾讯云服务nginx部署静态项目

    一直想要搭建自己的blog,买了基础云服务器练手 文章内容是根据腾讯文档(https://cloud.tencent.com/document/product/213/2131)总结 部署静态页面归纳 ...

  8. numpy教程02---ndarray数据和reshape重塑

    欢迎关注公众号[Python开发实战], 获取更多内容! 工具-numpy numpy是使用Python进行数据科学的基础库.numpy以一个强大的N维数组对象为中心,它还包含有用的线性代数,傅里叶变 ...

  9. Typora原生态的图片格式快速转化为HTML格式

    Typora更改图片样式 前言 ​ 在Typora中插入的图片,默认是居中且显示原图大小的,如果想要缩小显示,可以右击图片选择缩放图片. ​ 但是,当我上传到博客园中时,并没有保留 居中.缩放 的样式 ...

  10. MDL锁

    mdl锁的主要作用是用来维护表元数据的一致性.在表上有活动事务的时候,不可以对表元数据进行修改操作. 如果没有MDL锁的保护,那么session2可以直接执行,并导致session1出错. MDL锁是 ...