精华内容
下载资源
问答
  • 面向切面编程

    2021-04-02 09:17:21
    1. SpringAOP基本概念 1.AOP概念和使用原因 现实中有 些内容并不是面向对象 (OOP )可以解决的,比如数据库...从上图可知,交易和账户都是对象,两个对象需要在同一个事务中控制,因此需要面向切面的方法,切面就是

    1. SpringAOP基本概念

    1.AOP概念和使用原因
    现实中有 些内容并不是面向对象 (OOP )可以解决的,比如数据库事务,它对于企业级的 Java EE 应用而言是十分重要的 又如在电商网站购物需要经过交易系统、财务统,对于交易系统存在一个交易记录的对象,而财务系统则存在账户的信息对象。从这个角度而 ,我 需要对交易记录和账户操作形成一个统一的事务管理。交易和账户的事务,要么全部成功,要么全部失败。
    在这里插入图片描述
    从上图可知,交易和账户都是对象,两个对象需要在同一个事务中控制,因此需要面向切面的方法,切面就是数据库事务。
    AOP的意义:首选可以拦截一些方法,然后将各个对象组织成为一个整体。当固定好动态的流程,则可以在交易前后、交易正常完成后或者交易异常发生时,使用约定记录相关的日志。
    例如:使用MyBatis实现购买记录事务流程:
    在这里插入图片描述
    在这里插入图片描述
    使用Spring实现修改角色备注:
    在这里插入图片描述这段代码除了一个注解@Transactional 没有任何关于打开或者关闭数据库资源的代码,更没有任何提交或者回滚数据库事务的代码 但是它却能够完成之前上一段代码所示的全部功能。注意 这段代码更简洁, 更容易维护,主要都集中在业务处理上,而不是数据库事务和资源管控上,这就是 AOP 的魅力 。
    一般正常的SQL的执行逻辑:
    1、打开通过数据库连接池获得数据库连接资源,进行设置。
    2、执行对应的SQL语句,对数据进行操作
    3、当SQL执行过程发生异常则回滚事务
    4、当SQL执行过程没有异常则最后提交事务
    5、最后,关闭一些链接资源
    在这里插入图片描述
    可以看到该图和AOP的流程类似,完全可以按照该流程进行封装,使用动态代理将代码编入对应的AOP流程。例如:
    1、打开获取数据连接在before方法中完成
    2、执行SQL,使用反射机制调用
    3、当发生异常则回滚事务,当没有异常则提交事务,然后关闭数据库连接资源
    对于事务有如下约定:
    1、当方法标有@Transactional时,则方法启用数据库事务
    2、默认情况下,当原有方法出现异常则回滚事务,当没有发生异常则提交事务,这样整个事务管理AOP完成整个流程,无需编写任何代码实现
    3、最后关闭数据库资源
    使用AOP框架的SQL流程:
    在这里插入图片描述
    AOP通过动态代理方式带来管控各个对象操作的切面环境,管理包括日志、数据库事务等,可以在反射原有对象方法之前正常返回、异常返回事后插入自己的逻辑代码,甚至取代原始方法。一些常用的流程中,如数据库事务,AOP提供默认的实现逻辑。
    2.面向切面的术语
    (1)切面
    切面就是在一个怎样的环境中工作。数据库事务直接贯穿整个代码层面,就是一个切面,能够在被代理对象的方法之前、之后,产生异常或正常返回后切入自己的代码。动态代理中可以将其理解为一个拦截器。
    (2)通知
    通知是切面开启后,切面的方法。根据在代理对象真实方法调用前、后的顺序和逻辑区分。
    前置通知:在动态代理反射原有对象方法或者执行环绕通知前执行的通知
    后置通知:在动态代理反射原有对象方法或者执行环绕通知后执行的通知,无论是否抛出异常都会被执行
    返回通知:在动态代理反射原油对象方法或者执行环绕通知后执行的通知
    异常通知:在动态代理反射原有对象方法或执行环绕通知产生异常后执行的通知
    环绕通知:在动态代理中,取代当前被拦截对象的方法,通过参数或反射调用被拦截对象的方法
    (3)引入
    引入允许在现有的类添加自定义的类和方法
    (4)切点
    动态代理中被切面拦截的方法就是一个切点,切面将可以将其切点和被拦截的方法按照一定的逻辑织入约定的流程中
    (5)连接点
    连接点是一个判断条件,由它可以指定那些是切点。对于指定的切点,Spring会生成代理对象去使用对应的切面对其拦截,否则不会拦截。
    (6)织入
    织入是一个生成代理对象的过程,实际代理的方法分为静态代理和动态代理。静态代理在编译class文件时生成的代码逻辑,但是在Spring中不使用该方式。一种通过ClassLoader也就是在类加载的时候生成的代码逻辑,在应用程序代码运行前就生成对应的逻辑。另一种在运行期,动态生成代码,就是SpringAOP使用的方法。
    AOP流程如下:
    在这里插入图片描述
    3.Spring对AOP支持
    Spring 中有 4种方式去实现 AOP 拦截功能:

    • 使用ProxyFactoryBean和对应的接口实现AOP
    • 使用xml配置AOP
    • 使用@AspectJ注解驱动切面
    • 使用AspectJ注入切面

    3.使用@AspectJ注解开发SpringAOP

    1.选择切点
    以某个类的某个方法作为切点,首先建立一个接口:
    在这里插入图片描述
    对该接口进行实现:
    在这里插入图片描述那么用动态代理的语言就是要为类 RoleServicelmpl 生成代理对象 然后拦截 printRole 方法。
    2.创建切面
    选好切点,创建切面。如同一个拦截器,在Spring中只要使用@Aspect注解一个类,则SpringIoC容器就认为这是一个切面:
    在这里插入图片描述
    代码中的注解使用了正则式,也就是告知SpringAOP,需要拦截什么对象的什么方法。
    3. 连接点
    Spring如何判断是否需要拦截方法的,毕竟并不是所有的方法都需要使用 AOP 编程 这就是个连接点的问题。Spring 是通过这个表达式判断是否需要拦截你的方法的,这个表达式是:
    在这里插入图片描述
    其中:execution代表执行方法时候会触发;*代表任意返回类型的方法;com.ssm.chapterl l .aop .service.impl.RoleServicelmpl :代表类的全限定名。;printRole为被拦截方法名称;
    4.测试AOP
    首先对Sprint的Bean进行配置,使用注解java配置:
    在这里插入图片描述
    使用XML定义切面:
    在这里插入图片描述无论用 XML 还是用 Java 的配置,都能使 Spring 产生动态代理对象,从而组织切面,把各类通知织入到流程当中。测试AOP:
    在这里插入图片描述
    在这里插入图片描述
    5.环绕通知
    可以同时实现前置通知和后置通知。例如:
    在这里插入图片描述
    在一个切面里通过@Aorund注解加入切面的环绕通知,通知里有一个ProceedingJoinPonit参数,该参数可以反射切点方法。
    6、 织入
    织入是生成代理对象的过程,是否拥有接口不是使用AOP的一个强制要求。使用JDK动态代理时必须拥有接口,而使用CGLib不需要。
    动态代理对象是由 Spring loC 容器根据描述生成的, 一般不需要修改它,对于使用者而言,只要知道 AOP 术语中的约定便可以使用 AOP 了,只是在 Spring 中建议使用接口编程。
    7.给通知传递参数
    首先修改切点为一个多参数方法:
    在这里插入图片描述
    给通知传递参数:
    在这里插入图片描述

    在连接点上加入参数就可以获取动态代理。
    8.引入
    Spring AOP 只是通过动态代理技术 把各类通知织入到它所约定的流程当中,有时候我们希望通过引入其他类的方法来得到更好的实现,这时就可以引入其他的方法了。
    首先定义一个接口:
    在这里插入图片描述
    实现该接口:
    在这里插入图片描述
    在切面中加入RoleVerifier:
    在这里插入图片描述
    @DeclareParents注解:
    value="com.ssm.chapterl l aop.service.impl.RoleServicelmpl+”: 表示对 RoleServicelmpl类进行增强,也就是在 RoleServicelmpl 中引入1个新的接口.
    defaultlmpl :代表其默认实现类,这里是 RoleVerifierlmpl。
    使用引入增强检测角色是否为空:
    在这里插入图片描述

    4.使用XML配置开发SprintAOP

    xml配置AOP的元素:
    在这里插入图片描述
    定义XML配置AOP拦截的接口:
    在这里插入图片描述
    接口实现类:
    在这里插入图片描述
    实现一个切面类:
    在这里插入图片描述
    1.前置通知、后置通知、返回通知、异常通知
    使用XML配置多个通知:
    在这里插入图片描述
    在这里插入图片描述
    引入XML定义AOP的命名空间,然后定义一个roleService类和切面xmlAspect类,通过<aop:config >取出定义AOP的内容信息。
    定义通知的方法会织入到流程中,和使用注解一样,可以通过定义切点,然后引用到别的通知。
    定义切点并引入:spring-cfg4.xml:
    在这里插入图片描述
    2.环绕通知
    加入环绕通知:
    在这里插入图片描述
    ProceedingJoinPoint 连接点在注解方式中讨论过,通过调度它的 proceed 方法就能够调用原有的流程了。
    测试xml定义的AOP编程:
    在这里插入图片描述
    读入xml文件,然后铜鼓容器获取Bean,创建角色类,再打印角色。
    3.给通知传递参数
    通过xml配置,引入参数到通知当中。
    在这里插入图片描述
    带上参数role:
    在这里插入图片描述
    和注解的方式不同,使用and代替&&,在xml中&有特殊含义。
    4.引入
    无论是使用JDK动态代理或是使用CGLIB动态代理都可以将代理对象挂到多个接口,这样就能引入新的方法。通过xml引入新的功能:
    在这里插入图片描述

    5.经典SpringAOP应用程序

    首先定义一个类来实现前置通知,其要求类实现MethodBeforeAdvice接口的before方法。
    定义ProxyFactoryBeanAspect类:
    在这里插入图片描述
    使用xml描述ProxyFactoryBean生成代理对象:
    在这里插入图片描述
    在这里插入图片描述
    测试ProxyFactoryBean定义的前置通知:
    在这里插入图片描述

    6. 多个切面

    Spring可以支持多个切面,当有多个切面时,测试过程中发现它不会存在任何顺序,这些顺序代码会随机生成,但是有时候希望按照指定的顺序运行。首先要定义一个切点方法,创建一个接口MultiBean:
    在这里插入图片描述
    实现该接口:
    在这里插入图片描述
    定义三个切面:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    多切面测试Java配置:
    在这里插入图片描述
    使用AnnotationConfigApplicationContext加载配置文件。可以得到如下日志:
    在这里插入图片描述
    在Spring中有多个方法,当使用注解的切面,则可以给切面加入注解@Ordered,如在Aspect1类中加入@Order(1)"
    在这里插入图片描述
    给Aspect2类加入@Order(2),给Aspect3加入@Order(3),再进行测试,得到日志:
    在这里插入图片描述
    多个切面的执行顺序:
    在这里插入图片描述
    上图展示了一条责任链,Spring底层也是通过责任链模式处理多个切面。通过实现Ordered接口也可以实现切面,它定义了一个getOrder方法。通过Ordered接口实现排序:
    在这里插入图片描述
    AOP是Spring两大核心内容之一,通过AOP可以将一些公用的代码抽取出来,进而减少开发者的工作量。

    展开全文
  • 感受面向切面编程

    2020-12-20 14:34:36
    什么是面向切面初听面向切面编程时, 一头雾水, 什么是面向切面, 只听说过面向对象(OOP), 面向过程(PO), 函数式编程(FP), 面向切面 ? 面向的难道是某一个面?面向搜索引擎后才了解到, 面向切面是一种编程范式(Aspect ...

    什么是面向切面

    初听面向切面编程时, 一头雾水, 什么是面向切面, 只听说过面向对象(OOP), 面向过程(PO), 函数式编程(FP), 面向切面 ? 面向的难道是某一个面?

    面向搜索引擎后才了解到, 面向切面是一种编程范式(Aspect Oriented Programming), 简写 AOP, 特点是与原有逻辑解耦, 无侵入.

    在后端开发工作中, 常见使用的场景是 断点调试/打印日志/…,

    而在前端开发工作中, 应用场景比较灵活多变, 可以是在一次表单提交中, 在表单提交前作表单验证(前置), 或在表单提交后作数据刷新/页面跳转/Cookie 刷新等 (后置), 也可以在提交的同时作数据埋点(横向), 或是打印日志。。。

    对于外部的新加入的逻辑, 为了不破坏原有的业务逻辑, 我们就可以使用 AOP 去组织代码, 分离 [业务逻辑] 与 [琐碎事务]

    AOP 的关键概念点

    前置(before) 在目标方法执行前执行

    后置(after) 在目标方法执行后执行

    异常(after throwing)在目标方法抛出异常时执行

    环绕 (around) 在目标方法执行前后

    前置执行函数

    const before = function(fn, action) {

    return function(...args) {

    action.apply(this, args);

    const res = fn.apply(this, args);

    return { res, params: args };

    };

    };

    后置执行函数

    const after = function(fn, action) {

    return function(...args) {

    let res = fn.apply(this, args);

    action.apply(this, args);

    return { res, params: args };

    };

    };

    异常执行函数

    const throwing = function(fn, action) {

    let ret = { res: undefined, params: undefined };

    return function(...args) {

    try {

    const res = fn.apply(this, args);

    return (ret = { res, params: args });

    } catch (err) {

    action.apply(this, args);

    return (ret = { res: err, params: args });

    }

    };

    };

    环绕执行函数

    const round = function(fn, actionBefore, actionAfter) {

    return function(...args) {

    actionBefore.apply(this, args);

    const res = fn.apply(this, args);

    actionAfter.apply(this, args);

    return { res, params: args };

    };

    };

    使用场景 — 请求记录

    const request = config => axios.request(config);

    const ButtonClickFn = function () {

    ...do something

    }

    document.querySelector('#submit').click = before(ButtonClickFn, () => {

    request({

    url: 'http://your_upload_log_url',

    method: 'GET',

    params:

    { TYPE: 'BUTTON_CLICK', POSITION: '' }

    });

    });

    使用场景 — 异常处理

    const originRequest = config => axios.request(config);

    const wrapperedRequest = throwing(originRequest, function() {

    ...异常上报

    });

    其它实现方式

    实际上除了使用高阶函数的方法实现, 我们还可以使用 ES7 的装饰器/Ojbect.defineProperty 实现, 或基于原型链去实现

    作者:跨越银河Galaxy

    展开全文
  • 前言面向对象(OOP)作为经典的设计范式,对于我们来说可谓无人不知,还记得...既然OOP这么多优点,那么经常被大家提起的面向切面编程(AOP)是什么回事呢,下面我们就一起来看一下。AOP定义第一步还是要知道aop是什么,...

    前言

    面向对象(OOP)作为经典的设计范式,对于我们来说可谓无人不知,还记得我们入行起始时那句经典的总结吗-万事万物皆对象。

    是的,基于OOP思想封装、继承、多态的特点,我们会自然而然的遵循模块化、组件化的思维来设计开发应用,以到达易维护、可扩展、高复用的目的。

    既然OOP这么多优点,那么经常被大家提起的面向切面编程(AOP)是什么回事呢,下面我们就一起来看一下。

    AOP定义

    第一步还是要知道aop是什么,先个来自维基百科的解释:

    面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。

    侧面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。

    其从主关注点中分离出横切关注点是面向侧面的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来.

    业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过侧面来封装、维护.

    这样原本分散在在整个应用程序中的变动就可以很好的管理起来。

    tip

    确实有点那么不太清晰,有点乱。不过在乱之前,我们可以选能理解的部分先看一下:

    侧面(也就是切面) 用来描述分散在对象、类或函数中的横切关注点。

    重点在这,分散在对象中的横切关注点,可以猜一下是什么,应该就是不同对象之间公用的部分

    侧面的概念源于对面向对象的程序设计的改进,它还可以用来改进传统的函数.

    AOP 显然不是OOP的替代品,是OOP的一种补充。

    从主关注点中分离出横切关注点是面向侧面的程序设计的核心概念。

    具体到业务项目中来说,主关注点就是业务逻辑了。针对特定领域问题代码的调用,就是AOP要关注的部分

    简而言之,AOP是针对业务处理过程中的切面(即非业务逻辑部分,例如错误处理,埋点,日志等)进行提取.

    它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果(目的是降低耦合)。

    具体到实现来说就是通过动态的方式将非主关注点部分插入到主关注点(一般是业务逻辑中)

    说了这么多,可能不太明白,还是一起看代码吧。

    埋点场景

    很普遍的这么个场景,需要点击按钮之后进行信息上报。

    假设我们有这么个logger的工具,可以进行上报:

    const logger = console.log

    //引入即可使用

    logger('按钮被点击了')

    那么,我们直接撸起来吧:

    const doSomething = ()=>{

    console.log('doSomething')

    }

    let clickHandler = ()=>{

    logger('doSomething之前')

    // n行代码

    doSomething()

    logger('doSomething之后')

    //n 行代码

    }

    看起来也没什么的,简单粗暴。

    如果有30个按钮,每个业务逻辑不同,都需要埋这个点(假设打点信息一致)。

    我们30个函数里面,都要手动写这个方法的话,这也太坑爹了吧。

    主要是与业务代码严重耦合,哪天不小心动了点其他内容,手抖误删了,就gg了。

    后续维护的时候,简直噩梦。

    仔细看一下,这不就是符合AOP的使用前提吗,那么试试AOP吧。

    关注点划分

    根据前面提的,可以划分下关注点。

    主关注点

    侧关注点

    业务逻辑(doSomething)

    埋点信息 logger

    前面提到AOP关注的是步骤具体到例子来说其实就是插入logger的步骤。

    插入时机无非时业务逻辑执行之前或者之后的阶段。

    具体实现起来也不那么困难

    实现思路

    具体到js来说,由于语言本身的特性,天生就具有运行时动态插入逻辑的能力。

    重点在于在原函数上增加其他功能并不改变函数本身。

    毕竟函数可以接受一切形式的参数,当然函数也不例外了。

    当传入一个函数的时候,我们要对其操作的余地就很大了,

    保存原函数,然后利用后续参数加上call或apply,就可以达到我们的目的。

    此外为了给函数都增加一个属性,我们在原型上操作就行了。

    经典before或者after的实现

    网上太多类似实现了,直接看代码好了:

    // action 即为我们的侧关注点,即logger

    Function.prototype.after = function (action) {

    //保留当前函数,这里this指向运行函数即clickHandler

    var func = this;

    // return 被包装过的函数,这里就可以执行其他功能了。

    // 并且该方法挂在Function.prototype上,

    // 被返回的函数依然具有after属性,可以链式调用

    return function () {

    // 原函数执行,这里不考虑异步

    var result = func.apply(this, arguments);

    // 执行之后的操作

    action.apply(this,arguments);

    // 将执行结果返回

    return result;

    };

    };

    // before 实现类似,只不过执行顺序差别而已

    Function.prototype.before = function (action) {

    var func = this;

    return function () {

    action.apply(this,arguments);

    return func.apply(this, arguments);

    };

    };

    那么我们使用AOP改造之后的代码就如下了:

    const doSomething = ()=>{

    console.log('doSomething')

    }

    let clickHandler = ()=>{

    // n行代码

    doSomething()

    //n 行代码

    }

    clickHandler = clickHandler.before(()=>{

    logger('doSomething之前')

    }).after(()=>{

    logger('doSomething之后')

    })

    clickHandler() // 执行结果和预期一致

    到这里就实现了面向切面编程,我们的业务逻辑里面只管业务本身,侧关注点通过这种方式来动态引入,与主逻辑解耦,更加纯净、易于维护。

    结束语

    到这里,简单的AOP就介绍完成了。利用这种模式结合我们js本身的特性,可以尝试更多的可能。

    例如我们react中常见的HOC、es7的装饰者模式、HOF等,很多时候不得不感叹大牛们思想的精髓,会让我们有种顿悟的感觉。本文抛砖引玉,共同学习啦,对自己是总结和提高,更希望能帮助到需要的小伙伴。更多文章请移步我的博客

    参考文章

    展开全文
  • AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度...

    一、简述

    1、AOP的概念

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    2、项目场景

    项目开发过程中,可能会有这样的需求,需要我们在方法执行完成后,记录日志(后台开发中比较常见~),或是计算这个方法的执行时间,在不使用AOP的情况下,我们可以在方法最后调用另一个专门记录日志的方法,或是在方法体的首尾分别获取时间,然后通过计算时间差来计算整个方法执行所消耗的时间,这样也可以完成需求。那如果不只一个方法要这么玩怎么办?每个方法都写上一段相同的代码吗?后期处理逻辑变了要怎么办?最后老板说这功能不要了我们还得一个个删除?

    很明显,这是不可能的,这种问题我们完全可以用AOP来解决,不就是在方法前和方法后插入一段代码吗?AOP分分钟搞定。

    3、AOP的实现方式

    要注意了,AOP仅仅只是个概念,实现它的方式(工具和库)有以下几种:

    • AspectJ: 一个 JavaTM 语言的面向切面编程的无缝扩展(适用Android)。
    • Javassist for Android: 用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。
    • DexMaker: Dalvik 虚拟机上,在编译期或者运行时生成代码的 Java API。
    • ASMDEX: 一个类似 ASM 的字节码操作库,运行在Android平台,操作Dex字节码。

    本篇的主角就是AspectJ,下面就来看看AspectJ方式的AOP如何在Android开发中进行使用吧。

    二、AspectJ的引入

    本篇只介绍Android Studio如何引入AspectJ,Android Studio需要在app模块的build.gradle文件中引入,总共分为3个步骤:

    1)添加核心依赖

    dependencies {
        ...
        compile 'org.aspectj:aspectjrt:1.8.9'
    }

    2)编写gradle编译脚本

    buildscript {
        repositories {
            mavenCentral()  //AspectJ需要依赖maven仓库。
        }
        dependencies {
            classpath 'org.aspectj:aspectjtools:1.8.9'
            classpath 'org.aspectj:aspectjweaver:1.8.9'
        }
    }

    3)添加gradle任务

    dependencies {
        ...
    }
    // 贴上面那段没用的代码是为了说明:下面的任务代码与dependencies同级
    
    import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
    final def log = project.logger
    final def variants = project.android.applicationVariants
    
    variants.all { variant ->
        if (!variant.buildType.isDebuggable()) {
            log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
            return;
        }
    
        JavaCompile javaCompile = variant.javaCompile
        javaCompile.doLast {
            String[] args = ["-showWeaveInfo",
                             "-1.8",
                             "-inpath", javaCompile.destinationDir.toString(),
                             "-aspectpath", javaCompile.classpath.asPath,
                             "-d", javaCompile.destinationDir.toString(),
                             "-classpath", javaCompile.classpath.asPath,
                             "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
            log.debug "ajc args: " + Arrays.toString(args)
    
            MessageHandler handler = new MessageHandler(true);
            new Main().run(args, handler);
            for (IMessage message : handler.getMessages(null, true)) {
                switch (message.getKind()) {
                    case IMessage.ABORT:
                    case IMessage.ERROR:
                    case IMessage.FAIL:
                        log.error message.message, message.thrown
                        break;
                    case IMessage.WARNING:
                        log.warn message.message, message.thrown
                        break;
                    case IMessage.INFO:
                        log.info message.message, message.thrown
                        break;
                    case IMessage.DEBUG:
                        log.debug message.message, message.thrown
                        break;
                }
            }
        }
    }

    三、AOP的基本知识

    在使用AspectJ之前,还是需要先介绍下AOP的基本知识

    1、AOP术语

    1. 通知、增强处理(Advice):就是你想要的功能,也就是上面说的日志、耗时计算等。
    2. 连接点(JoinPoint):允许你通知(Advice)的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点(spring只支持方法连接点)。AspectJ还可以让你在构造器或属性注入时都行,不过一般情况下不会这么做,只要记住,和方法有关的前前后后都是连接点。
    3. 切入点(Pointcut):上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
    4. 切面(Aspect):切面是通知和切入点的结合。现在发现了吧,没连接点什么事,连接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过before,after,around等AOP注解就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
    5. 织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程。

    2、AOP注解与使用

    • @Aspect:声明切面,标记类
    • @Pointcut(切点表达式):定义切点,标记方法
    • @Before(切点表达式):前置通知,切点之前执行
    • @Around(切点表达式):环绕通知,切点前后执行
    • @After(切点表达式):后置通知,切点之后执行
    • @AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
    • @AfterThrowing(切点表达式):异常通知,切点抛出异常时执行

    @Pointcut、@Before、@Around、@After、@AfterReturning、@AfterThrowing需要在切面类中使用,即在使用@Aspect的类中。

    1)切点表达式是什么?

    这就是切点表达式:execution (* com.lqr..*.*(..))。切点表达式的组成如下:

    execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)

    除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

    修饰符模式指的是public、private、protected,异常模式指的是NullPointException等。

    对于切点表达式的理解不是本篇重点,下面列出几个例子说明一下就好了:

    @Before("execution(public * *(..))")
    public void before(JoinPoint point) {
        System.out.println("CSDN_LQR");
    }
    

    匹配所有public方法,在方法执行之前打印"CSDN_LQR"。

    @Around("execution(* *to(..))")
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("CSDN");
        joinPoint.proceed();
        System.out.println("LQR");
    }

    匹配所有以"to"结尾的方法,在方法执行之前打印"CSDN",在方法执行之后打印"LQR

    @After("execution(* com.lqr..*to(..))")
    public void after(JoinPoint point) {
        System.out.println("CSDN_LQR");
    }

    匹配com.lqr包下及其子包中以"to"结尾的方法,在方法执行之后打印"CSDN_LQR"。

    @AfterReturning("execution(int com.lqr.*(..))")
    public void afterReturning(JoinPoint point, Object returnValue) {
        System.out.println("CSDN_LQR");
    }
    

    匹配com.lqr包下所有返回类型是int的方法,在方法返回结果之后打印"CSDN_LQR"。

    @AfterThrowing(value = "execution(* com.lqr..*(..))", throwing = "ex")
    public void afterThrowing(Throwable ex) {
        System.out.println("ex = " + ex.getMessage());
    }
    

    匹配com.lqr包及其子包中的所有方法,当方法抛出异常时,打印"ex = 报错信息"。

    2)@Pointcut的使用

    @Pointcut是专门用来定义切点的,让切点表达式可以复用。

    你可能需要在切点执行之前和切点报出异常时做些动作(如:出错时记录日志),可以这么做:

    @Before("execution(* com.lqr..*(..))")
    public void before(JoinPoint point) {
        System.out.println("CSDN_LQR");
    }
    
    @AfterThrowing(value = "execution(* com.lqr..*(..))", throwing = "ex")
    public void afterThrowing(Throwable ex) {
        System.out.println("记录日志");
    }
    

    可以看到,表达式是一样的,那要怎么重用这个表达式呢?这就需要用到@Pointcut注解了,@Pointcut注解是注解在一个空方法上的,如:

    @Pointcut("execution(* com.lqr..*(..))")
    public void pointcut() {}
    

    这时,"pointcut()"就等价于"execution(* com.lqr..*(..))",那么上面的代码就可以这么改了:

    @Before("pointcut()")
    public void before(JoinPoint point) {
        System.out.println("CSDN_LQR");
    }
    
    @AfterThrowing(value = "pointcut()", throwing = "ex")
    public void afterThrowing(Throwable ex) {
        System.out.println("记录日志");
    }

    四、实战

    经过上面的学习,下面是时候实战一下了,这里我们来一个简单的例子。

    1、切点

    这是界面上一个按钮的点击事件,就是一个简单的方法而已,我们拿它来试刀。

    public void test(View view) {
        System.out.println("Hello, I am CSDN_LQR");
    }

    2、切面类

    要织入一段代码到目标类方法的前前后后,必须要有一个切面类,下面就是切面类的代码:

    @Aspect
    public class TestAnnoAspect {
    
        @Pointcut("execution(* com.lqr.androidaopdemo.MainActivity.test(..))")
        public void pointcut() {
    
        }    
    
        @Before("pointcut()")
        public void before(JoinPoint point) {
            System.out.println("@Before");
        }
    
        @Around("pointcut()")
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("@Around");
        }
    
        @After("pointcut()")
        public void after(JoinPoint point) {
            System.out.println("@After");
        }
    
        @AfterReturning("pointcut()")
        public void afterReturning(JoinPoint point, Object returnValue) {
            System.out.println("@AfterReturning");
        }
    
        @AfterThrowing(value = "pointcut()", throwing = "ex")
        public void afterThrowing(Throwable ex) {
            System.out.println("@afterThrowing");
            System.out.println("ex = " + ex.getMessage());
        }
    }

    3、各通知的执行结果

    先来试试看,这几个注解的执行结果如何。

    不对啊,按钮的点击事件中有打印"Hello, I am CSDN_LQR"的,这里没有,怎么肥事?

    这里因为@Around环绕通知会拦截原方法内容的执行,我们需要手动放行才可以。

    代码修改如下:

    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("@Around");
        joinPoint.proceed();// 目标方法执行完毕
    }

    也不对啊,少了一个@AfterThrowing通知。这个通知只有在切点抛出异常时才会执行,我们可以让代码出现一个简单的运行时异常:

    public void test(View view) {
        System.out.println("Hello, I am CSDN_LQR");
        int a = 1 / 0;
    }
    

    这下@AfterThrowing通知确实被调用了,而且也打印出了错误信息(divide by zero)。但@AfterReturning通知反而不执行了,原因很简单,都抛出异常了,切点肯定是不能返回结果的。也就是说:@AfterThrowing通知与@AfterReturning通知是冲突的,在同个切点上不可能同时出现。

    4、方法耗时计算的实现

    因为@Around是环绕通知,可以在切点的前后分别执行一些操作,AspectJ为了能肯定操作是在切点前还是在切点后,所以在@Around通知中需要手动执行joinPoint.proceed()来确定切点已经执行,故在joinPoint.proceed()之前的代码会在切点执行前执行,在joinPoint.proceed()之后的代码会切点执行后执行。于是,方法耗时计算的实现就是这么简单:

    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = SystemClock.currentThreadTimeMillis();
        joinPoint.proceed();
        long endTime = SystemClock.currentThreadTimeMillis();
        long dx = endTime - beginTime;
        System.out.println("耗时:" + dx + "ms");
    }

    5、JoinPoint的作用

    发现没有,上面所有的通知都会至少携带一个JointPoint参数,这个参数包含了切点的所有信息,下面就结合按钮的点击事件方法test()来解释joinPoint能获取到的方法信息有哪些:

    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    String name = signature.getName(); // 方法名:test
    Method method = signature.getMethod(); // 方法:public void com.lqr.androidaopdemo.MainActivity.test(android.view.View)
    Class returnType = signature.getReturnType(); // 返回值类型:void
    Class declaringType = signature.getDeclaringType(); // 方法所在类名:MainActivity
    String[] parameterNames = signature.getParameterNames(); // 参数名:view
    Class[] parameterTypes = signature.getParameterTypes(); // 参数类型:View

    6、注解切点

    前面的切点表达式结构是这样的:

    execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
    

    但实际上,上面的切点表达式结构并不完整,应该是这样的:

    execution(<@注解类型模式>? <修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
    

    这就意味着,切点可以用注解来标记了。

    1)自定义注解

    如果用注解来标记切点,一般会使用自定义注解,方便我们拓展。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnnoTrace {
        String value();
        int type();
    }
    • @Target(ElementType.METHOD):表示该注解只能注解在方法上。如果想类和方法都可以用,那可以这么写:@Target({ElementType.METHOD,ElementType.TYPE}),依此类推。
    • @Retention(RetentionPolicy.RUNTIME):表示该注解在程序运行时是可见的(还有SOURCE、CLASS分别指定注解对于那个级别是可见的,一般都是用RUNTIME)。

    其中的value和type是自己拓展的属性,方便存储一些额外的信息。

    这个自定义注解只能注解在方法上(构造方法除外,构造方法也叫构造器,需要使用ElementType.CONSTRUCTOR),像平常使用其它注解一样使用它即可:

    @TestAnnoTrace(value = "lqr_test", type = 1)
    public void test(View view) {
        System.out.println("Hello, I am CSDN_LQR");
    }

    3)注解的切点表达式

    既然用注解来标记切点,那么切点表达式肯定是有所不同的,要这么写:

    @Pointcut("execution(@com.lqr.androidaopdemo.TestAnnoTrace * *(..))")
    public void pointcut() {}
    

    切点表达式使用注解,一定是@+注解全路径,如:@com.lqr.androidaopdemo.TestAnnoTrace。

    4)获取注解属性值

    上面在编写自定义注解时就声明了两个属性,分别是value和type,而且在使用该注解时也都为之赋值了,那怎么在通知中获取这两个属性值呢?还记得JoinPoint这个参数吧,它就可以获取到注解中的属性值,如下所示:

    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    // 通过Method对象得到切点上的注解
    TestAnnoTrace annotation = method.getAnnotation(TestAnnoTrace.class);
    String value = annotation.value();
    int type = annotation.type();

    最后贴下Demo地址

    https://github.com/GitLqr/AndroidAopDemo

     

    展开全文
  • 1. 面向切面编程 以下内容来自百度百科: 定义:面向切面编程(AOP,Aspect Oriented Programming)是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 作用:利用AOP可以对业务逻辑的各个...
  • 1. pom 引入aop jarorg.springframework.bootspring-boot... 定义切面 Aspect@Aspect@Component // 这句不能少public class TestAspect {private Logger logger = Logger.getLogger(getClass());@Pointcut("executio...
  • 1.aop全称Aspect Oriented Programming 面向切面编程2.aop应用场景场景一: 记录日志场景二: 监控方法运行时间 (监控性能)场景三: 权限控制场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第...
  • 前面两篇文章记录了 Spring IOC 的相关知识,本文记录 Spring 中的另一特性 AOP 相关知识。...AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。
  • AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现在不修改源代码的情况下,给程序动态统一添加功能的一种技术,可以理解成动态代理。是Spring框架中的一个重要内容。利用 ...
  • Aspectj表示切面执行时间,用的通知(Advice)。 这个通知可以使用注解表示。 5个注解表示切面的5个执行时间, 这些注解叫做通知注解。 @Before : 前置通知 @AfterRetunring: 后置通知 @Around: 环绕通知 @...
  • AOP(Aspect Orient Programming),也就是 面向切面编程,AOP 是一种编程思想,是OOP(面向对象编程)的一种补充,面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。 为什么需要AOP ...
  • (1)本篇博客的代码,基于【Spring AOP面向切面编程2:初识AOP二:AOP初体验;(一个Spring AOP的案例,走了一遍流程)】中的代码; (2)如在【Spring AOP面向切面编程2:初识AOP二:AOP初体验;(一个Spring AOP...
  • 相比于面向切面编程,我更喜欢把AOP叫做方法增强,我用一幅图来解释下。 一句话概述就是在不影响原方法的情况下对其进行代码方法的补充、增强。 Spring是如何实现AOP的 说到Spring的AOP就要讲到Java的动态代理...
  • 学习目的:学会使用注解进行面向切面编程(AOP),实现在面向切面编程(AOP)中,使用XML配置完成的操作。Part 1修改cn.vaefun.dao.UserServiceImpl.java,在类上添加Component注解,告诉spring这是一个bean,并命名为...
  • //从切面织入点处通过反射机制获取织入点处的方法 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //获取切入点所在的方法 Method method = signature.getMethod(); //获取操作 MyLog ...
  • spring的AOP(面向切面编程) 切面编程,就是在你项目原有的功能基础上,通过AOP去添加新的功能,这些功能是建立在原有功能的基础上的,并且不修改任何原来功能的代码。 例如:你先打车去银行 > 进入银行大门 &...
  • 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码...
  • 在进入实例之前,可以先看我引用的另一篇文章:一.... 创建AOP切面类,直接上代码这里为了简单明了,我将切面类分为普通类方法切面和自定义注解方法切面1.普通类方法切面类package com.example.demo.aop;import l...
  • Aspectj表示切面执行时间,用的通知(Advice)。 这个通知可以使用注解表示。 5个注解表示切面的5个执行时间, 这些注解叫做通知注解。 @Before : 前置通知 @AfterRetunring: 后置通知 @Around: 环绕通知 @...
  • 参考博客: 大神的博客,点赞请去大神贴,本人仅做参考。 SpringBoot中的AOP处理1 理解AOP1.1 什么是AOP1.2 AOP体系与概念2 AOP实例2.1 第一个实例2.2 第...AOP(Aspect Oriented Programming),面向切面思想,是Sprin
  • } 三、定义注解对应的Aspect类 在类名上加“@Aspect”注解,使其变成切面类 定义个方法1定义切入点规则,方法名随意,添加注解“@PointCut("execution(切入点的位置)")”注解 定义个方法2作为通知,方法名随意,...
  • 1.切面类的实现 切面类都是通过实现各种特定的接口来实现的,缺点是必须要实现特定的接口,且一个类基本上只有一个重写方法是有用的。 对于普通的类,若不实现特定的接口,可以通过可以通过注解转化为切面类(通知类...
  • 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个...
  • 一、基本介绍1,什么是 AOP(1)AOP为 Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。(2)利用 AOP可以对业务逻辑的各个部分进行隔离,...
  • Spring(五)——【面向切面编程AOP】

    千次阅读 2021-10-23 09:55:28
    目录 Spring的AOP简介 aop是什么? AOP的作用及其优势 AOP的底层实现 JDK的动态代理 cglib 的动态代理 ...AOP为Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运
  • 理解面向切面编程

    2021-10-17 19:46:00
    最近在工作中碰到一个需求,需要针对用户的文章列表,评论列表,好友列表,进行...很早之前就学了动态代理和面向切面编程的知识,各种专业名词也熟记于胸,但是没有实际应用就没有深刻的体会,通过这个需求的实践,对
  • 这里写自定义目录标题1. AOP实例1.1 第一个实例1.2第二个实例2.AOP相关注解2.1 @Pointcut2.2 @...有需要可以看上一篇文章自学很好的例子Spring AOP——Spring 中面向切面编程(一) 1. AOP实例 使用 AOP,首先需要引入
  • springboot中面向切面编程(AOP)

    千次阅读 2021-02-26 22:52:47
    1.什么是AOP AOP就是通过动态为项目中某些类在运行过程中动态创建代理对象,在对象中完成附加...4.切面(Aspect)——封装了通知和切入点用来横向插入的类 5.代理(Proxy)——将通知应用到目标类动态创建的对象 6.织入(We
  • 想象一段自顶而下的程序,在这一段程序中的某个方法想要在执行后打印出日志,这时候使用面向切面编程的思想就可以很好的解决这个问题,我们可以通过代理模式来实现我们的打印日志功能,因为代理模式就是面向切面...
  • AOP面向切面编程 aop是将共有的代码抽象出来,切入需要的类中,减少冗余代码,提高代码复用性。主要应用场景为事务管理(调用方法前开启事务, 调用方法后提交关闭事务 )、记录日志等,底层实现主要是动态代理,aop...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 83,941
精华内容 33,576
关键字:

面向切面编程

友情链接: 非负矩阵分解.zip