背景
之前有很长一段时间学过Java的SpringMVC框架,被它的简洁优雅所感动。后面从事了C++开发,遇到相识的开发场景总会联想到Java的框架上,本文就分享一下如何用C++模仿实现SpringMVC中Controller的功能效果。
分析与实现
我们先来看看SpringMVC是怎么来写Controller的
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
public String index() {
return "Hello World";
}
}
Controller组件这样写之后,框架底层预先会在web容器加载的时候,根据@Controller
注解实例化HelloWorldController类的对象,当然,这波操作自然而然会用到Java的反射机制。
当客户端请求到来的时候,框架底层就会开始解析请求路径,然后根据请求路径"/hello"
找到能够匹配上的Controller实例以及对应的方法,去调用它,调用完之后,就根据返回的内容返回Json对象或者视图对象给下一个流程处理。
那么,对于C++要来实现这个Controller效果,我们需要怎么做呢?首先先总结一下我们需要做的事情,主要分以下两步:
- 实现类似的打上
@RestController
注解就能自动将HelloWorldController加到Bean容器的效果。 - 实现打上
@RequestMapping
注解就能标记下面紧挨着的方法是对于请求路径要调用的方法,这样还不够,还需要之后能够通过这个路径,去找到这个方法,然后调用它。
要实现以上这两点,用C++有很大的阻碍,因为它没有反射,无法通过字符串去实例化一个类,也无法通过注解去标记一个方法或者类。本文想来想去,主要用了两个方法,且听我细细道来:
第一:要实现自动将对象注册进入容器中,本文用到了C++的类静态成员的初始化特性。静态成员会在main函数被执行前就完成了初始化。有了这个特性,我们就可以在Controller中添加一个静态成员,在这个静态成员的构造函数中实例化这个类,如果保存起来,就成功的达到将这个类的对象存进一个容器里面了。
编码思路如下
template<typename T>
struct BaseController {
static struct AutoInit {
void doNothing() const {}
explicit AutoInit() {
std::cout << "AutoInit......" << std::endl;
//单实例
static T t;
}
} init;
//......
};
template<typename T>
typename BaseController<T>::AutoInit BaseController<T>::init;
上诉代码中,我们将对象自动实例化的方法抽取出来,用局部静态变量方法来实现Controller的单例,按照剧本,只要我的MyController
继承至BaseController<MyController>
,他就会自动实例化MyController
。如下:
struct MyController : public BaseController<MyController> {}
int main() {
return 0;
}
然而实际上代码运行效果并没有实例化MyController。这是怎么一回事?
如果把模板去掉,代码是完全没问题的。所以这主要是跟模板有关,模板有一个原则,就是用到了才会去实例化。对于模板中的静态成员也是一样的,只要那个静态成员没有使用到,他就会忽略这个静态成员。所以,解决这个办法也很简单,我们只要有个地方告诉编译器,我们用到了这个静态成员init
就行了。
第二:我们要通过字符串形式的路径去找到那个函数并且调用它,C++中只能通过函数指针了。我们需要保存字符串和函数指针的对应关系,再结合前面的单例,就可以成功通过字符串去调用函数了。
但是,我们需要有一种看起来简单的表示方式去做这种对应关系,我们用到了宏。宏内部实现的逻辑是:每次用到那个宏,就弄出一个成员变量,成员变量的初始化就是传入的函数指针/函数对象和路径字符串。这样只要在成员变量构造函数中将这个函数对象和路径存到全局对象中,就能实现通过路径去找到那个方法,然后调用了。
值得注意的是,类中不能声明相同名字的成员变量,所以,本文用了个小技巧,用__LINE__宏来做出不同名字的变量。因为一个类中,只要我们的成员的声明不放到同一行,行数就肯定是不一样的。
思想如下
#define M_CONCAT_(a, b) a##b
#define M_CONCAT(a, b) M_CONCAT_(a, b)
#define RequestMapping(func, url) \
const PathFunction M_CONCAT(p, __LINE__) = { url,[this](HttpServletRequest *request, HttpServletResponse *response) {init.doNothing();this->func(request,response);} };\
template<bool B = true>\
RequestMapping(pp, "user/name")
void pp(HttpServletRequest *request, HttpServletResponse *response) {
std::cout << "get username..." << std::endl;
}
完整代码
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>
struct HttpServletRequest {
};
struct HttpServletResponse {
};
static std::unordered_map<std::string,
std::function<void(HttpServletRequest *request, HttpServletResponse *response)>> funcPathMap;
//
template<typename T>
struct BaseController {
static struct AutoInit {
void doNothing() const {}
explicit AutoInit() {
//单实例
static T t;
}
} init;
struct PathFunction {
PathFunction(const std::string &path,
const std::function<void(HttpServletRequest *request, HttpServletResponse *response)> &func) {
funcPathMap.emplace(path, func);
}
};
};
template<typename T>
typename BaseController<T>::AutoInit BaseController<T>::init;
//
#define M_CONCAT_(a, b) a##b
#define M_CONCAT(a, b) M_CONCAT_(a, b)
#define RequestMapping(func, url) \
const PathFunction M_CONCAT(p, __LINE__) = { url,[this](HttpServletRequest *request, HttpServletResponse *response) {init.doNothing();this->func(request,response);} };\
template<bool B = true>\
struct MyController : public BaseController<MyController> {
RequestMapping(pp, "user/name")
void pp(HttpServletRequest *request, HttpServletResponse *response) {
std::cout << "get username..." << std::endl;
}
RequestMapping(pp2, "/user/index")
void pp2(HttpServletRequest *request, HttpServletResponse *response) {
std::cout << "go to index page..." << std::endl;
}
};
int main() {
for (auto &c : funcPathMap) {
std::cout << c.first << std::endl;
c.second(NULL, NULL);
std::cout << std::endl;
}
return 0;
}
总结
思路是这个思路,后面也可以根据这个思想去拓展实现SpringMVC中的其他组件,比如说Service
感兴趣的可以查看上篇博文:如何用C++实现Spring的AOP
C++11 设计模式–模板方法模拟实现Java Spring AOP