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. Redis报错:DENIED Redis is running in protected mode

    转:Redis使用认证密码登录   Redis默认配置是不需要密码认证的,也就是说只要连接的Redis服务器的host和port正确,就可以连接使用.这在安全性上会有一定的问题,所以需要启用Redis ...

  2. Linux编程 | 使用 make

    目录 简单的 makefile 文件 常规的 makefile 文件 常用参数 make 内置规则 后缀和模式规则 make 管理函数库 在Linux 环境中,make 是一个非常重要的编译命令.不管 ...

  3. 定常系统(时不变系统)和时变系统&& 动态系统和静态系统

    根据系统是否含有参数随时间变化的元件,自动控制系统可分为时变系统与定常系统两大类. 定常系统又称为时不变系统,其特点是:系统的自身性质(所研究物体的本质属性例如:质量.转动惯量等)不随时间而变化.具体 ...

  4. 洛谷 P5706 【深基2.例8】再分肥宅水

    题目连接: P5706 [深基2.例8]再分肥宅水 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 我提交的: 1 #include<iostream> 2 #inclu ...

  5. Android 预置APK

    1.   预置apk,使其不可卸载   第一步:      在 "/vendor/huawei/packages/apps" 目录下创建一个对应名称的文件夹.   第二步:   将 ...

  6. jdbc连接MySQL数据库+简单实例(普通JDBC方法实现和连接池方式实现)

    jdbc连接数据库 总结内容 1. 基本概念 jdbc的概念 2. 数据库连接 数据库的连接 DAO层思想 重构设计 3. 事务 概念 事务的ACID属性 事务的操作 4. 连接池 为什么要使用连接池 ...

  7. Blazor组件自做一 : 使用JS隔离封装viewerjs库

    Viewer.js库是一个实用的js库,用于图片浏览,放大缩小翻转幻灯片播放等实用操作 本文相关参考链接 JavaScript 模块中的 JavaScript 隔离 Viewer.js工程 Blazo ...

  8. 入行数字IC验证的一些建议

    0x00 首先,推荐你看两本书,<"胡"说IC菜鸟工程师完美进阶>(pdf版本就行)本书介绍整个流程都有哪些岗位,充分了解IC行业的职业发展方向.<SoC设计方法 ...

  9. JavaSE常用类之File类

    File类 只用于表示文件或目录的信息,不能对文件内容进行访问. java.io.File类∶代表文件和目录.在开发中,读取文件.生成文件.删除文件.修改文件的属性时经常会用到本类. File类不能访 ...

  10. C++五子棋(三)——判断鼠标有效点击

    分析 在鼠标左键点击时,我们不能让新棋子在已有棋子的位置落下,同时我们还要让棋子在规定位置落下--棋盘线的交点处. 功能实现 创建数据类型 创建头文件chessData.h和源文件chessData. ...