Java AOP(面向切面编程)

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

1、到底什么是AOP(面向切面编程)?

无论在学习或者面试的时候,大家都会张口说spring的特性AOP和IOC(控制反转),举个例子给大家说明一下:

有A,B,C三个方法,但是在调用每一个方法之前,要求打印一个日志:某一个方法被开始调用了!

在调用每个方法之后,也要求打印日志:某个方法被调用完了!

一般人会在每一个方法的开始和结尾部分都会添加一句日志打印吧,这样做如果方法多了,就会有很多重复的代码,显得很麻烦,这时候有人会想到,为什么不把打印日志这个功能封装一下,然后让它能在指定的地方(比如执行方法前,或者执行方法后)自动的去调用呢?如果可以的话,业务功能代码中就不会掺杂这一下其他的代码,所以AOP就是做了这一类的工作,比如,日志输出,事务控制,异常的处理等。

如果把AOP当做成给我们写的“业务功能”增添一些特效,就会有这么几个问题:

  • 我们要制作哪些特效
  • 这些特效使用在什么地方
  • 这些特效什么时候来使用

2、AOP术语

  • 2.1 通知(Advice)
      就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好,然后在想用的地方调用一下。
    1)前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
    2)后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
    3)异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
    4)最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
    5)环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

  • 2.2 连接点(JoinPoint)
      这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前、后(两者都有也行)以及抛出异常时都可以是连接点。
    spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行。

  • 2.3 切入点(Pointcut)
      上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

  • ** 2.4 切面(Aspect)**
      切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before、after、around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。

  • 2.5 引入(introduction)
      允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗

  • 2.6 目标(target)
      引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

  • 2.7 代理(proxy)
      怎么实现整套aop机制的,都是通过代理,这个一会给细说。

  • 2.8 织入(weaving)
      把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时?后面解释。

关键就是:切点定义了哪些连接点会得到通知

3、AOP 编码流程

3.1 导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.6.RELEASE</version>
</dependency>

3.2 准备Target

public class UserServiceImpl implements UserService{
    private UserDAO userDAO;
    // set/get...
    @Override
    public void updateUser(User user) {
        System.out.println("update in service==========");
        userDAO.updateUser(user);
    }

    @Override
    public void insertUser(User user) {
        System.out.println("insert in service===============");
        userDAO.insertUser(user);
    }
}

3.3 准备Advice

public class MyBeforeAdvice implements MethodBeforeAdvice{
    //环绕额外功能
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        System.out.println("环绕前.....");

        Object ret = invocation.proceed();   //执行目标业务方法

        System.out.println("环绕后");
        return ret;
    }
    //后置额外功能
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        Date date = new Date();
        SimpleDateFormat sd = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
        String da = sd.format(date);
        System.out.println("于"+da+"之后 切入额外功能...");
    }
    /**
     * 前置额外功能
     * 
     * @param method 当前执行的方法
     * @param args   当前执行的方法中的参数
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("before~~~");
    }
}

异常额外功能

public class MyThrows implements ThrowsAdvice{
    //目标业务方法中抛出异常时,执行此方法。ex=抛出的异常对象
    public void afterThrowing(Exception ex){
        System.out.println(ex.getMessage()+"~~~");
    }
}

3.4 编制Weave

所谓编织,即,将Target 和 Advice 组装 形成代理。
当然组装过程由spring管理,开发者只需要做出配置,告知spring需要组装谁即可。

<!-- 声明 Target + Advice -->
<!-- 声明 Target -->
<bean id="userService" class="com.zhj.service.UserServiceImpl">
    <!-- 为userDAO属性赋值,值为id=“userDAO”的组件 -->
    <property name="userDAO" ref="userDAO"/>
</bean>

<!-- Advice -->
<bean id="myBefore" class="com.zhj.advice.MyBeforeAdvice"/>

<!-- 编织 配置 -->
<aop:config>
         <!-- ref="引入MyAdvice" -->
          <aop:aspect ref="myAdvice">
        <!-- 切入点=pointcut
            execution()表达式:描述切入位置
            组成:修饰符  返回值  包  类  方法名  参数表-->
            
        <aop:pointcut id="pc" expression="execution(* com.service.UserServiceImpl.queryUser(..))"/>
     
        <aop:advisor advice-ref="myBefore" pointcut-ref="pc"/>
    </aop:aspect>
</aop:config>

3.5 切入点表达式

3.5.1 execution

1> * com.service.UserServiceImpl.queryUser(..)
    修饰符:任意
    返回值:任意
    包:com.service
    类:UserServiceImpl
    方法:queryUser
    参数表:任意
2> * com.service.UserServiceImpl.*(..)
    修饰符:任意
    返回值:任意
    包:com.service
    类:UserServiceImpl
    方法:所有,任意
    参数表:任意
3> * com..UserServiceImpl.*(..)
    修饰符:任意
    返回值:任意
    包:com包,及其子包
    类:UserServiceImpl
    方法:所有,任意
    参数表:任意
4> * com.service.*.*(..)
    修饰符:任意
    返回值:任意
    包:com.service
    类:所有,任意
    方法:所有,任意
    参数表:任意
5> * *(..)    不建议
    修饰符:任意
    返回值:任意
    包:任意
    类:所有,任意
    方法:所有,任意
    参数表:任意
6> * com.service.UserServiceImpl.query*(..)  【技巧:批量切入】
    修饰符:任意
    返回值:任意
    包:com.service
    类:UserServiceImpl
    方法:所有,任意
    参数表:任意
  *注意:尽量精确,避免不必要的切入

3.5.2 within

描述包和类,类中所有方法都切入,示例:

`within(com.service.UserServiceImpl) 类中的所有方法

within(com…UserServiceImpl) com包和com子包下的类中的所有方法

<aop:pointcut id=“pc” expression=“within(com…UserServiceImpl)”/>

3.5.3 args

描述参数表,符合的方法都切入。

args(int,String,com.entity.User) 参数表如此的方法

<aop:pointcut id=“pc” expression=“args(int,String,com.entity.User)”/>

4、SpringBoot AOP使用

SpringBoot AOP使用

参考文档:

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