前文 中,已经讲解了Spring传统的AOP开发,但在实际开发中,我们都是使用AspectJ进行AOP开发。


AspectJ 简介

  • AspectJ 是一个基于Java语言的独立的AOP框架。

  • 在没有AspectJ 之前,Spring在进行AOP开发的时候,都是使用 前文 中讲过的传统的AOP开发方式,而有了AspectJ 之后,就使得Spring的AOP开发变的简单灵活,大大提高了程序的开发效率 和 后期的维护效率

  • Spring 2.0 后,新增了对AspectJ 切点表达式支持

  • AspectJ 1.5 后,新增了@AspectJ 注解开发功能,通过JDK5 技术,可以直接在Bean类中定义切面

  • 新版本Spring框架,建议使用灵活简单的AspectJ 方式 取代 繁杂的Spring传统AOP开发方式


Spring 使用AspectJ进行AOP开发所需jar包:

  • 4个spring基本jar包

    • spring-core-4.3.22.RELEASE.jar

    • spring-beans-4.3.22.RELEASE.jar

    • spring-expression-4.3.22.RELEASE.jar

    • spring-context-4.3.22.RELEASE.jar

  • spring aop相关jar包

    • spring-aop-4.3.22.RELEASE.jar
    • aopalliance-1.0.jar ,aop联盟
  • aspectj 相关jar包

    • aspectjweaver-1.9.2.jar
  • spring整合aspectj 相关jar包

    • spring-aspects-4.3.22.RELEASE.jar

当然,为了方便测试,你可以添加 junit-4.12.jarspring-test-4.3.22.RELEASE.jar


AspectJ 定义切点的方式

前文介绍的Spring传统AOP开发中,我们是通过pattern正则表达式 或 默认切点方式来定义切点;而在AspectJ中,则是通过execution函数来定义切入点。

通过execution函数构建的切入点表达式:
例如:

匹配com.cd4356.spring_aspectj.annotation包及其子孙包下,的任意类,的任意方法,参数可以是任意参数

  • execution(* com.cd4356.spring_aspectj.annotation..*.*(..))

匹配指定包下所有类方法

  • execution(* com.cd4356.spring_aspectj.annotation.*.*(..))

匹配指定UserService类的方法

  • execution(* com.cd4356.spring_aspectj.annotation.UserService.*(..))

匹配指定UserService类的save方法

  • execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))

匹配指定UserService类的以save开头的方法

  • execution(* com.cd4356.spring_aspectj.annotation.UserService.save*(..))

匹配所有以save开头的方法

  • execution(* save*(..))

匹配所有不以save开头的方法

  • !execution(* save*(..))

Spring使用AspectJ实现AOP的两种方式

  • 注解方式

  • XML方式

@AspectJ 提供的通知类型

  • @Before(前置通知),相当于BeforeAdvice

  • @AfterReturning(后置通知),相当于AfterReturningAdvice,拦截的目标方法异常,该通知不起作用,对应的方法不会执行

  • @Around(环绕通知),相当于MethodIntercepter

  • @AfterThrowing(异常抛出通知),相当于ThrowAdvice,拦截的目标方法无异常,该通知不起作用

  • @After(最终通知),不管是否发生异常,该通知都会执行,相当于 finally 代码块

通知执行的现后次序


需求:对save方法进行前置通知、对delete方法进行后置通知、对update方法进行环绕通知、对find方法进行异常抛出通知、对findAll方法进行最终通知

定义目标类

package com.cd4356.spring_aspectj.annotation;import org.springframework.stereotype.Service;@Service("userService")
public class UserService {public void save(){System.out.println("保存用户");}public String delete(){System.out.println("删除用户");return "我是返回值!";}public void update(){System.out.println("修改用户");}public void find(){System.out.println("查找用户");}public void findAll(){System.out.println("查找所有用户");}
}

定义切面类,别忘了在类上添加@Aspect注解
细看,精华都在该切面类中了,内含注释说明

package com.cd4356.spring_aspectj.annotation;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Aspect
public class AspectAnno {/*** 前置通知* @param joinPoint , 通过在方法中传入的JoinPoint对象,用来获取切点(目标方法)信息*/@Before(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))")public void before(JoinPoint joinPoint){//打印切点所属类名System.out.println(joinPoint.getTarget().getClass().getSimpleName());//打印切点对应方法名System.out.println(joinPoint.getSignature().getName());//打印切点对应方法的第一个参数//System.out.println(joinPoint.getArgs()[0]);System.out.println(". . . 前置通知 . . . ");}/*** 后置通知* @param returning , 通过returning属性(也可以不定义该属性),可以获取目标方法的返回值,作为参数传入后置方法中*/@AfterReturning(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))",returning = "returning")public void afterReturning(Object returning){System.out.println(". . . 后置通知 . . . "+returning);}/*** 环绕通知* @param joinPoint , 参数为ProceedingJoinPoint,用来调用拦截的目标方法执行* @return , around方法的返回值,就是目标代理方法执行返回值* @throws Throwable , 目标方法有异常,则只执行环绕前通知,不执行环绕后通知*/@Around(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.update(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println(". . . 环绕前通知 . . . ");//注意,如果不调用ProceedingJoinPoint的proceed方法,那么目标方法就被拦截了,不会执行该目标方法Object obj = joinPoint.proceed();  //执行目标方法System.out.println(". . . 环绕后通知 . . . ");return obj;}/*** 异常抛出通知* @param e , 通过throwing属性,获取拦截目标方法的异常*/@AfterThrowing(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.find(..))",throwing = "e")public void afterThrowing(Throwable e){System.out.println(". . . 异常抛出通知 . . . "+e.getMessage());}/*** 最终通知*/@After(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.findAll(..))")public void after(){System.out.println(". . . 最终通知 . . . ");}
}

在resource目录下,新建spring目录,用来spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!--开启AspectJ的注解开发,自动代理--><aop:aspectj-autoproxy/><!--开启全局扫描--><context:component-scan base-package="com.cd4356.spring_aspectj.annotation"/></beans>

定义测试类

package com.cd4356.spring_aspectj.annotation;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
//加载spring配置文件
@ContextConfiguration("classpath:spring/applicationContext1.xml")
public class SpringDemo {@Autowiredprivate UserService userService;@Test //使用了@Test注解的方法作用相当于main方法public void demo(){userService.save();userService.update();userService.delete();userService.find();userService.findAll();}
}

看一下运行结果


              通过@Pointcut 为切点命名

前面已经介绍了AspectJ的通知类型,并通过案例进行了演示。
  回顾一下,前面案例中切面类的代码,需求较为简单,都是一个通知作用再一个方法(切点)中,这样看起来没有什么不足之处。
  但如果有多个类型的通知作用再同一个方法上,再使用前面案例中的这种方式就会显得很麻烦,而且维护量也会很大。
  针对这种情况,AspectJ提供了@Pointcut注解,为切点名,进行统一管理

@Pointcut切入点

  • 再每个通知内定义切点,会造成工作量大,不易维护,所以对于重复使用的切点,可以使用@Pointcut 进行定义

  • 切点方法格式:private void 切点名(){} ,( 因为切点方法仅在本切面类中使用,且不会有返回值,方法体中也不需要任何内容,切点方法名切点名一致方便阅读 )

  • 当通知多个切点时,可以使用||进行连接


下面通过案例来对@Pointcut进一步了解取前面案例,改变需求( 对save和delete方法使用,前置通知、后置通知和异常抛出通知 )提示,只修改切面类的代码

不使用@Pointcut的做法,直接在每通知内定义切点

package com.cd4356.spring_aspectj.annotation;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Aspect
public class AspectAnno {@Before(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"+ "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")public void before(){System.out.println(". . . 前置通知 . . . ");}@AfterReturning(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"+ "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")public void afterReturning(){System.out.println(". . . 后置通知 . . . ");}@AfterThrowing(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"+ "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")public void afterThrowing(){System.out.println(". . . 异常抛出通知 . . . ");}
}

使用@Pointcut的做法,将每个切点独立出来统一管理

package com.cd4356.spring_aspectj.annotation;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Aspect
public class AspectAnno {@Before(value = "save()||delete()")public void before(){System.out.println(". . . 前置通知 . . . ");}@AfterReturning(value = "save()||delete()")public void afterReturning(){System.out.println(". . . 后置通知 . . . ");}@AfterThrowing(value = "save()||delete()")public void afterThrowing(){System.out.println(". . . 异常抛出通知 . . . ");}@Pointcut(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))")private void save(){}@Pointcut(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")private void delete(){}
}

对比这两种方式,是不是发现使用@Pointcut,代码量降低了,而且看起开显得更加整洁,没有那么辣眼睛。

需求:对save方法和update方法,进行前置通知和后置通知
(该案例以实现了接口的类进行演示)

package com.cd4356.spring_aspectj.xml;public interface UserService {void save();void delete();void update();void find();void findAll();
}
package com.cd4356.spring_aspectj.xml;public class UserServiceImpl implements UserService {public void save(){System.out.println("保存用户");}public void delete(){System.out.println("删除用户");}public void update(){System.out.println("修改用户");}public void find(){System.out.println("查找用户");}public void findAll(){System.out.println("查找所有用户");}
}

定义切面类,类中定义了5种通知类型的方法

package com.cd4356.spring_aspectj.xml;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;public class AspectXml {/*** 前置通知* @param joinPoint , 通过在方法中传入的JoinPoint对象,用来获取切点(目标方法)信息*/public void before(JoinPoint joinPoint){//打印切点所属类名System.out.println(joinPoint.getTarget().getClass().getSimpleName());//打印切点对应方法名System.out.println(joinPoint.getSignature().getName());//打印切点对应方法的第一个参数//System.out.println(joinPoint.getArgs()[0]);System.out.println(". . . 前置通知 . . . ");}/*** 后置通知* @param obj, 通过returning属性(也可以不定义该属性),可以获取目标方法的返回值,作为参数传入后置方法中*/public void afterReturning(Object obj){System.out.println(". . . 后置通知 . . . "+obj);}/*** 环绕通知* @param joinPoint , 参数为ProceedingJoinPoint,用来调用拦截的目标方法执行* @return , around方法的返回值,就是目标代理方法执行返回值* @throws Throwable , 目标方法有异常,则只执行环绕前通知,不执行环绕后通知*/public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println(". . . 环绕前通知 . . . ");Object obj = joinPoint.proceed();System.out.println(". . . 环绕后通知 . . . ");return obj;}/*** 异常抛出通知* @param e , 通过throwing属性,获取拦截目标方法的异常*/public void afterThrowing(Throwable e){System.out.println(". . . 异常抛出通知 . . . "+e.getMessage());}/*** 最终通知*/public void after(){System.out.println(". . . 最终通知 . . . ");}
}

AspectJ基于XML的方式重点了解 < aop:config > 标签

< aop:config >标签有三个子标签:

  • < aop:pointcut /> ,用来配置切入点
  • < aop:advisor /> ,用来配置切面(用来配置一个切入点和一个通知的切面组合
  • < aop:aspect /> , 用来配置切面(可以用来一个切入点和一个通知的切面组合,也可用来配置多个切入点和多个通知的切面组合),开发中一般使用该标签

事大数

< aop:aspect />标签中有与各种类型通知相对应的子标签

  • <aop:before /> ,用来配置前置通知
  • <aop:after-returning /> ,用来配置后置通知
  • <aop:around /> ,用来配置环绕置通知
  • <aop:after-throwing /> ,用来配置异常抛出通知
  • <aop:after /> ,用来配置最终通知
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"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"><!--配置目标类--><bean id="userService" class="com.cd4356.spring_aspectj.xml.UserServiceImpl"/><!--配置通知类--><bean id="aspectXml" class="com.cd4356.spring_aspectj.xml.AspectXml"/><!--配置aop完成增强--><aop:config><!--配置切入点: 哪些类的哪些方法需要增强--><aop:pointcut id="save" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.save(..))"/><aop:pointcut id="update" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.update(..))"/><!--配置切面--><aop:aspect ref="aspectXml"><!--配置前置通知--><aop:before method="before" pointcut-ref="save"/><!--配置后置通知--><aop:after-returning method="afterReturning" pointcut-ref="save" returning="obj"/><!--配置前置通知--><aop:before method="before" pointcut-ref="update"/><!--配置后置通知--><aop:after-returning method="afterReturning" pointcut-ref="update" returning="obj"/></aop:aspect></aop:config></beans>
package com.cd4356.spring_aspectj.xml;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
//加载spring配置文件
@ContextConfiguration("classpath:spring/applicationContext2.xml")
public class SpringDemo {@Autowiredprivate UserService userService;@Test //使用了@Test注解的方法作用相当于main方法public void demo(){userService.save();userService.update();userService.delete();userService.find();userService.findAll();}
}

运行效果:


前面案例我们是,对save方法和update方法,进行前置通知和后置通知。下面改变需求:对每个方法使用一种不同类型的增强(通知)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"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"><!--配置目标类--><bean id="userService" class="com.cd4356.spring_aspectj.xml.UserServiceImpl"/><!--配置通知类--><bean id="aspectXml" class="com.cd4356.spring_aspectj.xml.AspectXml"/><!--配置aop完成增强--><aop:config><!--配置切入点: 哪些类的哪些方法需要增强--><aop:pointcut id="save" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.save(..))"/><aop:pointcut id="update" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.update(..))"/><aop:pointcut id="delete" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.delete(..))"/><aop:pointcut id="find" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.find(..))"/><aop:pointcut id="findAll" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.findAll(..))"/><!--配置切面--><aop:aspect ref="aspectXml"><!--对save方法使用前置增强--><aop:before method="before" pointcut-ref="save"/><!--对update方法使用后置增强,后置通知方法中若没有定义参数,则无需使用returning属性--><aop:after-returning method="afterReturning" pointcut-ref="update" returning="obj"/><!--对delete方法使用环绕增强--><aop:around method="around" pointcut-ref="delete"/><!--对find方法使用异常抛出增强--><aop:after-throwing method="afterThrowing" pointcut-ref="find" throwing="e"/><!--对findAll方法使用最终增强--><aop:after method="after" pointcut-ref="findAll"/></aop:aspect></aop:config></beans>

运行效果:


案例源码:

  链接:https://pan.baidu.com/s/1MUJZyPG2aMp41rV-oFX3Uw
  提取码:wthk

使用AspectJ开发AOP更加便捷,你不知道嘛相关推荐

  1. Spring使用AspectJ开发AOP

    AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言.Spring 2.0 以后,新增了对 AspectJ 方式的支持,新版本的 Spring 框架,建议使用 Aspe ...

  2. AspectJ的注解开发AOP:异常抛出通知的学习

    AspectJ的注解开发AOP:异常抛出通知的学习 参考文章: (1)AspectJ的注解开发AOP:异常抛出通知的学习 (2)https://www.cnblogs.com/xiaolaha/p/1 ...

  3. (转)Spring使用AspectJ进行AOP的开发:注解方式

    http://blog.csdn.net/yerenyuan_pku/article/details/69790950 Spring使用AspectJ进行AOP的开发:注解方式 之前我已讲过Sprin ...

  4. Spring注解驱动开发——AOP常用注解

    一.用于开启注解AOP支持的 @EnableAspectJAutoProxy (一) 作用 表示开启spring对注解aop的支持.它有两个属性,分别是指定采用的代理方式和 是否暴露代理对象,通过Ao ...

  5. 学习AspectJ框架(一):AspectJ开发环境搭建与Hello World

    AOP大家都不陌生,它是一种编程理念,一种规范,有很多的实现者如Spring AOP,JBoss AOP,还有我们今天要讲的AspectJ.我们平时项目用到最多的是Spring AOP,它是用纯Jav ...

  6. Spring基于AspectJ实现AOP操作

    基于AspectJ实现AOP操作 准备工作 在项目工程里面引入 AOP 相关依赖. 如果是maven项目,使用pom.xml代替引入jar包的过程(注意) 学会使用切入点表达式 AOP 操作(Aspe ...

  7. Spirng使用Aspectj实现AOP

    Aspectj实现AOP有两种方式: (1)基于aspectj的xml配置: (2)基于aspectj的注解方式: 一.基于aspectj的xml配置: 1.导入相关的AOP的jar包: 2.创建Sp ...

  8. Spring @AspectJ 实现AOP 入门例子(转)

    AOP的作用这里就不再作说明了,下面开始讲解一个很简单的入门级例子. 引用一个猴子偷桃,守护者守护果园抓住猴子的小情节. 1.猴子偷桃类(普通类): Java代码   package com.samt ...

  9. Spring框架基于AspectJ的AOP开发规范和步骤

    AOP和动态代理的关系: AOP术语: 横向关注点: 需要新增的到业务代码中的功能(在目标对象那里叫横切关注点,在切面类中叫通知) 切面类: 封装了增强方法(横向关注点)的类 通知: 切面类中的每一个 ...

最新文章

  1. SAP MM 公司间STO的交货单里的移动类型的确定
  2. Reactor/Proactor的比较 (ZZ)
  3. 看看那些双车中接力装置
  4. 《Swift编程入门经典》
  5. 数据库及线程死锁(转)
  6. 原因以及如何避免产生僵尸进程
  7. c语言编手机蓝牙软件的代码,51单片机C语言的简易蓝牙锁代码
  8. ArrayList和Vector的区别
  9. thinkPHP-空操作
  10. Linux unzip命令:解压zip文件
  11. 路由器链路聚合技术(Eth-Trunk、Ip-Trunk)
  12. 成功运行官方Tensorflow Android的demo的过程
  13. pytorch autograd.grad
  14. unity 生成assestbundle资源的插件
  15. 最新android工程目录下armeabi-v7a,armeabi的具体含义,有什么区别
  16. msm8937 bootloader流程分析
  17. vue结合vue-amap调用高德地图行政区划分
  18. 【数据结构】人名查询哈希表设计(线性探测法)
  19. KOC十问:品牌缺钱的谎言,还是新瓶装旧酒?
  20. PBRT-v2在windows下的配置与使用

热门文章

  1. 购物车的数据应该保存在哪?
  2. Qt从入门到放弃_0x01:建立项目
  3. 前端异步编程之Promise和async的用法
  4. centos7.4下安装配置PHP服务(源码安装)并配置nginx支持php
  5. Linux 下挂载新硬盘方法(转)
  6. 自学linux指令总结
  7. GO学习笔记 - Go 只有一种循环结构—— for 循环。
  8. mysqld 多线程 用pstree -p 显示
  9. 通过NSString初始化OC类
  10. centos部署时间服务器