前言
面向切面编程(思想)AOP Aspect Oriented Programming,是面向对象基础上 更关注最终目标 而不关注中间的小目标,简而言之,就是我们的目标(例如constroller)触发了我们关注的方法,此时就执行我们的观察者行为,例如在目标的方法触发前做事,触发后做事等等。
为了让大家更好的理解,这里把面向过程、面向对象先讲解一遍。
面向过程编程(思想)POP
Procedure Oriented Programming,面向过程编程思想(关注事情步骤,事情本身),将一件事情的整体看作是一个流程,我们更关注事情的流程、步骤。比如我去超市买菜,我要下楼,等红绿灯,走过几条街道,进入超市选择自己要的菜,然后排队买单。我会关注这些过程要经历的事件,步骤。
面向对象编程(思想)OOP
Object Oriented Programming,面向对象编程思想(关注中间有几个对象参与其中),将原有整体事情的步骤,拆分成小段,每一个小段封装成一个单独的事情(方法),不同的方法交给不同的人来做,例如我去超市买菜,我在家里出发,最终目的是在超市买菜,中间几个环节交给不同的人(对象)来帮我做:下楼(被人背下去),打车,选菜,排队等等都有人帮我做,我只要把这些顺序连起来,指挥不同的对象按照我要求的顺序执行便可。
面向切面编程(思想)AOP
Aspect Oriented Programming,面向切面编程思想(面向对象基础上 更关注最终目标 而不关注中间的小目标),但要注意切面本身也是对象,比如我去超市买菜,我为起始对象,超市为目标对象,在我到超市之前所经历的事情都归纳到切面对象(下楼,打车,选菜,排队执行这些方法的人(对象)等等),连接点(管理切面对象中的方法按顺序执行),代理对象负责管理这些切面对象,切点为买菜(即在目标对象中触发我们关心的方法);
案例
这里就用js模拟了,js模拟简单些,java模拟的话思路也是一样的,而且java的spring本身用xml或注解就可以了,这里主要讲思想
// 面向对象的方式 let money = 100; class PersonA{ static before(){ console.log("走路去超市买菜"); } } class My{ static buy(){ money -= 55; console.log("买完菜,减去55元"); } } class PersonC{ static after(){ console.log("走路回家"); } } PersonA.before();//走路去超市买菜 My.buy();//买完菜,减去55元 PersonC.after();//走路回家
// 面向切面的方式 class agency{//可以理解为过滤器 before(){ console.log("走路去超市"); } buy(Func,spend){//代理对象 this.before(); Func(spend); this.after(); } after(){ console.log("走路回家"); } } let agencyObj = new agency();//找代理,类似找人帮我们买菜送上门,这方式好处在于其他人还可以找代理买家具,买学习用品等等,我们生成了一次代理,然后传不同的任务进去让代理帮我们执行 class Myself{ buy(agency,spend){ agencyObj.buy(agency,spend);//代理对象帮我们执行的买菜方法 } } let my = new Myself(10000);//实例化自己 //制定所要做的第一件事 function buy(spend) { money -= spend; console.log("买完菜,减去"+spend+"元,还剩:"+money+"元"); } my.buy(buy,55);//这里我们以为还是自己调用方法去买菜,实际底层是代理对象帮我们执行的买菜方法 money = 1000; //制定所要做的第二件事 function buy2(spend) { money -= spend; console.log("买完日用品,减去"+spend+"元,还剩:"+money+"元"); } my.buy(buy2,198); //可以看到,每次我们只需要把最终要实现的功能交给代理,最终代理对象返回想要的结果给我们 // 执行结果: // 走路去超市 // 买完菜,减去55元,还剩:45元 // 走路回家 // 走路去超市 // 买完日用品,减去198元,还剩:802元 // 走路回家
上面的before和after方法可以当成过滤器,比如我要得到的数据渲染前先解密,解密渲染后,部分数据用*替换这类场景。
稍微复杂一点的使用方式
设置前置方法--before,后置方法--after-returning,异常处理--after-throwing,最终执行--after,环绕--around;
前置方法是在目标方法前执行,后置方法在目标方法之后执行,异常处理即处理异常事件,最终执行即不管发生什么都一定会触发的事件,环绕即在目标方法触发时先后执行的事件,类似前置方法加后置方法。
案例:
// 面向切面的方式 class agency{//可以理解为过滤器 beforeMethod(){ console.log("走路去超市"); } buy(Func,spend){//代理对象 try{ // 前置 this.beforeMethod(); // 环绕前 // 目标方法(环绕的是目标的前后) // Func(spend); this.aroundMethod(Func,spend); // 环绕后 // 后置 this.afterReturningMethod(); }catch(e){ // 异常 this.afterThrowingMethod(); }finally{ // 最终 this.afterMethod(); } } afterReturningMethod(){ console.log("走路回家"); } afterThrowingMethod(){ console.log("代购有事,此单取消"); } afterMethod(){ console.log("谢谢您,辛苦啦"); } aroundMethod(Func,spend){ console.log("上电梯"); Func(spend); console.log("下电梯"); } } let agencyObj = new agency();//找代理,类似找人帮我们买菜送上门,这方式好处在于其他人还可以找代理买家具,买学习用品等等,我们生成了一次代理,然后传不同的任务进去让代理帮我们执行 class Myself{ buy(agency,spend){ agencyObj.buy(agency,spend);//代理对象帮我们执行的买菜方法 } } let my = new Myself(10000);//实例化自己 //制定所要做的第一件事 function buy(spend) { money -= spend; console.log("买完菜,减去"+spend+"元,还剩:"+money+"元"); } my.buy(buy,55);//这里我们以为还是自己调用方法去买菜,实际底层是代理对象帮我们执行的买菜方法 money = 1000; //制定所要做的第二件事 function buy2(spend) { money -= spend; console.log("买完日用品,减去"+spend+"元,还剩:"+money+"元"); } my.buy(buy2,198); //可以看到,每次我们只需要把最终要实现的功能交给代理,最终代理对象返回想要的结果给我们 // 执行结果: // 走路去超市 // 上电梯 // 买完菜,减去55元,还剩:45元 // 下电梯 // 走路回家 // 谢谢您,辛苦啦 // 走路去超市 // 上电梯 // 买完日用品,减去198元,还剩:802元 // 下电梯 // 走路回家 // 谢谢您,辛苦啦
这里也可以设置为前置方法和后置方法为默认,其他方法体根据我们传入的参数值的不同(例如数字)来判断执行哪些方法,就是约定优于配置,大家约定俗成,会更有效率。
思路
面向切面的方式就是更注重结果而不注重中间实现的步骤,中间无论由多少个切面对象帮你做了事,都由一个代理对象来帮你管理执行,而我们需要做的就是把要做的事告诉代理对象。当然面向切面的方式是否使用要根据业务场景来定,如果发现有多个方法中间需要执行的步骤流程一致,而这些方法只需要拿到经过这些步骤之后所得到的结果,不关心中间发生了什么,就使用面向切面的方式。