Spring AOP的使用详解

什么是AOP

AOP(Aspect Oriented Programming 面向切面编程),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

常用于日志记录,性能统计,安全控制,事务处理,异常处理等等。

定义AOP术语

切面(Aspect):切面是一个关注点的模块化,这个关注点可能是横切多个对象;

连接点(Join Point):连接点是指在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候;

通知(Advice):指在切面的某个特定的连接点上执行的动作。Spring切面可以应用5中通知:

  • 前置通知(Before):在目标方法或者说连接点被调用前执行的通知;
  • 后置通知(After):指在某个连接点完成后执行的通知;
  • 返回通知(After-returning):指在某个连接点成功执行之后执行的通知;
  • 异常通知(After-throwing):指在方法抛出异常后执行的通知;
  • 环绕通知(Around):指包围一个连接点通知,在被通知的方法调用之前和之后执行自定义的方法。

切点(Pointcut):指匹配连接点的断言。通知与一个切入点表达式关联,并在满足这个切入的连接点上运行,例如:当执行某个特定的名称的方法。

引入(Introduction):引入也被称为内部类型声明,声明额外的方法或者某个类型的字段。

目标对象(Target Object):目标对象是被一个或者多个切面所通知的对象。

AOP代理(AOP Proxy):AOP代理是指AOP框架创建的对对象,用来实现切面契约(包括通知方法等功能)

织入(Wearving):指把切面连接到其他应用出程序类型或者对象上,并创建一个被通知的对象。或者说形成代理对象的方法的过程。

Spring对AOP的支持

  1. 基于代理的经典SpringAOP;
  2. 纯POJO切面;
  3. @AspectJ注解驱动的切面;
  4. 注入式AspectJ切面(适用于Spring各版本);

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

切入点表达式

使用SpringAOP

SpringAOP的支持必须呀导入spring-aspects的jar包

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>4.3.5.RELEASE</version>
</dependency>

使用注解定义切面

采用注解的方式定义切面以及通知

@Aspect
public class Audience {
  //使用@Pointcut注解声明频繁使用的切入点表达式
  @Pointcut("execution(* com.wqh.concert.Performance.perform(..))")
  public void performance(){}

  @Before("performance()")
  public void silenceCellPhones(){
    System.out.println("Sillencing cell phones");
  }
  @Before("performance()")
  public void takeSeats(){
    System.out.println("Task Seat");
  }
  @AfterReturning("performance()")
  public void applause(){
    System.out.println("CLAP CLAP CLAP");
  }
  @AfterThrowing("performance()")
  public void demandRefund(){
    System.out.println("Demand a Refund");
  }
}

另外需要在applicationContext.xml也就是spring的配置文件中添加配置:

<!--启用AspectJ的自动代理-->
<aop:aspectj-autoproxy/>
<!--声明bean-->
<bean class="com.wqh.concert.Audience"/>

在XML中声明切面

定义pojo类,这里只是把上面定义的注解全public class AudienceXML {

public void silenceCellPhones() {
  System.out.println("Sillencing cell phones");
}
public void takeSeats() {
  System.out.println("Task Seat");
}
public void applause() {
  System.out.println("CLAP CLAP CLAP");
}
public void demandRefund() {
  System.out.println("Demand a Refund");
}

applicationContext.xml配置

<!--声明bean-->
<bean name="audienceXML" class="com.wqh.concert.AudienceXML"/>
<aop:config>
  <!--引入bean-->
  <aop:aspect ref="audienceXML">
    <!--定义切点-->
    <aop:pointcut id="perform"
           expression="execution(* com.wqh.concert.Performance.perform(..))"/>
    <!--定义通知
      method:通知,也就是具体的方法
      pointcut-ref:引用的切点
      pointcut:切点-->
    <aop:before method="silenceCellPhones"
          pointcut-ref="perform"/>
    <aop:before method="takeSeats" pointcut-ref="perform"/>
    <aop:after-returning method="applause" pointcut-ref="perform"/>
    <aop:after-throwing method="demandRefund"
              pointcut="execution(* com.wqh.concert.Performance.perform(..))"/>
  </aop:aspect>
</aop:config>

环绕通知

在springAOP中有五种通知,环绕通知是最为强大的通知。它能够让你编写的逻辑将被通知的目标方法完全包装起来。实际上就像在一个通知方法中同时编写前置通知和后置通知。
本片文章具体讲解环绕通知的使用。

使用注解

使用环绕通知定义切面:

@Aspect
public class AudienceAround {
  //使用@Pointcut注解声明频繁使用的切入点表达式
  @Pointcut("execution(* com.wqh.concert.Performance.perform(..))")
  public void performance(){}
  @Around("performance()")
  public void watchPerformance(ProceedingJoinPoint joinPoint){
    try {
      System.out.println("Silencing cell phones");
      System.out.println("Taking seats");
      joinPoint.proceed();
      System.out.println("Demanding a refund");
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }
  }
}

可以看到在上面的代码中,定义通知的时候在通知方法中添加了入参:ProceedingJoinPoint。在创建环绕通知的时候,这个参数是必须写的。因为在需要在通知中使用ProceedingJoinPoint.proceed()方法调用被通知的方法。

另外,如果忘记调用proceed()方法,那么通知实际上会阻塞对被通知方法的调用。

在XML中定义

首先去掉上面类的所有注解:这里为了区别就重新创建一个类

public class AudienceAroundXML {
  public void watchPerformance(ProceedingJoinPoint joinPoint){
    try {
      System.out.println("Silencing cell phones");
      System.out.println("Taking seats");
      joinPoint.proceed();
      System.out.println("Demanding a refund");
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }
  }
}

配置:

<!--声明bean-->
<bean name="audienceAroundXML" class="com.wqh.concert.AudienceAroundXML"/>
 <!--配置切面及通知-->
<aop:config>
  <aop:aspect ref="audienceAroundXML">
    <aop:pointcut id="performance"
           expression="execution(* com.wqh.concert.Performance.perform(..))"/>
    <aop:around method="watchPerformance" pointcut-ref="performance"/>
  </aop:aspect>
</aop:config>

处理通知中的参数

Spring借助AspectJ的切点表达式语言来定义Spring切面

在spring中尝试使用其他指示器时,会抛出IllegalArgument-Exception异常。

如上的这些指示器,只有exception指示器是实际执行匹配的,而其他都是用来限制匹配的。

切面表达式分析

带参数的切点表达式分解

在该切点表达式中使用了args(trackNumber)限定符。表示传递给playTrack()方法的int类型参数也会传递到通知中去。参数名trackNumber也与切点方法签名中的参数相匹配。

创建切面

@Aspect
public class TrackCounter {
  @Pointcut("execution(* com.wqh.aop.CompactDisc.playTrack(int))&&args(trackNumber)")
  public void trackPlayder(int trackNumber){}
  @Before("trackPlayder(trackNumber)")
  public void countTrack(int trackNumber) {
    System.out.println("前置通知:targetNumber=" + trackNumber);
  }
}

连接点类

@Service
public class CompactDisc {
  public void playTrack(int trackNumber){
    System.out.println("trackNumber =" + trackNumber);
  }
}

XML配置

<!--启用AspectJ的自动代理-->
<aop:aspectj-autoproxy/>
<!--声明bean-->
 <bean class="com.wqh.aop.TrackCounter"/>
 <!--自动扫描包下的类-->
<context:component-scan base-package="com.wqh.aop"/>

测试

@Test
public void testT(){
  ApplicationContext applicationContext =
      new ClassPathXmlApplicationContext(
          new String[]{"classpath:/spring/applicationContext.xml"});
  CompactDisc compactDisc = (CompactDisc) applicationContext.getBean("compactDisc");
  compactDisc.playTrack(12);
}

上面给指定方法传入的参数是12,在通知中获取到了该参数

另外:在xml中配置切面来处理通知中的参数,其实也差不多,只是把切点表达式放到了XML配置文件中。

给类添加新的功能

引入Spring实战中的知识

在SpringAOP中,我们可以为Bean引入新的方法。代理拦截器调用并委托给实现该方法的其他对象。

当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某给其他对象。

使用注解方式引入

代码
首先是连接点的接口及其实现类

public interface Person {
  void say();
}
public class ChinesePerson implements Person {
  @Override
  public void say() {
    System.out.println("说中文");
  }
}

创建需要添加的功能,这里个人类扩展一个吃的功能

public interface Food {
  void eat();
}
public class ChineseFood implements Food {
  @Override
  public void eat() {
    System.out.println("吃中餐");
  }
}

编写切面

@Aspect
public class addFuction {
  @DeclareParents(value = "com.wqh.addfunction.Person+",defaultImpl = ChineseFood.class)
  public static Food food;
}

注意这里的表达式使用的式@DeclareParents注解;该注解所标注的静态属性指明了要引入的接口。

注解中使用的value属性指定哪种类型的bean要引入该接口,这里Person后后面的“+”号表示所有子类型,而不是该类的本身。defaultImpl,指定了为引入功能提供实现的类。

使用XML配置bean:

<!--启用AspectJ的自动代理-->
<aop:aspectj-autoproxy/>
<!--声明bean-->
<bean class="com.wqh.addfunction.addFuction"/>
<bean name="chinesePerson" class="com.wqh.addfunction.ChinesePerson"/>

测试

@Test
public void testAdd(){
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
      "classpath:spring/applicationContext.xml");
  Person person = (Person) applicationContext.getBean("chinesePerson");
  person.say();
  //这里可以将chinesePerson bean转换为Food类,所以添加成功
  Food food = (Food) applicationContext.getBean("chinesePerson");
  food.eat();
}

在XML中引入

首先将上面的addFuction注解全部删除,其他不变;然后在xml中添加相应的配置:

<!--启用AspectJ的自动代理-->
<aop:aspectj-autoproxy/>
<!--声明bean-->
<bean name="chinesePerson" class="com.wqh.addfunction.ChinesePerson"/>
 <aop:config>
   <aop:aspect>
     <aop:declare-parents types-matching="com.wqh.addfunction.Person+"
               implement-interface="com.wqh.addfunction.Food"
     default-impl="com.wqh.addfunction.ChineseFood"/>
   </aop:aspect>
 </aop:config>

这里的types-matching与上面的vale作用一样;

default-impl与defaultImpl作用一样,这也可以使用delegate-ref;当然如果使用delegate-ref则是要引用SpringBean;

implement-interface则是要引入的接口

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • spring boot如何使用spring AOP实现拦截器

    在spring boot中,简单几步,使用spring AOP实现一个拦截器: 1.引入依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 2.创建拦截器类(在该类中,定义了拦截规则:拦截com.xjj.web.controller包下面的所

  • Spring Boot中使用AOP统一处理web层异常的方法

    在springboot错误默认是跳转到 请求返回渲染路径中的error/错误页面中. 源码分析:DefaultErrorViewResolver.java private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = th

  • 在Spring Boot框架中使用AOP的正确姿势

    前言 Spring Boot是基于Spring的用来开发Web应用的框架,功能与Spring MVC有点类似,但是Spring Boot的一大特点就是需要的配置非常少.Spring Boot推荐convention over configuration,也就是约定大于配置,因此Spring Boot会帮你做许多自动的配置,并且Spring Boot使用的是Java Config,几乎可以做到零XML文件配置. 假设现在有这样一种场景,需要统计某个接口的处理耗时,我们可以使用AOP来实现,AOP为

  • 使用Spring的注解方式实现AOP实例

    spring对AOP的实现提供了很好的支持.下面我们就使用Spring的注解来完成AOP做一个例子. 首先,为了使用Spring的AOP注解功能,必须导入如下几个包.aspectjrt.jar,aspectjweaver.jar,cglib-nodep.jar.然后我们写一个接口 package com.bird.service; public interface PersonServer { public void save(String name); public void update(S

  • 详解使用Spring Boot的AOP处理自定义注解

    上一篇文章Java 注解介绍讲解了下Java注解的基本使用方式,并且通过自定义注解实现了一个简单的测试工具:本篇文章将介绍如何使用Spring Boot的AOP来简化处理自定义注解,并将通过实现一个简单的方法执行时间统计工具为样例来讲解这些内容. AOP概念 面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计.观点导向编程.剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型.该范型以一种称为侧面(aspect,又译作方面)的语

  • Spring Boot使用AOP防止重复提交的方法示例

    在传统的web项目中,防止重复提交,通常做法是:后端生成一个唯一的提交令牌(uuid),并存储在服务端.页面提交请求携带这个提交令牌,后端验证并在第一次验证后删除该令牌,保证提交请求的唯一性. 上述的思路其实没有问题的,但是需要前后端都稍加改动,如果在业务开发完在加这个的话,改动量未免有些大了,本节的实现方案无需前端配合,纯后端处理. 思路 自定义注解 @NoRepeatSubmit 标记所有Controller中的提交请求 通过AOP 对所有标记了 @NoRepeatSubmit 的方法拦截

  • SpringBoot中使用AOP打印接口日志的方法

    前言 AOP 是 Aspect Oriented Program (面向切面)的编程的缩写.他是和面向对象编程相对的一个概念.在面向对象的编程中,我们倾向于采用封装.继承.多态等概念,将一个个的功能在对象中来实现.但是,我们在实际情况中也发现,会有另外一种需求就是一类功能在很多对象的很多方法中都有需要.例如有一些对数据库访问的方法有事务管理的需求,有很多方法中要求打印日志.按照面向对象的方式,那么这些相同的功能要在很多地方来实现或者在很多地方来调用.这就非常繁琐并且和这些和业务不相关的需求耦合太

  • 详解使用Spring AOP和自定义注解进行参数检查

    引言 使用SpringMVC作为Controller层进行Web开发时,经常会需要对Controller中的方法进行参数检查.本来SpringMVC自带@Valid和@Validated两个注解可用来检查参数,但只能检查参数是bean的情况,对于参数是String或者Long类型的就不适用了,而且有时候这两个注解又突然失效了(没有仔细去调查过原因),对此,可以利用Spring的AOP和自定义注解,自己写一个参数校验的功能. 代码示例 注意:本节代码只是一个演示,给出一个可行的思路,并非完整的解决

  • SpringBoot AOP使用笔记

    1. 启用AOP a. 在类上添加@Aspect注解 b. 注入该类, 可以使用@Component进行注入到Spring容器中 2. 通过PointCut对象创建切入点 a. 在某个方法使用类似下面的方法进行注入 @Pointcut("execution(* com.sguess.service.IAOPService.*(..))") private void pointcut() { } i. 其中,execution表达式为 execution(modifiers-patter

  • SpringBoot项目中使用AOP的方法

    本文介绍了SpringBoot项目中使用AOP的方法,分享给大家,具体如下: 1.概述 将通用的逻辑用AOP技术实现可以极大的简化程序的编写,例如验签.鉴权等.Spring的声明式事务也是通过AOP技术实现的. 具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-aop Spring的AOP技术主要有4个核心概念: Pointcut: 切点,用于定义哪个方法会被拦截,例如 execution(* cn.sp

随机推荐