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的领跑者

  • 相关阅读:
    Internet上的音频/视频概述
    防火墙
    数据链路层安全
    两类密码体制
    Windows Terminal 美化分享
    2019.11.14 启用了FlagCounter
    检测一个App是不是有UWP血统
    UWP 记一次x64平台无法单步调试的bug
    UWP 使用FontIcon
    Git和Github简单教程
  • 原文地址:https://www.cnblogs.com/junzi2099/p/8275116.html
Copyright © 2011-2022 走看看