zoukankan      html  css  js  c++  java
  • AspectJ AOP介绍

    idea下aspectj程序运行示例

    有些同学可能想自己编写aspect程序进行测试练习,博主在这简单介绍运行环境的搭建,首先博主使用的idea的IDE,因此只对idea进行介绍。首先通过maven仓库下载工具包aspectjtools-1.8.9.jar,该工具包包含ajc核心编译器,然后打开idea检查是否已安装aspectJ的插件:

    配置项目使用ajc编译器(替换javac)如下图:

    如果使用maven开发(否则在libs目录自行引入jar)则在pom文件中添加aspectJ的核心依赖包,包含了AspectJ运行时的核心库文件:

    <dependency>
        <groupId>aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>1.8.10</version>
    </dependency>
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.6.12</version>
    </dependency>
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.7.4</version>
    </dependency>

    新建文件处创建aspectJ文件,然后就可以像运行java文件一样,操作aspect文件了。

    这里先进行一个简单案例的演示。编写一个HelloWord的类,然后利用AspectJ技术切入该类的执行过程。

    /**
     * Created by zejian on 2017/2/15. */
    public class HelloWord {
    
        public void sayHello(){
            System.out.println("hello world !");
        }
        public static void main(String args[]){
            HelloWord helloWord =new HelloWord();
            helloWord.sayHello();
        }
    } 

    编写AspectJ类,注意关键字为aspect(MyAspectJDemo.aj,其中aj为AspectJ的后缀),含义与class相同,即定义一个AspectJ的类

    /**
     * Created by zejian on 2017/2/15.
     * 切面类
     */
    public aspect MyAspectJDemo {
        /**
         * 定义切点,日志记录切点
         */
        pointcut recordLog():call(* HelloWord.sayHello(..));
    
        /**
         * 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
         */
        pointcut authCheck():call(* HelloWord.sayHello(..));
    
        /**
         * 定义前置通知!
         */
        before():authCheck(){
            System.out.println("sayHello方法执行前验证权限");
        }
    
        /**
         * 定义后置通知
         */
        after():recordLog(){
            System.out.println("sayHello方法执行后记录日志");
        }
    } 

    ok~,运行helloworld的main函数:

    对于结果不必太惊讶,完全是意料之中。我们发现,明明只运行了main函数,却在sayHello函数运行前后分别进行了权限验证和日志记录,事实上这就是AspectJ的功劳了。

    AOP中抽象概念(切入点(pointcut)、通知(advice)、切面(aspect)、织入(weaving))解释

    对aspectJ有了感性的认识后,再来聊聊aspectJ到底是什么?AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易。

    在案例中,我们使用aspect关键字定义了一个类,这个类就是一个切面,它可以是单独的日志切面(功能),也可以是权限切面或者其他,在切面内部使用了pointcut定义了两个切点,一个用于权限验证,一个用于日志记录,而所谓的切点就是那些需要应用切面的方法,如需要在sayHello方法执行前后进行权限验证和日志记录,那么就需要捕捉该方法,而pointcut就是定义这些需要捕捉的方法(常常是不止一个方法的),这些方法也称为目标方法,最后还定义了两个通知,通知就是那些需要在目标方法前后执行的函数,如before()即前置通知在目标方法之前执行,即在sayHello()方法执行前进行权限验证,另一个是after()即后置通知,在sayHello()之后执行,如进行日志记录。到这里也就可以确定,切面就是切点和通知的组合体,组成一个单独的结构供后续使用,下图协助理解。

    这里简单说明一下切点的定义语法:关键字为pointcut,定义切点,后面跟着函数名称,最后编写匹配表达式,此时函数一般使用call()或者execution()进行匹配,这里我们统一使用call()

    pointcut 函数名 : 匹配表达式

    案例:recordLog()是函数名称,自定义的,* 表示任意返回值,接着就是需要拦截的目标函数,sayHello(..)的..,表示任意参数类型。这里理解即可,后面Spring AOP会有关于切点表达式的分析,整行代码的意思是使用pointcut定义一个名为recordLog的切点函数,其需要拦截的(切入)的目标方法是HelloWord类下的sayHello方法,参数不限。

    pointcut recordLog():call(* HelloWord.sayHello(..)); 

    关于定义通知的语法:首先通知有5种类型分别如下:

    • before 目标方法执行前执行,前置通知
    • after 目标方法执行后执行,后置通知
    • after returning 目标方法返回时执行 ,后置返回通知
    • after throwing 目标方法抛出异常时执行 异常通知
    • around 在目标函数执行中执行,可控制目标函数是否执行,环绕通知

    语法:

    [返回值类型] 通知函数名称(参数) [returning/throwing 表达式]:连接点函数(切点函数){ 
    函数体 
    }

    案例如下,其中要注意around通知即环绕通知,可以通过proceed()方法控制目标函数是否执行。

    /**
      * 定义前置通知
      *
      * before(参数):连接点函数{
      *     函数体
      * }
      */
     before():authCheck(){
         System.out.println("sayHello方法执行前验证权限");
     }
    
     /**
      * 定义后置通知
      * after(参数):连接点函数{
      *     函数体
      * }
      */
     after():recordLog(){
         System.out.println("sayHello方法执行后记录日志");
     }
    
    
     /**
      * 定义后置通知带返回值
      * after(参数)returning(返回值类型):连接点函数{
      *     函数体
      * }
      */
     after()returning(int x): get(){
         System.out.println("返回值为:"+x);
     }
    
     /**
      * 异常通知
      * after(参数) throwing(返回值类型):连接点函数{
      *     函数体
      * }
      */
     after() throwing(Exception e):sayHello2(){
         System.out.println("抛出异常:"+e.toString());
     }
    
    
    
     /**
      * 环绕通知 可通过proceed()控制目标函数是否执行
      * Object around(参数):连接点函数{
      *     函数体
      *     Object result=proceed();//执行目标函数
      *     return result;
      * }
      */
     Object around():aroundAdvice(){
         System.out.println("sayAround 执行前执行");
         Object result=proceed();//执行目标函数
         System.out.println("sayAround 执行后执行");
         return result;
     } 

    切入点(pointcut)和通知(advice)的概念已比较清晰,而切面则是定义切入点和通知的组合如上述使用aspect关键字定义的MyAspectJDemo,把切面应用到目标函数的过程称为织入(weaving)。在前面定义的HelloWord类中除了sayHello函数外,还有main函数,以后可能还会定义其他函数,而这些函数都可以称为目标函数,也就是说这些函数执行前后也都可以切入通知的代码,这些目标函数统称为连接点,切入点(pointcut)的定义正是从这些连接点中过滤出来的,下图协助理解。

    AspectJ的织入方式及其原理概要

    经过前面的简单介绍,我们已初步掌握了AspectJ的一些语法和概念,但这样仍然是不够的,我们仍需要了解AspectJ应用到java代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程。对于这个过程,一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术,这里主要重点分析一下静态织入,ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

    关于ajc编译器,是一种能够识别aspect语法的编译器,它是采用java语言编写的,由于javac并不能识别aspect语法,便有了ajc编译器,注意ajc编译器也可编译java文件。为了更直观了解aspect的织入方式,我们打开前面案例中已编译完成的HelloWord.class文件,反编译后的java代码如下:

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package com.zejian.demo;
    
    import com.zejian.demo.MyAspectJDemo;
    //编译后织入aspect类的HelloWord字节码反编译类
    public class HelloWord {
        public HelloWord() {
        }
    
        public void sayHello() {
            System.out.println("hello world !");
        }
    
        public static void main(String[] args) {
            HelloWord helloWord = new HelloWord();
            HelloWord var10000 = helloWord;
    
       try {
            //MyAspectJDemo 切面类的前置通知织入
            MyAspectJDemo.aspectOf().ajc$before$com_zejian_demo_MyAspectJDemo$1$22c5541();
            //目标类函数的调用
               var10000.sayHello();
            } catch (Throwable var3) {
            MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574();
                throw var3;
            }
    
            //MyAspectJDemo 切面类的后置通知织入 
            MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574();
        }
    } 

    显然AspectJ的织入原理已很明朗了,当然除了编译期织入,还存在链接期(编译后)织入,即将aspect类和java目标类同时编译成字节码文件后,再进行织入处理,这种方式比较有助于已编译好的第三方jar和Class文件进行织入操作,由于这不是本篇的重点,暂且不过多分析。

    参考资料

    http://blog.csdn.net/javazejian/article/details/56267036#神一样的aspectj-aop的领跑者

  • 相关阅读:
    rest framework 认证 权限 频率
    rest framework 视图,路由
    rest framework 序列化
    10.3 Vue 路由系统
    10.4 Vue 父子传值
    10.2 Vue 环境安装
    10.1 ES6 的新增特性以及简单语法
    Django 跨域请求处理
    20190827 On Java8 第十四章 流式编程
    20190825 On Java8 第十三章 函数式编程
  • 原文地址:https://www.cnblogs.com/junzi2099/p/8275116.html
Copyright © 2011-2022 走看看