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

作者:yaominghui 时间:2023-09-29 00:17:45 

这篇文章主要介绍了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;

来源:https://www.cnblogs.com/yangyuanhu/p/12180732.html

标签:Spring,AOP,AspectJ
0
投稿

猜你喜欢

  • Java编程调用微信接口实现图文信息推送功能

    2023-11-25 07:20:47
  • 微信开发之使用java获取签名signature

    2022-08-01 10:47:01
  • C# List介绍及具体用法

    2021-09-15 07:59:14
  • maven profile动态选择配置文件详解

    2023-11-05 22:05:20
  • Mapper批量插入Oracle数据@InsertProvider注解

    2023-02-11 15:13:40
  • android实现密码框右侧显示小眼睛

    2023-01-26 19:54:59
  • Spring如何解决单例bean线程不安全的问题

    2023-12-18 23:50:20
  • Spring框架中@PostConstruct注解详解

    2021-09-20 09:35:58
  • java反射拼接方法名动态执行方法实例

    2022-12-07 00:44:28
  • Spring实战之方法级别缓存用法示例

    2022-10-20 02:00:32
  • Java8中的 Lambda表达式教程

    2023-10-13 01:32:29
  • mybatis plus使用redis作为二级缓存的方法

    2023-11-19 08:34:58
  • linux(center OS7)安装JDK、tomcat、mysql 搭建java web项目运行环境

    2022-07-04 07:02:37
  • Android实现环形进度条的实例

    2023-01-11 17:39:22
  • Android View移动的3种方式总结

    2022-04-29 02:04:47
  • android自定义进度条渐变色View的实例代码

    2023-07-01 20:58:57
  • InputStream数据结构示例解析

    2022-06-12 20:11:53
  • 一篇看懂Java中的Unsafe类

    2022-02-13 04:02:38
  • Android Studio项目中导入开源库的方法

    2022-02-02 02:40:27
  • C#线程倒计时器源码分享

    2023-08-16 07:23:36
  • asp之家 软件编程 m.aspxhome.com