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. python学习笔记(九)——线程与进程

    一.线程 Python 中为我们提供了两个模块来创建线程. _thread threading thread 模块已被废弃.用户可以使用 threading 模块代替.所以,在 Python 中不能再 ...

  2. 解决 css 浮动后 父元素高度失效问题

    应用场景 子元素标签使用 浮动后,会出现浮在父元素上层,脱离了.导致父元素没办法根据子元素的高度而变化,提供以下解决方案. 解决代码 把 '.clearfix ' Class 样式添加到 父元素即可. ...

  3. APICloud Github 5大开源项目集合展示

    APICloud自成立之初,一直秉承着开源一切的初心,为了给予广大开发者们更多的资源及内容.不知不觉,2年时间已过,APICloud的github上已经集合了APICloud模块.前端框架及文档.云A ...

  4. SphinxJS——把字符串编码成png图片的超轻量级开源库

    体验地址:https://jrainlau.github.io/sp...项目地址:https://github.com/jrainlau/s... SphinxJS 一个能够把字符串编码成png图片 ...

  5. Kurento安装与入门02——运行示例前的准备

    官方一共提供了13个示例,这些示例运行的方式大同小异,一般会提供JAVA.Browser JavaScript.Node.js三种版本,这里仅演示java版本的示例.这些示例要求系统内已经正确安装了K ...

  6. Android Studio登陆界面+Button不变色问题

    今日所学内容: 1.初始相对布局 2.AS登录界面 3.一个可以下载小图标的阿里的网站iconfont-阿里巴巴矢量图标库 用GitHub账号绑定就可以免费下载 4.取颜色工具ColorCop 遇到的 ...

  7. mysql基本操作1

    数据库的分类 --1.关系型数据库-----用"表"保存数据,相关数据存入一张表中   --2.非关系型数据库-----键值数据库-----对象数据库 ###主流关系型数据库-Or ...

  8. 安装scrapy速度慢解决方案

    使用终端pip安装scrapy龟速 解决方案: 使用清华源下载 清华园链接 https://mirrors.tuna.tsinghua.edu.cn/help/pypi/ win+R打开cmd 输入p ...

  9. 在定义C++, C通用接口函数时让C++接口支持默认参数

    在SOUI4的开发中,所有SOUI核心对象都采用了一种类似COM接口的技术来导出接口. 这所以采用这种方案,主要目的是为了让SOUI4支持C语言调用,扩展SOUI的使用场景. 众所周知,C++函数的参 ...

  10. 五分钟搭建博客系统 OK?

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 概要: 通过 Docker Compose 在使用Docker容器构建的隔离环境中轻松运行 WordPress.在开始之前,请确保已安 ...