C++11实现一个轻量级的AOP框架

AOP介绍

  AOP(Aspect-Oriented Programming,面向方面编程),可以解决面向对象编程中的一些问题,是OOP的一种有益补充。面向对象编程中的继承是一种从上而下的关系,不适合定义从左到右的横向关系,如果继承体系中的很多无关联的对象都有一些公共行为,这些公共行为可能分散在不同的组件、不同的对象之中,通过继承方式提取这些公共行为就不太合适了。使用AOP还有一种情况是为了提高程序的可维护性,AOP将程序的非核心逻辑都“横切”出来,将非核心逻辑和核心逻辑分离,使我们能集中精力在核心逻辑上,例如图1所示的这种情况。
123

实现AOP的一些方法

  实现AOP的技术分为:静态织入和动态织入。静态织入一般采用抓们的语法创建“方面”,从而使编译器可以在编译期间织入有关“方面”的代码,AspectC++就是采用的这种方式。这种方式还需要专门的编译工具和语法,使用起来比较复杂。我将要介绍的AOP框架正是基于动态织入的轻量级AOP框架。动态织入一般采用动态代理的方式,在运行期对方法进行拦截,将切面动态织入到方法中,可以通过代理模式来实现。下面看看一个简单的例子,使用代理模式实现方法的拦截,如代码所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<memory>
#include<string>
#include<iostream>
using namespace std;
class Base
{
public:

Base()
{
}

virtual ~Base()
{
}

virtual void Output(const string& str)
{

}
};

class Derived : public Base
{
public:
void Output(const string& str) override
{
cout <<str<< endl;
}
};

class Aspect : public Base
{
public:
Aspect(Base* p) : m_ptr(p)
{

}
~Aspect()
{
delete m_ptr;
m_ptr = nullptr;
}
void Output(const string& str) final
{
cout <<"Before real Output"<< endl;
m_ptr->Output(str);
cout <<"After real Output"<< endl;
}

private:
Base* m_ptr;
};


void TestAspect()
{
std::shared_ptr<Base> obj1 = std::make_shared<Aspect>(new Derived());
obj1->Output("It is obj1 test");
}

int main(int argc, char const *argv[])
{
TestAspect();
return 0;
}

输出结果:

改进版

上面的例子是通过代理对象实现了方法拦截,这里的Hello::Output就是核心逻辑,而HelloProxy是一个切面。
但是通过代理方法实现AOP还是有很多不足:不够灵活,耦合性不强。

分析

在下面这个AOP框架中,虽然只有百来行代码。但是作者就C++11的特性(尤其是可变参数模板和右值引用,所以确保你有这两个的基础再来看代码)和AOP的思想体现的淋漓尽致。

首先,作者定义了一个宏,这个定义了一个struct(众所周知在C++里面struct和class没啥区别)。这个struct只有一个成员变量,是一个枚举,枚举只有一个值。

这个struct的作用其实就是用于检测某个类是否具有Before和After函数,这两个类分别在核心代码之前和之后起作用。在后面的类中,需要根据是否有定义这两个方法来决定是否进行调用,否则会编译报错。

decltype 是一种根据括号类型来得到一种类型,经常用作函数的后置返回类型。但很多人不知道的是:当decltype接收两个参数时,如果前面那个没有定义,则返回后面的默认类型。当然很多人说decltype并不是接收两个参数,而是一个表达式和一个返回类型。但我是作为接收两个参数理解的。

declval 返回某种类型的右值引用。

Aspect类

在Aspect类中定义了三个Invoke成员函数,分别对应三种情况:有Before没After,有After没Before和两者都有。

这个Invoke并不是重载,因为enable_if这个元函数决定了只有一个Invoke被定义。

随后才重载了一个void Invoke,这个模板函数负责接收切面,通过递归的方式解开可变参数模板的参数包。在递归之前调用每次递归的切面的Before方法,递归之后调用After方法。

两个重载函数的区别在于一个接收可变数量的切面,另一个只接收一个切面。所以在递归到只剩下最里层的那个切面时,会调用上面的Invoke。

上面的Invoke通过enable_if可以接收三种不同的切面,但是作者不知道为什么下面的Invoke没有延续这种做法,只定义了一个Invoke。这就导致只能是最里面的切面可以随意定义,外面几层的切面必须定义Before和After成员函数。

感兴趣的可以将接收多层切面的Invoke也改造成上面那种形式。

此外,这个AOP框架另一个最大的问题就是其核心代码的参数和切面的参数必须保持一致,这就导致缺少了很多实用价值。没办法,可变参数模板的递归调用其参数是共享的,你传入多个切面的话根本无法区分哪里到哪里是某个切面的参数。这是一个原理上的缺陷。

但是这个AOP框架的很多思想还是值得学习的。

附上原书源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**********************************************
> File Name : aop.cpp
> Author : lunar
> Email : lunar_ubuntu@qq.com
> Created Time : Wed 14 Oct 2020 11:53:12 PM CST
**********************************************/

#include <functional>
// write an AOP framework with C++11

// Tip: when decltype receives two args, it returns the second argument when the first one is not compilable.
// This struct is to check if a class has the member function Before or After.
#define HAS_MEMBER(member)\
template<typename T, typename... Args> struct has_member_ ## member{\
private:\
template<typename U> static auto Check(int) -> decltype(std::declval<U>().member(std::declval<Args>()...), std::true_type());\
template<typename U> static std::false_type Check(...);\
public:\
enum{value = std::is_same<decltype(Check<T>(0)), std::true_type>::value};\
};\

HAS_MEMBER(Foo)
HAS_MEMBER(Before)
HAS_MEMBER(After)

//#include <Noncopyable.hpp>
template <typename Func, typename... Args>
struct Aspect {
Aspect(Func& f): m_func(std::forward<Func>(f)) {

}

template <typename T>
typename std::enable_if<has_member_Before<T, Args...>::value && has_member_After<T, Args...>::value>::type Invoke(Args&&... args, T&& aspect) {
aspect.Before(std::forward<Args>(args)...); //aspect before the core code
m_func(std::forward<Args>(args)...); //core code
aspect.After(std::forward<Args>(args)...); //aspect after the core code
}

template <typename T>
typename std::enable_if<has_member_Before<T, Args...>::value && !has_member_After<T, Args...>::value>::type Invoke(Args&&... args, T&& aspect) {
aspect.Before(std::forward<Args>(args)...);
m_func(std::forward<Args>(args)...);
}

template <typename T>
typename std::enable_if<!has_member_Before<T, Args...>::value && has_member_After<T, Args...>::value>::type Invoke(Args&&... args, T&& aspect) {
m_func(std::forward<Args>(args)...);
aspect.After(std::forward<Args>(args)...);
}

//to insert multiple aspects
template <typename Head, typename... Tail>
void Invoke(Args&&... args, Head&& headAspect, Tail&&... tailAspect) {
headAspect.Before(std::forward<Args>(args)...);
Invoke(std::forward<Args>(args)..., std::forward<Tail>(tailAspect)...);
headAspect.After(std::forward<Args>(args)...);
}

private:
Func m_func;
};
template <typename T>
using identity_t = T;

template <typename... AP, typename... Args, typename Func>
void Invoke(Func&& f, Args&&... args) {
Aspect<Func, Args...> asp(std::forward<Func>(f));
asp.Invoke(std::forward<Args>(args)..., identity_t<AP>()...);
}

#include <iostream>
using namespace std;
struct AA {
void Before() {
cout << "AA Before" << endl;
}

void After() {
cout << "AA After" << endl;
}
};

struct BB {
void Before() {
cout << "BB Before" << endl;
}

void After() {
cout << "BB After" << endl;
}
};

void core_func() {
cout << "core function called" << endl;
}

int main() {
std::function<void()> f = std::bind(core_func);
Invoke<AA, BB>(f);
return 0;
}

运行结果:

Author: chacebai
Link: http://www.spyhex.com/2020/cpp11-lightweight-aop-framework/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.