Spring AOP AspectJ使用及配置过程解析

这篇文章主要介绍了Spring AOP AspectJ使用及配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

AspectJ是一个基于Java语言的AOP框架,Spring2.0以后新增了对AspectJ切点表达式支持。因为Spring1.0的时候Aspectj还未出现;

AspectJ1.5中新增了对注解的支持,允许直接在Bean类中定义切面。新版本的Spring框架建
议我们都使用AspectJ方式来开发AOP,并提供了非常灵活且强大的切点表达式 ;

当然无论使用Spring自己的AOP还是AspectJ相关的概念都是相同的;

注解配置

依赖导入:

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

通知类型

  • @AspectJ提供的通知类型:
  • @Before 前置通知 在原始方法执行前执行
  • @AfterReturning 后置通知 在原始方法执行前执行
  • @Around 环绕通知 彻底拦截原始方法的执行,执行前后都可以增加逻辑,也可以不执行原始方法
  • @AfterThrowing抛出通知,执行原始方法出现异常时执行
  • @After 最终final通知,不管是否异常,原始方法调用后都会执行
  • @DeclareParents 引介通知,相当于IntroductionInterceptor (了解即可)

定义切点

通过execution函数来定义切点

语法:execution(访问修饰符 返回类型 方法名 参数 异常)

表达式示例:

  • 匹配所有类public方法:execution(public * *(..))第一个*表示返回值 ..表示任意个任意类型参数
  • 匹配指定包下所有方法: execution(* cn.xxx.dao.*(..)) 第一个想*表示忽略权限和返回值类型
  • 匹配指定包下所有方法:execution(* cn.xxx.dao..*(..))包含子包
  • 匹配指定类所有方法: execution(* cn.xxx.service.UserService.*(..))
  • 匹配实现特定接口所有类方法 : execution(* cn.xxx.dao.GenericDAO+.*(..))
  • 匹配所有save开头的方法: execution(* save*(..))

前置通知

pom依赖:

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

xml需要添加aop名称空间及xsd:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--  启用aspectj  -->
  <aop:aspectj-autoproxy/>
<!--  目标-->
  <bean id="personDao" class="com.yh.demo1.PersonDao"/>
<!--  切面-->
  <bean class="com.yh.demo1.MyAspect"/>
</beans>

test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
  @Autowired
  PersonDao personDao;

  @Test
  public void test(){
    personDao.delete();
    personDao.update();
  }
}

切面类:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
   //表示PersonDao下所有方法都作为切点
  @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
  public void beforeAdvice(){
    System.out.println("before code run.....");
  }
}

当我们需要获取切点信息(被增强的代码)时,可以在通知添加参数,想下面这样

@Aspect
public class MyAspect {
  @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
  public void beforeAdvice2(JoinPoint point){
    System.out.println("before code run2....." + point);
  }
}

后置通知:

//当需要获取原始方法的返回值时可以在注解中添加returning参数来指定参数名 Aspectj会自动将返回值放到参数中
@AfterReturning(value = "execution(* com.yh.demo1.PersonDao.delete(..))",returning = "result")
public void afterAdvice(Object result){
  System.out.println("删除方法执行后 .....  返回值为:"+ result);
}

后置通知可以获取目标方法的返回值

环绕通知:

@Around(value = "execution(* com.yh.demo1.PersonDao.insert(..))")
public void aroundAdvice(ProceedingJoinPoint point) throws Throwable {
  //code............
  System.out.println("环绕前置..");

  //执行原始方法 __当需要获取返回值时可以声明变量接收
  Object result = point.proceed();
  System.out.println("原始方法返回值: "+result);
  //code............
  System.out.println("环绕后置..");
}

环绕通知与其他通知最大的区别在于环绕通知可以控制是否调用原始方法

注意:参数类型必须为ProceedingJoinPoint,否则 无法执行原始方法,

异常通知

@AfterThrowing(value = "execution(* com.yh.demo1.PersonDao.save(..))",throwing = "e")
public void exceptionHandler(JoinPoint point,Exception e){
  System.out.println(point + " 方法出现"+e.getMessage()+"异常");
}

当方法中出现时才会执行该通知,若需要获取异常信息,可在注解中添加throwing指定参数名称

我们可以使用环绕+异常通知来处理数据库事务,在环绕中开启事务以及提交事务,异常通知中回滚事务,当然Spring已经对事务进行了封装不需要自己写

最终通知

@After(value = "execution(* *delete(..))")
public void afterRun(){
  System.out.println("最终");
}

最终通知叫做after 即调用原始方法之后执行无论原始方法中是否出现异常

而后置叫做afterReturning表示在成功返回后才会执行执行

带有逻辑符的表达式:
在表达式中可以使用户逻辑操运算符,与&& 或|| 非!

示例:

/*
execution(* cn.xxx.service.UserDao.insert(..))||execution(* cn.xxx.service.UserDao.delete(..))

execution(* cn.xxx.service.UserDao.*nsert(..))&&execution(* cn.xxx.service.UserDao.inser*(..))

!execution(* cn.xxx.service.UserDao.insert(..))
*/
2|4切点命名

切点命名

假设有多个通知应用在同一个切点上时,我们需要重复编写execution表达式,且后续要修改切点时则多个通知都需要修改,维护起来非常麻烦,我们可以通过给切点指定名称从而完成对切点的重复使用和统一操作,以提高开发维护效率;

//定义命名切点 方法名称即切点名称
@Pointcut(value = "execution(* com.yh.demo1.PersonDao.save(..))")
private void savePointcut(){}

@Pointcut(value = "execution(* com.yh.demo1.PersonDao.delete(..))")
private void deletePointcut(){}

多个通知应用到同一个切点:

//使用命名切点
@Before(value = "savePointcut()")
public void beforeAdvice(){
  System.out.println("before code run.....");
}
//使用命名切点
@Around(value = "savePointcut()")
public void beforeAdvice2(ProceedingJoinPoint point) throws Throwable {
  System.out.println("环绕前");
  point.proceed();
  System.out.println("环绕后");

一个通知应用到多个切点

//同一个通知对应多个切点
@After(value = "savePointcut()||deletePointcut()")
public void afterAdvice(){
  System.out.println("after code run.....");
}

XML配置

XML配置所需的jar 以及各个对象之间的依赖关以及表达式的写法都是一样的,仅仅是换种方式来写而已;

xml:

    <!--目标-->
  <bean id="studentDao" class="com.yh.demo2.StudentDao"/>
    <!--通知-->
  <bean id="advices" class="com.yh.demo2.XMLAdvice"/>

    <!--织入信息-->
  <aop:config>
        <!--切点定义-->
    <aop:pointcut id="select" expression="execution(* com.yh.demo2.StudentDao.select(..))"/>
        <!--切面定义-->
    <aop:aspect ref="advices">
      <aop:before method="before" pointcut-ref="select"/>
      <aop:after-returning method="afterReturning" pointcut-ref="select" returning="result"/>
      <aop:after method="after" pointcut-ref="select" />
      <aop:after-throwing method="exception" pointcut-ref="select" throwing="e"/>
      <aop:around method="around" pointcut-ref="select"/>
    </aop:aspect>

    <!--入侵式通知 即通知需要实现指定接口 两种不能同时使用 -->
    <aop:advisor advice-ref="advice2" pointcut-ref="select"/>
  </aop:config>
 <!--入侵式通知Bean-->
 <bean id="advice2" class="com.yh.demo2.XMLAdvice2"/>

通知类:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;

public class XMLAdvice {
  public void before(JoinPoint pointcut){ System.out.println("前置通知 切点:"+pointcut); }

  public void afterReturning(JoinPoint point,Object result){
    System.out.println("后置通知 切点:"+point);
  }

  public void after(JoinPoint point){ System.out.println("最终通知 切点:"+point); }

  public void exception(JoinPoint point,Throwable e){
    System.out.println("异常通知: " + e+"切点:"+point);
  }

  public void around(ProceedingJoinPoint point) throws Throwable {
    System.out.println("环绕前");
    point.proceed();
    System.out.println("环绕后");
  }
}

你会发现 ,无论是XML还是注解都不需要手动指定代理,以及目标对象,Aspectj会从切点中获取目标对象信息并自动创建代理;

AspectJ是目前更流行的方式,具体采用XML还是注解需要根据项目具体情况,小组协作开发推荐xml;

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

(0)

相关推荐

  • 详解Spring Aop实例之AspectJ注解配置

    上篇<Spring Aop实例之xml配置>中,讲解了xml配置方式,今天来说说AspectJ注解方式去配置spring aop. 依旧采用的jdk代理,接口和实现类代码请参考上篇博文.主要是将Aspect类分享一下: package com.tgb.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aft

  • Android AOP框架AspectJ使用详解

    前言 之前了解过android的AOP框架,用法主要用来打日志:现在有一个需求需要函数在新线程中执行,并且函数主体执行完之后,在UI线程返回结果.想到手写的话,每次都要new Thread的操作,比较麻烦:因此就尝试用注解的方法解决这个问题. AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc.ajc会构建目标程序与AspectJ代码的联系,在编译期将Aspe

  • java基于AspectJ(面向切面编程)编码示例分享

    一.基本概念 AspectJ是一种面向切面程序设计的基于Java 的实现.它向 Java 中加入了连接点(Join Point)这个新概念,其实它也只是现存的一个 Java概念的名称而已.它向 Java 语言中加入少许新结构:切点(pointcut).通知(Advice).类型间声明(Inter-type declaration)和方面(Aspect).切点和通知动态地影响程序流程,类型间声明则是静态的影响程序的类等级结构,而切面则是对所有这些新结构的封装. 基于切面.连接点.切点.通知的概念如

  • 分析java 中AspectJ切面执行两次的原因

    分析java 中AspectJ切面执行两次的原因 背景 转眼之间,发现博客已经将近半年没更新了,甚是惭愧.话不多说,正如标题所言,最近在使用AspectJ的时候,发现拦截器(AOP切面)执行了两次了.我们知道,AspectJ是AOP的一种解决方案,本质上是通过代理类在目标方法执行通知(Advice),然后由代理类再去调用目标方法.所以,从这点讲,拦截器应该只会执行一次.但是在测试的时候发现拦截器执行了两次. 问题重现 既然问题已经明了,那么可以通过代码简单重现这个问题,从而更深层次分析到底是什么

  • Android中使用AspectJ详解

    什么是AOP AOP是Aspect Oriented Programming的缩写,即『面向切面编程』.它和我们平时接触到的OOP都是编程的不同思想,OOP,即『面向对象编程』,它提倡的是将功能模块化,对象化,而AOP的思想,则不太一样,它提倡的是针对同一类问题的统一处理,当然,我们在实际编程过程中,不可能单纯的安装AOP或者OOP的思想来编程,很多时候,可能会混合多种编程思想,大家也不必要纠结该使用哪种思想,取百家之长,才是正道. 那么AOP这种编程思想有什么用呢,一般来说,主要用于不想侵入原

  • AndroidStudio 配置 AspectJ 环境实现AOP的方法

    昨天看了一段android配置aspectj实现AOP的直播视频,就试着自己配置了一下,可能是因为我自己的AndroidStudio环境的问题,碰到了不少的坑(其实还是因为对gradle理解的不多),但总归是配置好了,就分享一下. 试了两种方式,不过项目下的build.gradle,没什么变化,直接看一下代码吧: build.gradle(项目下) buildscript { ext { //android appcompat支持库版本 androidSupportVersion = '26.1

  • 在Android项目中使用AspectJ的方法

    什么是AOP AOP是 Aspect Oriented Programming 的缩写,即面向切面编程,和平常遇到的面向对象OOP编程不一样的是,OOP是将功能模块化对象化,AOP是针对同一类的问题统一化处理.例如做日志埋点,性能监控,动态权限控制等. AspectJ AspectJ实际上是对AOP编程的实践,目前还有很多的AOP实现,如ASMDex,但笔者选用的是AspectJ. 在Android项目中使用AspectJ 如果使用原生AspectJ在项目中配置会非常麻烦,在GitHub上有个开

  • AspectJ的基本用法

    AOP虽然是方法论,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP.目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy[1]那样的拓展.).当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好.所以,使用AspectJ有两种方法: 完全使用AspectJ的语言.这语言一点也不难,和Java几乎一样,也能

  • Spring Aop之AspectJ注解配置实现日志管理的方法

    最近项目要做一个日志功能,我用Spring Aop的注解方式来实现. 创建日志注解 package com.wyj.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lan

  • Spring使用AspectJ注解和XML配置实现AOP

    本文演示的是Spring中使用AspectJ注解和XML配置两种方式实现AOP 下面是使用AspectJ注解实现AOP的Java Project 首先是位于classpath下的applicationContext.xml文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmln

随机推荐