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. scanf()函数的原理

    最近使用scanf发现了自己对scanf函数还是不太了解,主要出现在无意中出现的一个错误: scanf正确的写法是,scanf中以什么格式输入变量,则变量的类型就应该是什么格式,如下面scanf输入到 ...

  2. HTML5与HTML4区别简介

    移动互联网的快速发展,尤其是4G时代已经来临,加上微软在Windows 10中搭载了新的浏览器Edge取代了IE的地位,所以现在很多网站都开始抛弃IE朝着HTML5发展,PC端在不同浏览器之间的兼容性 ...

  3. Leetcode1/242/383-HashMap常用方法以及遍历排序方式

    HashMap常用方法以及遍历排序方式 常用方法 map.containsKey() map.put() map1.equals(map2) 遍历方式 Iterator<Map.Entry< ...

  4. mysql数据乱码

    更改数据库安装时的字符编码.打开mysql安装目录,找到my.ini文件,通过使用记事本的方式打开,将这里面的default-character-set=latin1修改成gbk,注意这里面有两处需要 ...

  5. Java中的反射以及简单运用(原理+例子)

    Java反射 学习内容 1. 为什么要使用反射 2. 反射的概念 3. Java反射加载过程 4. 字节码对象理解 5. 获取字节码对象(.class)的三种方式 6. 反射常用API 8. 反射综合 ...

  6. js多线程worker

    参考地址:https://blog.csdn.net/huang100qi/article/details/89303555?utm_source=app https://www.cnblogs.co ...

  7. scss使用方法以及相关内容

    我们平时都称之为 Sass,其实可分成sass和scss, 其中Sass 是以严格的缩进式语法规则来书写,不带大括号({})和分号(;),以".sass"后缀为扩展名:而 SCSS ...

  8. Python Django项目日志查询系统

    该项目适合中小型公司日志查询工作.大型公司可以使用elk等.该系统其实就是调用了absible命令去查日志,然后把输出的信息输到页面查看. 日志查询系统 维护手册 作者:陈土锋 日期:2020年6月1 ...

  9. 时间篇之linux系统时间和RTC时间

    一.linux系统下包含两个时间:系统时间(刚启动时读取的是rtc时间)和RTC时间. 一般情况下都会选择芯片上最高精度的定时器作为系统时间的定时基准,以避免在系统运行较长时间后出现大的时间偏移.特点 ...

  10. findmnt、lsblk、mount 命令查看磁盘、目录挂载、挂载点以及文件系统格式等情况

    findmnt 展示出了目标挂载点( TARGET ).源设备( SOURCE ).文件系统类型( FSTYPE )以及相关的挂载选项( OPTIONS ),例如文件系统是否是可读可写或者只读的.根( ...