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. s函数

    Matlab 中S-函数模板翻译 10.0 基础知识 (1)Simulink仿真过程 Simulnk仿真分为两步:初始化.仿真循环.仿真是由求解器控制的,求解器主要作用是:计算模块输出.更新模块离散状 ...

  2. WebView的一些简单用法

    一直想写一个关于 WebView 控件的 一些简单运用,都没什么时间,这次也是挤出时间写的,里面的一些基础知识就等有时间再更新讲解一下,今天就先把项目出来做一些简单介绍,过多的内容可以看我的源码,都传 ...

  3. 介绍一项让 React 可以与 Vue 抗衡的技术

    好吧,我承认我是标题党.React 明明如日中天,把它与 Vue 倒过来,给 Vue 加点东西或可与 React 抗衡.不过,这两年 Vue 干的正是这事,不断加东西,不断优化,按它现有发展速度超越 ...

  4. Centos搭建 Git 服务器教程

    搭建 GIT 服务器教程 下载安装 git Git 是一款免费.开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. 此实验以 CentOS 7.2 x64 的系统为环境,搭建 git 服 ...

  5. C语言之:结构体动态分配内存(利用结构体数组保存不超过10个学生的信息,每个学生的信息包括:学号、姓名和三门课(高数、物理和英语 )的成绩和平均分(整型)。)

    题目内容: 利用结构体数组保存不超过10个学生的信息,每个学生的信息包括:学号.姓名和三门课(高数.物理和英语 )的成绩和平均分(整型). 编写程序,从键盘输入学生的人数,然后依次输入每个学生的学号. ...

  6. MySQL---char和varchar的区别

    char和varchar的区别 char表示定长, 即长度固定. varchar表示变长, 即长度可变. 当输入数据的长度小于定义的长度时, char会用空格填充, 而varchar则按照实际长度存储 ...

  7. springboot+maven实现模块化编程

    1.创建新项目repo-modele 2.右键Repo_modele -> New -> Module-->next 分别创建bs-web,bs-service,bs-entity, ...

  8. Mybatis-plugins分页助手实现查询数据分页

    其他具体代码接上文->mybatis自定义处理器 1.导入坐标 <dependency> <groupId>com.github.pagehelper</group ...

  9. cali1e4a9cee8dc这是什么东西?

    //我们查下k8s node节点,发现很多类似 cali7c620a7a67b 这样的类似网络设备的东西.//这些是什么呢?//k8s集群节点ht10,node网络情况.[root@ht10 cali ...

  10. BI系统打包Docker镜像及容器化部署的具体实现

    在过去的几年中,"云"作为明星热词站在了各种新潮技术之中,你可能使用过,但说不清它的原理:或者是没用过,但听过它的大名:也可能连它的名字都没听过,但你对这只蓝色鲸鱼一定十分眼熟.作 ...