OOP的完美点缀—AOP之SpringAOP实现原理

前言

OOP与AOP

OOP(Object Oriented Programming,面向对象编程),通过封装、继承将程序抽象为各个层次的对象,进而组合为模块或者程序,达到了软件工程中的重用性、灵活性、扩展性。程序的运行笼统地可以看为各层次对象之间的相互调用。
AOP(Aspect Oriented Programming,面向切面编程),将程序运行过程分解为一个个的切面,对特定的切面(某个步骤或者阶段)进行提取,达到解耦各种不同逻辑代码。
OOP是在程序分块层面上进行考虑,而AOP则是在程序运行的切面上进行考虑。

可以将AOP理解为一种无损伤型的"切面"激光手术刀。OOP使属性和行为被封装为了一个严实、密不透风的对象。想要改变由对象保护着的方法,就可以通过该激光手术刀,既不损伤对象(即是,不破坏对象的封装),又不添加任何冗余的代码,实现对原对象的方法的增强处理。

不得不说,AOP实在是一种巧妙的编程思想!!!弥补了OOP中一些难以解决的问题。例如,
1. 类应该是纯净的,不应含有与本身无关的逻辑。如日志跟踪的逻辑代码。这样的类就可以更好地重用,更有效地被AOP的切入更多的业务逻辑, 举例代码如下:


/*<br>
 * 假如需要记录某只柯基狗的日常, 
 * 我们总不能让它自己来记录吧??
 * 如 下面的注释了的方法
 * 看起来是不是非常怪异,一只狗狗自己给自己写日记
 */
class dog{
    void run(){
        //note.write("散步了");
    }
    void sleep(){
        //note.write("又睡懒觉了");
    }
}

2. OOP为不同类别的对象引入公共方法时,往往力不从心,造成大量的分散的重复代码,重用性真的很差,每次都要改动真的很麻烦。

class dog{
    private Note note = new Note();
    
    void run(){
        note.write("散步了");
    }
    void sleep(){
        note.write("又睡懒觉了");
    }
}

AOP实现原理

AOP编程.png

本文中"特定处理"指的日志记录、事务管理、异常处理等等之类的各种通用的业务逻辑
主要分为两大类:
是采用动态代理,对被代理对象和特定处理进行修饰和封装,得到代理对象,从使得被代理对象中不需切入任何的代码,如上图:
简单的代理:实现不入侵原始对象而切入各种通用的业务逻辑(eg: 参数验证、日志记录方法等),代码示例如下:
是采用静态织入,如AspectJ,使用其特定的语法创建切面,在编译期间将切面织入代码中。又如,通过AspectJ编译出来的class文件与普通编译出来的有很大区别,这块没有了解,不再赘述。

interface Interface{
    void doSomething();
}

//原始对象
class RealObject implements Interface{
    @Override
    public void doSomething() {
        System.out.println("原始对象的行为");
    }
}

//代理
class SimplProxy implements Interface {
    private Interface proxied;

    public SimplProxy(Interface proxied){
        this.proxied = proxied;
    }

    public void doSomething(){
        System.out.println("处理一些通用的业务逻辑, 如参数校验等等");
        proxied.doSomething();
    }
}

//调用者
class Caller{
    public static void call(Interface iface){
        iface.doSomething();
    }

    public static void main(String[] args){
        call(new SimplProxy(new RealObject()));
    }
}
//输出:
1.处理一些通用的业务逻辑, 如参数校验等等
2.原始对象的行为
就这样,一些通用的业务逻辑被代理简单地切入到了原始对象之前执行

AOP使用场景

权限控制、异常处理、缓存、事务管理、日志记录、数据校验等等

AOP基本概念

  • 切面(Aspect): 程序运行过程中的某个的步骤或者阶段
  • 连接点(Joinpoint): 程序运行过程中可执行特定处理(增强处理)的点, 如异常处理。而在SpringAOP中,方法调用是连接点。
  • Advice(通知、处理、增强处理): 在符合的连接点进行的特定处理 (增强处理)
  • 切入点(Pointcut): 可切入进行增强处理的连接点。AOP核心之一就是如何用表达式来定义符合的切入点。在Spring中,默认使用AspectJ的切入点语法。
    由于Spring AOP只支持以Spring Bean的方法调用来作为连接点, 所以在这里切入点的定义包括:
    • 切入点表达式, 来限制该能作用的范围大小,即是,能匹配哪些bean的方法
    • 命名切入点
                                                        
    @Pointcut("execution(*   com.xxx.xxx.service.*.*     (..))    ")        //切入点
    public void pointCutExpress(){                                         //命名切入点
    }
    //常见的 切入点表达式语法 如下:
    execution(返回值类型    方法所属类.方法名    方法形参列表    抛出的异常声明)
    例如:
    上面第一个 * 指匹配所有返回值类型
    第二个 * 指service包下的所有类
    第三个 * 指各个类中的所有方法
    (..)中的 .. 指零个或者多个(任意数量)的参数
  • 目标对象: 被进行增强处理的对象
  • AOP代理: 是一个重新封装了(增强处理 + 被代理对象的方法 )方法的代理对象。
  • 织入(Weaving): 就如它名字表述的一样,增强处理切入目标对象以后,并获得代理对象(AOP代理)的这个过程,就是织入。按其作用的时间分类为,编译时织入与运行时织入。

SpringAOP的使用方法

基于注解的零配置方式:
一、启动@AspectJ注解支持,旭在相应的容器内,加入如下片段:

<beans  xmlns:aop="http://www.springframework.org/schema/aop" <!-- 必须有相应的XML Schema 配置 -->
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 
        
        <!-- 必须相应的容器内,启动@AspectJ支持,如对Springmvc的Contrller织入,  则应在Springmvc.xml中配置 -->
        <aop:aspectj-autoproxy />
        <!-- 扫描相应的包 -->

二、定义切面bean

@Aspect
@Compement
public class Advice{
    
    //定义切入点, 业务处理逻辑等等其他内容
    
}

三、定义@Before、@After、@Around等增强处理

    //定义切入点表达式
    //配置匹配service包下所有的类、所有的方法
    @Pointcut("execution(* com.xxx.xxx.service.*.*(..))")
    public void pointCutExpress(){
    }

四、定义处理方法

    
    @After("pointCutExpress()")
    public  void closeResource(){
        //After注解,更适合于释放资源
    }
    

通过注解和动态代理实现简单AOP

一、切入点注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyPointCut {
    /*模拟简单的注解定义切入点*/
    public String expression();
}

二、原始对象及接口

interface SomeMethods{
    void method1();
    void method2(String args);
}

public class Implementation implements SomeMethods{

    @Override
    public void method1() {
        System.out.println("原始对象的方法1");
    }

    @Override
    public void method2(String args) {
        System.out.println("原始对象的方法2及参数:" + args);
    }
}

三、动态代理工厂


class MyAspect{

    @MyPointCut(expression = "com.auhnayuil.comm.Implementation.*")
    public void Logger(){
        System.out.println(">>>>>>>>>>>>>>>>>正在记载日志中<<<<<<<<<<<<<<<<<<<<<<<");
    }

}
class SimpleProxyFactory{
    /*简单代理工厂*/

    public static Object getProxyObject(final Object realObject, final Object aspectObject){/*代理对象  切面定义类*/
        final Class<?> realObjectClass = realObject.getClass();
        final Class<?> aspectObjectClass = aspectObject.getClass();
        return Proxy.newProxyInstance(
                realObjectClass.getClassLoader(),
                realObjectClass.getInterfaces(),
                new InvocationHandler(){
                    /*模拟简单的@Before日志注解*/
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /*加载切入点信息. 这里的方法Logger被硬编码了, 后期可以根据注解来解决*/
                        Method pointCutMethod = aspectObjectClass.getMethod("Logger", new Class[] {});
                        MyPointCut myPointCut = pointCutMethod.getAnnotation(MyPointCut.class);
                        /*判断切入点, 并执行其方法*/
                        String expression = myPointCut.expression();
                        String[] express = expression.split("\\.");
                        int exprLength = express.length;
                        if("*".equals(express[exprLength - 1])){
                            /*这里只演示一种情况*/
                            pointCutMethod.invoke(aspectObject, new Class[] {});
                        }
                        /*执行原始对象的方法*/
                        return method.invoke(realObject, args);
                    }
                }
        );
    }

}

public class ProxyDemo {

    public static void main(String[] args){

        SomeMethods someMethods = new Implementation();
        SomeMethods proxyObject = (SomeMethods) SimpleProxyFactory.getProxyObject(someMethods, new MyAspect());
        proxyObject.method1();
        proxyObject.method2("auhnayuiL");

    }

}

四、输出结果

>>>>>>>>>>>>>>>>>正在记载日志中<<<<<<<<<<<<<<<<<<<<<<<
原始对象的方法1
>>>>>>>>>>>>>>>>>正在记载日志中<<<<<<<<<<<<<<<<<<<<<<<
原始对象的方法2及参数:auhnayuiL
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容