Spring AOP学习及应用

Spring 模块


Spring AOP学习及应用_第1张图片
Spring框架由6个不同的模块组成


Spring 核心容器

容器是Spring框架最核心的部分,它管理着Spring应用中bean的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为Spring提供了DI的功能。基于bean工厂,还有多种Spring应用上下文的实现,每一种都提供了配置Spring的不同方式。

该模块也提供了许多企业服务,例如 E-mail、JNDI、EJB集成和调度。

所有的Spring模块都构建于核心容器之上。当配置应用时,其实隐式地使用了这些类。

Spring的AOP模块

在AOP模块中,Spring对面向切面编程提供了丰富的支持。这个模块是Spring应用系统中开发切面的基础。与DI一样,AOP可以帮助应用对象解耦。借助于AOP,可以将遍布系统的关注点(例如事务和安全)从它们所应用的对象中解耦出来。

数据访问与集成

Spring的JDBC和DAO(Data Access Object)模块抽象了JDBC的样板式代码(例如获取数据库连接、创建语句、关闭连接等),代码简单明了。在多种数据库服务的错误信息之上构建了一个语义丰富的异常层,SQL错误信息相对明确。

ORM(Object-Relational Mapping),Spring的ORM模块建立在对DAO的支持之上,并为多个ORM框架提供了一种构建DAO的简便方式。Spring对许多流行的ORM框架进行了集成,包括Hibernate、Java Persisternce API、IBATIS 等。

Web与远程调用

MVC(Model-View-Controller)模式是普遍被接受的构建Web应用的方法,可以帮助用户将界面逻辑与应用逻辑分离。Spring MVC有助于在Web层提升应用的松耦合水平。

Spring远程调用功能集成了RMI(Remote Method Invocation)、Hessian等,还自带了一个远程调用框架:HTTP invoker。还提供了暴露和使用REST API的良好支持。

Instrumentation

提供了为JVM添加代理(agent)的功能。具体来讲,它为Tomcat提供了一个织入代理,能够为Tomcat传递类文件,就像这些文件是被类加载器加载的一样。使用场景非常有限。。

1 AOP简介

定义:Aspect Oriented Programming 面向切面编程

目的

把核心业务和非核心业务进行分离

把多个非核心业务处理(点)变成一个切面(之前的非核心业务代码需要写多次变成只需要写一次。)

要求:只有当核心类中有多个业务需要进行处理的时候使用AOP较为合理,如果只有一个业务需要被处理那么就没有必要使用AOP了。

1.1 什么是面向切面编程

  面向切面编程(aspect-oriented programming, AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。

切面能帮助我们模块化横切关注点。简而言之,横切关注点可以被描述为影响应用多处的功能。例如,安全就是一个横切关注点,应用中的许多方法都会涉及到安全规则。

图1.1-1直观呈现了横切关注点的概念。


Spring AOP学习及应用_第2张图片
图1.1-1切面实现了横切关注点(跨多个应用对象的逻辑)的模块化


  图1.1-1展现了一个被划分为模块的典型应用。每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能,例如安全和事务管理。

如果要重用通用功能的话,最常见的面向对象技术是继承(inheritance)或委托(delegation) 但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。

切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类。

横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect) 这样做有两个好处:

首先,现在每个关注点都集中于一个地方,而不是分散到多处代码中;

其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。

我们可以把切面想象为覆盖在很多组件之上的一个外壳。可以将安全、事物和日志关注点与核心业务逻辑相分离。


Spring AOP学习及应用_第3张图片
图 1.1-2 利用AOP,系统范围内的关注点覆盖在它们所影响组件之上


1.2 AOP术语

相关概念


Spring AOP学习及应用_第4张图片
  图 1.2-1


Spring AOP学习及应用_第5张图片
 图 1.2-2 在一个或多个连接点上,可以把切面的功能(通知)织入到执行过程中

.

.通知(Advice)

当抄表员出现在我们家门口时,他们要登记用电呈并回去向电力公司报告。显然,他们必须

有一份需要抄表的住户清单,他们所汇报的信息也很重要,但记录用电量才是抄表员的主要

工作。

切面也有目标——它必须要完成的工作。在AOP术语中,切面的工作披称为通知。

描述切面要完成的工作

何时执行这个工作

在方法被调用之前?之后?

在方法之前和之后都调用?

只在方法抛出异常时调用?

通知类型:

前置通知(Before):在目标方法被调用之前调用通知功能

后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么

返回通知(After-returning):在目标方法成功执行之后调用通知

异常通知(After-throwing):在目标方法抛出异常后调用通知

坏绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。


连接点(Join point)

电力公司为多个住户提供服务,甚至可能是整个城市。毎家都有一个电表,这些电表上的数

字都需要读取,因此每家都是抄表员的潜在目标。抄表员也许能够读取各种类型的设备,但

是为了完成他的工作,他的目标应该是房屋内所安装的电表。

同样,我们的应用可能也有数以千计的时机应用通知。这些时机被称为连接点。连接点是在

应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改

一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

切点(Poincut)

如果让一位抄表员访问电力公司所服务的所有住户,那肯定是不现实的。实际上,电力公司为每一个抄表员都分别指定某一块区域的住户.类似地,一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。

如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点.我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

切面(Aspect)

当抄表员开始一天的工作时,他知道自己要做的事情(报告用电量)和从哪些房屋收集信息。因此,他知道要完成工作所需要的一切东西。

切面是通知和切点的结合。通知和切点共同定义了切面的全部内容一它是什么,在何时和何处完成其功能.

引入(Introduction)

引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简单,只需一个方法,setLastModified(Date),和一个实例变量来保存这个状态。然后,新方法和实例变量就可以被引入到现有的类中,从而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状态。

织入(Weaving)

(切面发挥效果的过程)

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。

类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入(load-time weaving, LTW)就支持以这种方式织入切面。

运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

1.3 Spring 对 AOP 的支持

Spring捉供了4种类型的AOP支持:

基于代理的经典Spring AOP

纯POJO切面

@AspectJ注解驱动的切面

注入式AspectJ切面(适用于Spring各版本》

前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此Spring对AOP的支持局限于方法拦截。

 Spring的经典AOP编程模型曾经的确非常棒。但是现在Spring提供了更简洁和干净的面向切面编程方式.引入了简单的声明式AOP和基于注解的AOP之后,Spring 经典的AOP看起来就显得非常笨重和过于复杂,直接使用ProxyFactoryBean会让人感觉厌烦。

借助Spring的aop命名空间,我们可以将纯POJO转换为切面。实际上,这些POJO只是提供了满足切点条件时所要调用的方法.遗憾的是,这种技术需要XML配置,但这的确是声明式地将对象转换为切面的简便方式。

Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。本质上,它依然是Spring基于代理的AOP,但是编程模型几乎与编写成熟的AspectJ注解切面完全一致。这种AOP风格的好处在于能够不使用XML来完成功能。

如果你的AOP需求超过了简单的方法调用(如构造器或属性拦截》,那么你需要考虑使用AspectJ来实现切面。在这种情况下,上文所示的第四种类型能够帮助你将值注入到AspectJ驱动的切面中。

了解SpringAOP框架的一些关键知识。

Spring通知是Java编写的

Spring所创建的通知都是用标准的Java类编写的。这样的话,我们就可以使用与普通Java开发一样的集成开发环境(IDE)来开发切面。而且,定义通知所应用的切点通常会使用注解或在Spring配置文件里采用XML来编写,这两种语法对于Java开发者来说都是相当熟悉的。

Spring在运行时通知对象

通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。如图1.3-1所示,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。


Spring AOP学习及应用_第6张图片
图 1.3-1 Spring的切面由包裹了目标对象的代理类实现。 代理类处理方法的调用,执行额外的切面逻辑,并调用目标方法


Spring 只支持方法级别的连接点

因为Spring基于动态代理,所以Spring只支持方法连接点。无法创建更细粒度的通知,例如拦截对象字段的修改。而且也不支持构造器连接点,无法在bean创建时应用通知。

2 通过切点选择连接点

切点用于准确定位应该在什么地方应用切面的通知。通知和切点是切面的最基本元素。因此,了解如何编写切点非常重要。

在Spring AOP中,要使用AspectJ的切点表达式语言来定义切点。

下图列出了Spring AOP所支持的AspectJ切点指示器


Spring AOP学习及应用_第7张图片
图 2-1 Spring 借助 AspectJ 的切点表达式语言来定义 Spring 切面


注意,只有 execution 指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。这说明 execution 指示器是我们在编写切点定义时最主要使用的指示器。在此基础上,我们使用其他指示器来限制所匹配的切点。

2.1 编写切点


Spring AOP学习及应用_第8张图片
图 2.1-1


使用 execution()指示器选择 HistoryService 的 getTeacherRankingList 方法,方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了权限定类名和方法名。对于方法参数列表,使用了两个点号(..)表明切点要选择任意的 getTeacherRankingList 方法,无论该方法的入参是什么。

假设我们需要配置的切点仅匹配 rankingserver 包。在此场景下,可以使用 within() 指示器来限制匹配,如图 2.1-2 所示。


图 2.1-2


注意,使用了“&&”操作符把 execution() 和 within() 指示器连接在一起形成了与(and)关系(切点必须匹配所有的指示器)。类似的,我们可以使用 ”||“操作符来标识或(or)关系,而使用 ”!“ 操作符来标识非(not)操作。

3 使用注解创建切面

3.1 定义切面

五种注解定义通知:


Spring AOP学习及应用_第9张图片
图 3.1-1 Spring 使用 AspectJ 注解来声明通知方法


每个模块接口都有自己的核心关注点,而多个模块之间会有一些辅助功能且不是核心业务。比如日志、安全等,可以称为横切关注点。因此,可以将日志收集定义为一个切面。

package com.ztjy.rankingserver.aop;

import lombok.extern.log4j.Log4j2;

import org.aspectj.lang.annotation.AfterReturning;

import org.aspectj.lang.annotation.AfterThrowing;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.springframework.stereotype.Component;


/**

 * 切面测试类1

 * 记录接口开始、结束时间

 **/

@Component

@Aspect

@Log4j2

public class LogAop1 {


    @Before("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")

    public void before(){

        log.info("接口要开始执行了---->>begin time:{}",System.currentTimeMillis());

    }


    @AfterReturning("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")

    public void after(){

        log.info("接口执行结束了---->>begin time:{}",System.currentTimeMillis());

    }


    @AfterThrowing("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")

    public void doAfterThrowing(){

        log.info("接口报错了...--->>{}",System.currentTimeMillis());

    }

}

LogAop1 类使用了 @Aspect 注解进行了标注。该注解表明 LogAop1 不仅仅是一个 POJO,还是一个切面。LogAop1 类中的方法都是用注解来定义切面的具体行为。

问题,切点表达式重复了三次。

使用 @Pointcut 注解能够在一个切面内定义可重用的切点。如下:


package com.ztjy.rankingserver.aop;

import lombok.extern.log4j.Log4j2;

import org.aspectj.lang.annotation.*;

import org.springframework.stereotype.Component;

/**

* 切面测试类2

* 记录接口开始、结束时间

 **/

@Component

@Aspect

@Log4j2

public class LogAop2 {

       @Pointcut("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")

       public void printLog(){}

          @Before("printLog()")

          public void before(){

             log.info("接口要开始执行了---->>begin time:{}",System.currentTimeMillis());

        }



       @AfterReturning("printLog()")

        public void after(){

           log.info("接口执行结束了---->>begin time:{}",System.currentTimeMillis());

        }

        @AfterThrowing("printLog()")

        public void doAfterThrowing(){

            log.info("接口报错了...--->>{}",System.currentTimeMillis());

        }

}


3.2 创建环绕通知

使用环绕通知重新实现 LogAop 切面


@Pointcut("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")

public void printLog(){}

        @Around("printLog()")

        public void watchLog(ProceedingJoinPoint jp){

        try{

           log.info("接口要开始执行了---->>begin time:{}",System.currentTimeMillis());

           //执行方法

           jp.proceed();

            log.info("接口执行结束了---->>begin time:{}",System.currentTimeMillis());

         }catch (Throwable e){

           log.info("接口报错了...--->>{}",e);

        }

}


ProceedingJoinPoint 参数是必须要有的,因为要在通知中通过它来调用被通知的方法。通知方法中可以做任何事情,当要将控制权交给被通知的方法时,需要调用 ProceedingJoinPoint 的proceed()方法。

注意,你可以不调用 proceed() 方法,从而阻塞对被通知方法的访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一个场景就是实现重试逻辑,也就是在被同志方法失败后,进行重复尝试。

.

demo分享:使用环绕通知,简单实现监控接口访问时长


package com.ztjy.rankingserver.aop;

import lombok.extern.log4j.Log4j2;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.stereotype.Component;


/**

 * 切面测试类3

 * 记录接口开始、结束时间

 **/

@Component

@Aspect

@Log4j2

public class LogAop3 {


    @Pointcut("execution(* com.ztjy.rankingserver.service.impl.*.*(..))")

    public void service(){}


    @Around("service()")

    public Object monitorService(ProceedingJoinPoint pjp) {

        /** 拦截的方法名称 */

        String methodName = pjp.getTarget().getClass().getSimpleName() + "." + pjp.getSignature().getName();

        //参数

        String strArgs = null;

        Object[] args = pjp.getArgs();

        Object obj = null;

        try {

            if(null!=args && args.length>0){

                strArgs = args[0].toString();

            }

            long implTimeOut=200;

            long start = System.currentTimeMillis();

            log.info("接口要开始执行了---->>begin time:{}",start);

            //接口执行

            obj = pjp.proceed();

            long finish = System.currentTimeMillis();

            long useTime = finish - start;

            /** 接口响应时间监控 */

            if(implTimeOut > useTime){

                log.info("service接口访问,interface:[{}].args:[{}].useTime:[{}]",new Object[]{methodName,strArgs, useTime+"ms"});

            }else{

                log.warn("service接口超时,interface:[{}].args:[{}].useTime:[{}].settingUseTime:[{}]",new Object[]{methodName,strArgs, useTime+"ms", implTimeOut+"ms"});

            }

        }catch(Throwable e) {

            log.error("service接口访问异常,interface:[{}].args:[{}].errorInfo:[{}]",new Object[]{methodName,strArgs, e});

        }

        return obj;

    }

}




日志打印结果

monitorService - service接口超时,interface:[HistoryServiceImpl.getTeacherRankingList].args:[TeacherRankingAO(teacherId=b706e8e4ca8881530b6d, roleType=4, schoolId=1ca4fbd5b6346ef8363b, termType=2, page=0, pageSize=100, isCurrent=1)].useTime:[290ms].settingUseTime:[200ms]

3.3 通过注解引入新功能


Spring AOP学习及应用_第10张图片
图 3.3-1 使用Spring Aop,我们可以为bean引入新方法。  代理拦截调用并委托给实现该方法的其他对象


注意,当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个bean的实现被拆分到了多个类中

新接口


package com.ztjy.rankingserver.aop;

/**

* 扩展的新接口

 */

public interface AopExtend {

void extendMethod1();

 void extendMethod2();

}

新接口实现类

package com.ztjy.rankingserver.aop;

import lombok.extern.log4j.Log4j2;

/**

* 新接口实现类

 */

@Log4j2

public class AopExtendImpl implements AopExtend{

     @Override

     public void extendMethod1() {

         log.info("新增方法 extendMethod1 .....{}",System.currentTimeMillis());

     }

    @Override

     public void extendMethod2() {

           log.info("新增方法 extendMethod2 .....{}",System.currentTimeMillis());

     }

}


引入代理类


package com.ztjy.rankingserver.aop;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.DeclareParents;

import org.springframework.stereotype.Component;

/**

* 引入代理类

 */

@Component

@Aspect

public class LogExtendIntroducer {

           @DeclareParents(value = "com.ztjy.rankingserver.service.HistoryService+",defaultImpl = AopExtendImpl.class)

            /**

            * 要引入的接口

            */

            public static AopExtend aopExtend;

}


@DeclareParents 注解由三部分组成:

value 属性指定了哪种类型的bean要引入该接口。在本例中,就是所有实现了 HistoryService 接口的类型。(标记符后面的加号表示是HistoryService 的所有子类型,而不是 HistoryService 本身)

defaultImpl 属性指定了为引入功能提供实现的类

标注的静态属性指明了要引入了接口


测试实现


package com.ztjy.rankingserver.service.impl;

import com.ztjy.rankingserver.aop.AopExtend;

import com.ztjy.rankingserver.service.HistoryService;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)

@SpringBootTest

public class ServiceTest {

       @Autowired

        private HistoryService historyService ;



      @Test

      public void test(){

           AopExtend aopExtend=(AopExtend)historyService;

          aopExtend.extendMethod1();

          aopExtend.extendMethod2();

     }

}


4 XML创建切面

一般原则是,基于注解的配置要优于基于java的配置,基于java的配置要优于基于XML的配置。

以上基于注解实现的功能,也可以基于XML实现。不再一一赘述

5 Spring AOP 自定义注解实现

在实际业务需求中,会存在一些重要的业务接口,它们的地址会比较分散:不同包的类、类中的不同方法等,但它们所要实现的辅助功能是一致的。

比如需要记录关键方法的请求和响应时间,入参、结果等日志信息。

自定义注解可以解决这种问题,只在需要的地方调用即可。

1.编写自定义注解。实现对方法所实现的功能进行描述,以便在通知中获取描述信息


package com.ztjy.rankingserver.aop;

import java.lang.annotation.*;

/**

*

Description: 自定义系统日志注解

**/

@Target({ElementType.PARAMETER, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SystemLog {

         /**

          * 功能描述

         * @return

         */

        String description() default "";

         /**

         * 操作类型

         * @return

          */

          String operType() default "";

}


@Target 用于描述注解的使用范围(即:被描述的注解可以用在什么地方),其取值有:


取值描述

CONSTRUCTOR用于描述构造器

FIELD用于描述域

LOCAL_VARIABLE用于描述局部变量

METHOD用于描述方法。

PACKAGE用于描述包

PARAMETER用于描述参数。

TYPE用于描述类或接口(甚至 enum )。



@Retention 用于描述注解的生命周期(即:被描述的注解在什么范围内有效),其取值有:


取值描述

SOURCE注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中

CLASS注解的信息被保留在class文件(字节码文件)中当程序编译时,但不会被虚拟机读取在运行的时候

RUNTIME注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时


2.编写操作日志切面通知实现类


package com.ztjy.rankingserver.aop;


import com.alibaba.fastjson.JSON;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

import java.util.HashMap;

import java.util.Map;


@Aspect

@Component

public class SystemLogAspect {


    private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);


    @Pointcut("@annotation(com.ztjy.rankingserver.aop.SystemLog)")

    public void logAspect(){

    }


    @After("logAspect()")

    public void after(JoinPoint joinPoint){

        try {

            //获取用户请求方法的参数并序列化为JSON格式字符串

            String params = "",paramsType = "";

            if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {

                for ( int i = 0; i < joinPoint.getArgs().length; i++) {

                    params += JSON.toJSONString(joinPoint.getArgs()[i]) + ";";

                    paramsType += joinPoint.getArgs()[i].getClass()+ ";";

                }

            }

            String targetName = joinPoint.getTarget().getClass().getName();

            String methodName = joinPoint.getSignature().getName();

            Object[] arguments = joinPoint.getArgs();

            Class targetClass = Class.forName(targetName);

            Method[] methods = targetClass.getMethods();

            String description = "";

            String operType = "";

            for (Method method : methods) {

                if (method.getName().equals(methodName)) {

                    Class[] clazzs = method.getParameterTypes();

                    if (clazzs.length == arguments.length) {

                        description = method.getAnnotation(SystemLog.class).description();

                        operType = method.getAnnotation(SystemLog.class).operType();

                        break;

                    }

                }

            }

            Map map=new HashMap();

            map.put("params",params);

            map.put("paramsType",paramsType);

            map.put("description",description);

            map.put("operType",operType);

            map.put("methodName",(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));

            //*****数据入库

            logger.info("results:{}",JSON.toJSONString(map));

        }catch (Exception e){

            logger.error("-----后置通知日志处理失败:{}-----",e.getMessage());

        }

    }

}




3.执行引用

在需要的方法上引用注解。


/**

* 获取历史榜单列表

 * @param teacherRankingAO 入参对象

 * @return 榜单列表

 */

@Override

@SystemLog(description = "获取历史榜单列表",operType = "QUERY")

public List getTeacherRankingList(TeacherRankingAO teacherRankingAO) {

          //*******内容忽略

}


4.执行结果集

results:{"description":"获取历史榜单列表","methodName":"com.ztjy.rankingserver.service.impl.HistoryServiceImpl.getTeacherRankingList()","params":"{\"isCurrent\":1,\"page\":0,\"pageSize\":100,\"roleType\":4,\"schoolId\":\"1ca4fbd5b6346ef8363b\",\"teacherId\":\"b706e8e4ca8881530b6d\",\"termType\":2};","operType":"QUERY","paramsType":"class com.ztjy.rankingserver.model.ao.TeacherRankingAO;"}

6 注入 AspectJ 切面

虽然Spring AOP能够满足许多应用的切面需求,但是与AspectJ相比,Spring AOP是一个功能

比较弱的AOP解决方案.AspectJ 提供了Spring AOP所不能支持的许多类型的切点。

Spring AOP 和 AspectJ 之间的关键区别

Spring AOP学习及应用_第11张图片
图4-1 Spring AOP 和 AspectJ 之间的关键区别


如何选择

简单地说, 选择很大程度上取决于我们的要求:

框架: 如果应用程序没有使用 spring 框架, 那么我们就别无选择, 只能放弃使用 spring AOP 的想法, 因为它无法管理任何超出 spring 容器范围的东西。但是, 如果我们的应用程序是完全使用 spring 框架创建的, 那么我们可以使用 spring AOP, 因为它是简单的学习和应用

灵活性: 由于有限的 joinpoint 支持, Spring aop 不是一个完整的 aop 解决方案, 但它解决了程序员面临的最常见的问题。尽管如果我们想深入挖掘和开发 AOP 以达到其最大能力, 并希望得到广泛的可用 joinpoints 的支持, 那么最好选择 AspectJ

性能: 如果我们使用的是有限的切面, 那么就会有细微的性能差异。但有时, 应用程序有成千上万个切面的情况。我们不想在这样的情况下使用运行时编织, 所以最好选择 AspectJ。AspectJ 已知的速度比 Spring AOP 快8到35倍

两者的最佳之处: 这两个框架都是完全兼容的。我们总是可以利用 Spring AOP; 只要有可能, 仍然可以在不支持前者的地方使用 AspectJ 获得支持

你可能感兴趣的:(Spring AOP学习及应用)