C++ 公共组件-轻量级AOP库

C++ 公共组件-轻量级AOP库

1.AOP介绍

AOP(Aspect-Oriented Programming,面向方面编程),可以解决面向对象编程中的一些问题,是OOP的一种有益补充。面向对象编程中的继承是一种从上而下的关系,不适合定义从左到右的横向关系,如果继承体系中的很多无关联的对象都有一些公共行为,这些公共行为可能分散在不同的组件、不同的对象之中,通过继承方式提取这些公共行为就不太合适了。使用AOP还有一种情况是为了提高程序的可维护性,AOP将程序的非核心逻辑都“横切”出来,将非核心逻辑和核心逻辑分离,使我们能集中精力在核心逻辑上,
在这里插入图片描述
在上图中,每个业务流程都有日志和权限验证的功能,还有可能增加新的功能,实际上我们只关心核心逻辑,其他的一些附加逻辑,如日志和权限不需要关注,这时,就可以将日志和权限等非核心逻辑“横切”出来,使核心逻辑尽可能保持简洁和清晰,方便维护。这样“横切”的另一个好处是,这些公共的非核心逻辑被提取到多个切面中了,使它们可以被其他组件或对象复用,消除了重复代码。

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,它们经常发生在核心关注点的多处,而各处都基本相似,比如权限认证、日志、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

2.AOP的简单实现-通过代理模式

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

#include<memory>
#include<string>
#include<iostream>
using namespace std;
class IHello{
public:
    IHello(){}
    virtual ~IHello(){}
    virtual void Output(const string& str){}
};
class Hello : public IHello{
public:
    void Output(const string& str) override{
        cout <<str<< endl;
    }
};
class HelloProxy : public IHello{
public:
    HelloProxy(IHello* p) : m_ptr(p){}
    ~HelloProxy(){
        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:
    IHello* m_ptr;
};
void TestProxy(){
    std::shared_ptr<IHello> hello = std::make_shared<HelloProxy>(new Hello());
    hello->Output("It is a test");
}

int main() {
    TestProxy();
    return 0;
}

输出结果如下:

Before real Output
It is a test
Before real Output

通过HelloProxy代理对象实现了对Output方法的拦截,这里Hello::Output就是核心逻辑,HelloProxy实际上就是一个切面,我们可以把一些非核心逻辑放到里面,比如在核心逻辑之前的一些校验,在核心逻辑执行之后的一些日志等。

虽然通过代理模式可以实现AOP,但是这种实现还存在一些不足之处:

·不够灵活,不能自由组合多个切面。代理对象是一个切面,这个切面依赖真实的对象,如果有多个切面,要灵活地组合多个切面就变得很困难。这一点可以通过装饰模式来改进,虽然可以解决问题但还是显得“笨重”。

·耦合性较强,每个切面必须从基类继承,并实现基类的接口。

我们希望能有一个耦合性低,又能灵活组合各种切面的动态织入的AOP框架。

3.轻量级的AOP框架的实现-通过模板实现

要实现灵活组合各种切面,一个比较好的方法是将切面作为模板的参数,这个参数是可变的,支持1到N(N>0)切面,先执行核心逻辑之前的切面逻辑,执行完之后再执行核心逻辑,然后执行核心逻辑之后的切面逻辑。这里,可以通过可变参数模板来支持切面的组合。AOP实现的关键是动态织入,实现技术就是拦截目标方法,只要拦截了目标方法,我们就可以在目标方法执行前后做一些非核心逻辑,通过继承方式来实现拦截,需要派生基类并实现基类接口,这使程序的耦合性增加了。为了降低耦合性,这里通过模板来做约束,即每个切面对象必须有Before(Args…)或After(Args…)方法,用来处理核心逻辑执行前后的非核心逻辑。下面介绍如何实现能灵活组合各种切面的动态织入的AOP框架,如下面代码所示:

_Pragma("once")

#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(Before)
HAS_MEMBER(After)

class NonCopyable{
public:
    NonCopyable(const NonCopyable&) = delete; // deleted
    NonCopyable& operator = (const NonCopyable&) = delete; // deleted
    NonCopyable() = default;   // available
};

template<typename Func, typename... Args>
struct Aspect : NonCopyable
{
    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)...);//核心逻辑之前的切面逻辑
        m_func(std::forward<Args>(args)...);//核心逻辑
        aspect.After(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)
    {
        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)...);//核心逻辑之后的切面逻辑
    }

    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; //被织入的函数
};

//AOP的辅助函数,简化调用
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)..., AP()...);
}
  • main.cpp
#include <iostream>
#include <functional>
#include "Aspect.hpp"

using namespace std;

struct AA{
    void Before(int i){
        cout <<"Before from AA"<<i<< endl;
    }
    void After(int i){
        cout <<"After from AA"<<i<< endl;
    }
};
struct BB{
    void Before(int i){
        cout <<"Before from BB"<<i<< endl;
    }
    void After(int i){
        cout <<"After from BB"<<i<< endl;
    }
};
struct CC{
    void Before(){
        cout <<"Before from CC"<< endl;
    }
    void After(){
        cout <<"After from CC"<< endl;
    }
};
struct DD{
    void Before(){
        cout <<"Before from DD"<< endl;
    }
    void After(){
        cout <<"After from DD"<< endl;
    }
};
void GT(){
    cout <<"real GT function"<< endl;
}
void HT(int a){
    cout <<"real HT function: "<<a<< endl;
}
void TestAop1(){
    // 织入普通函数
    std::function<void(int)> f = std::bind(&HT, std::placeholders::_1);
    Invoke<AA, BB>(std::function<void(int)>(std::bind(&HT, std::placeholders::_1)), 1);
    // 组合了两个切面AA BB
    Invoke<AA, BB>(f, 1);

    // 织入普通函数
    Invoke<CC, DD>(&GT);
    Invoke<AA, BB>(&HT, 1);

    // 织入lambda表达式
    Invoke<AA, BB>([](int i){}, 1);
    Invoke<CC, DD>([]{});
}

/*------------------------------------*/
struct TimeElapsedAspect{
	void Before(int i){
        cout <<"time start"<< endl;
	}
	void After(int i){
		cout <<"time end"<< endl;
	}
};
struct LoggingAspect{
	void Before(int i){
		std::cout <<"entering"<< std::endl;
	}
	void After(int i){
		std::cout <<"leaving"<< std::endl;
	}
};
void foo(int a){
	cout <<"real HT function: "<<a<< endl;
}

void TestAop2() {
    Invoke<LoggingAspect, TimeElapsedAspect>(&foo, 1); //织入方法
    cout <<"-----------------------"<< endl;
    Invoke<TimeElapsedAspect, LoggingAspect>(&foo, 1);
}

int main() {
    TestAop1();
    TestAop2();
    return 0;
}

实现思路很简单,将需要动态织入的函数保存起来,然后根据参数化的切面来执行Before(Args…)处理核心逻辑之前的一些非核心逻辑,在核心逻辑执行完之后,再执行After(Args…)来处理核心逻辑之后的一些非核心逻辑。上面代码中的has_member_Before和has_member_After这两个traits是为了让使用者用起来更灵活。使用者可以自由选择Before和After,可以仅仅有Before或After,也可以二者都有。

需要注意的是切面中的约束,因为通过模板参数化切面,要求切面必须有Before或After函数,这两个函数的入参必须和核心逻辑的函数入参保持一致,如果切面函数和核心逻辑函数入参不一致,则会报编译错误。从另外一个角度来说,也可以通过这个约束在编译期就检查到某个切面是否正确。

从测试结果中看到,我们可以任意组合切面,非常灵活,也不要求切面必须从某个基类派生,只要求切面具有Before或After函数即可(这两个函数的入参要和拦截的目标函数的入参相同)。

发布了155 篇原创文章 · 获赞 15 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/wangdamingll/article/details/104803599