当前位置 : 主页 > 编程语言 > java >

Spring使用AspectJ开发AOP

来源:互联网 收集:自由互联 发布时间:2023-02-04
简介 AspectJ 是一个基于 [Java]语言的 AOP 框架,它扩展了 Java 语言。[Spring]2.0 以后,新增了对 AspectJ 方式的支持,新版本的 Spring 框架,建议使用 AspectJ 方式开发 AOP。 使用 AspectJ 开发

简介

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

使用 AspectJ 开发 AOP 通常有两种方式:

  • 基于 XML 的声明式。
  • 基于 Annotation 的声明式。

@AspectJ是AspectJ1.5以后新增的功能,通过JDK5注解技术,允许直接在Bean类中定义切面

AspectJ 通知类型

  • aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。
  • aspectj 通知类型,只定义类型名称。已经方法格式。
  • @before前置通知(应用:各种校验):
    • 在方法执行前执行,如果通知抛出异常,阻止方法运行
  • @afterReturning后置通知(应用:常规数据处理):
    • 方法正常返回后执行,如果方法中抛出异常,通知无法执行
    • 必须在方法执行后才执行,所以可以获得方法的返回值。
  • @around环绕通知(应用:十分强大,可以做任何事情):
    • 方法执行前后分别执行,可以阻止方法的执行
    • 必须手动执行目标方法
  • @afterThrowing抛出异常通知(应用:包装异常信息):
    • 方法抛出异常后执行,如果方法没有抛出异常,无法执行
  • @after最终通知(应用:清理现场):
    • 方法执行完毕后执行,无论方法中是否出现异常

在通知中通过value属性定义切点

  • 通过execution函数,可以定义切点的方法切入
  • 语法: execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
  • 例如:
    • 匹配所有类public方法:execution(public * *(..))
    • 匹配指定包下所有类方法:execution(* com.yibo.dao.*(..)) 不包含子包
    • execution(* com.yibo.dao..*(..)) ..*表示包、子孙包下所有类
    • 匹配指定类所有方法:execution(* com.yibo.dao.UserDao.*(..))
    • 匹配实现特定接口的所有类方法:execution(* com.yibo.dao.UserDao+.*(..))
    • 匹配所有save开头的方法:execution(* save*(..))

接下来将对这两种 AOP 的开发方式进行讲解。

基于XML的声明式

基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在 aop:config 元素中。 下面通过案例演示 Spring 中如何使用基于 XML 的声明式实现 AOP 的开发。

导入 JAR 包

使用 AspectJ 除了需要导入 Spring AOP 的 JAR 包以外,还需要导入与 AspectJ 相关的 JAR 包,具体如下。

  • spring-aspects:Spring 为 AspectJ 提供的实现,在 Spring 的包中已经提供。
  • aspectjweaver:是 AspectJ 提供的规范
  • spring-aspects:spring整合AspectJ

1、创建目标类 GoodsDao

public class CustomerDao { public void add() { System.out.println("添加商品..."); } public void update() { System.out.println("修改商品..."); } public void delete() { System.out.println("删除商品..."); } public void find() { System.out.println("修改商品..."); } }

2、创建切面类 MyAspect

//切面类 public class MyAspect { // 前置通知 public void myBefore(JoinPoint joinPoint) { System.out.print("前置通知,目标:"); System.out.print(joinPoint.getTarget() + "方法名称:"); System.out.println(joinPoint.getSignature().getName()); } // 后置通知 public void myAfterReturning(JoinPoint joinPoint) { System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName()); } // 环绕通知 public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕开始"); // 开始 Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法 System.out.println("环绕结束"); // 结束 return obj; } // 异常通知 public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("异常通知" + "出错了" + e.getMessage()); } // 最终通知 public void myAfter() { System.out.println("最终通知"); } }

上述代码中,分别定义了几种不同的通知类型方法,在这些方法中,通过 JoinPoint 参数可以获得目标对象的类名、目标方法名和目标方法参数等。需要注意的是,环绕通知必须接收一个类型为 ProceedingJoinPoint 的参数,返回值必须是 Object 类型,且必须抛出异常。异常通知中可以传入 Throwable 类型的参数,用于输出异常信息。

3、创建 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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!--目标类 --> <bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl" /> <!--切面类 --> <bean id="myAspect" class="com.mengma.aspectj.xml.MyAspect"></bean> <!--AOP 编程 --> <aop:config> <aop:aspect ref="myAspect"> <!-- 配置切入点,通知最后增强哪些方法 --> <aop:pointcut expression="execution ( * com.mengma.dao.*.* (..))" id="myPointCut" /> <!--前置通知,关联通知 Advice和切入点PointCut --> <aop:before method="myBefore" pointeut-ref="myPointCut" /> <!--后置通知,在方法返回之后执行,就可以获得返回值returning 属性 --> <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal" /> <!--环绕通知 --> <aop:around method="myAround" pointcut-ref="myPointCut" /> <!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 --> <!-- *注意:如果程序没有异常,则不会执行增强 --> <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable --> <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e" /> <!--最终通知:无论程序发生任何事情,都将执行 --> <aop:after method="myAfter" pointcut-ref="myPointCut" /> </aop:aspect> </aop:config> </beans>

4、创建测试类

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")   public class XMLTest { @Resource(name="customerDao") private CustomerDao customerDao; @Test public void test() { customerDao.add(); customerDao.update(); customerDao.delete(); customerDao.find(); } }

基于 Annotation 的声明式

在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

 

为此,AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。

 

关于 Annotation 注解的介绍如表所示。

名称 说明 @Aspect 用于定义一个切面。 @Before 用于定义前置通知,相当于 BeforeAdvice。 @AfterReturning 用于定义后置通知,相当于 AfterReturningAdvice。 @Around 用于定义环绕通知,相当于MethodInterceptor。 @AfterThrowing 用于定义抛出通知,相当于ThrowAdvice。 @After 用于定义最终final通知,不管是否异常,该通知都会执行。 @DeclareParents 用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。

下面使用注解的方式重新实现《基于XML的声明式》部分的功能。

1、创建切面类 MyAspect,2中方式创建

1、直接使用execution函数,定义切点的方法切入

//切面类 @Aspect @Component public class MyAspect { // 前置通知 @Before(value="execution(* com.mengma.dao.CustomerDao.add(..))") public void myBefore(JoinPoint joinPoint) { System.out.print("前置通知,目标:"); System.out.print(joinPoint.getTarget() + "方法名称:"); System.out.println(joinPoint.getSignature().getName()); } // 后置通知 @AfterReturning(value = "execution(* com.mengma.dao.CustomerDao.update(..))",returning="ret") public void myAfterReturning(JoinPoint joinPoint,Object ret) { System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName() + " , -->" + ret); } // 环绕通知 @Around(value="execution(* com.mengma.dao.CustomerDao.delete(..))") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕开始"); // 开始 Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法 //如果不调用proceedingJoinPoint.proceed()方法,那么目标方法就会被拦截 System.out.println("环绕结束"); // 结束 return obj; } // 异常通知 @AfterThrowing(value = "execution(* com.mengma.dao.CustomerDao.delete(..))", throwing = "e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("异常通知" + "出错了" + e.getMessage()); } // 最终通知 @After(value="execution(* com.mengma.dao.CustomerDao.find(..))") public void myAfter() { System.out.println("最终通知"); } }

上述代码中:

  • @Aspect 注解用于声明这是一个切面类,该类作为组件使用,所以要添加 @Component 注解才能生效。
  • 在每个通知相应的方法上都添加了注解声明,并且将切入点方法名通过execution函数作为参数传递给要执行的方法,如需其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。

2、通过@Pointcut为切点命名

  • 在每个通知内定义切点,工作量会很大,而且不易维护,对于重复的切点,可以使用@Pointcut进行定义
  • 切点方法:private void 无参数方法,方法名为切点名
  • 当通知多个切点时,可以使用||进行连接
//切面类 @Aspect @Component public class MyAspect { // 要求:方法必须是private,没有值,名称自定义,没有参数 @Pointcut("execution(* com.mengma.dao..*.*(..))") private void myPointCut() { } // 前置通知 @Before("myPointCut()") public void myBefore(JoinPoint joinPoint) { System.out.print("前置通知,目标:"); System.out.print(joinPoint.getTarget() + "方法名称:"); System.out.println(joinPoint.getSignature().getName()); } // 后置通知 @AfterReturning(value = "myPointCut()",returning="ret") public void myAfterReturning(JoinPoint joinPoint,Object ret) { System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName() + " , -->" + ret); } // 环绕通知 @Around("myPointCut()") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕开始"); // 开始 Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法 //如果不调用proceedingJoinPoint.proceed()方法,那么目标方法就会被拦截 System.out.println("环绕结束"); // 结束 return obj; } // 异常通知 @AfterThrowing(value = "myPointCut()", throwing = "e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("异常通知" + "出错了" + e.getMessage()); } // 最终通知 @After("myPointCut()") public void myAfter() { System.out.println("最终通知"); } }

上述代码中:

  • @Aspect 注解用于声明这是一个切面类,该类作为组件使用,所以要添加 @Component 注解才能生效。
  • @Poincut 注解用于配置切入点,取代 XML 文件中配置切入点的代码。
  • 在每个通知相应的方法上都添加了注解声明,并且将切入点方法名“myPointCut”作为参数传递给要执行的方法,如需其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。

2、为目标类添加注解

添加注解 @Repository("customerDao")。

3、创建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-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--扫描含com.mengma包下的所有注解--> <context:component-scan base-package="com.mengma"/> <!-- 使切面开启自动代理 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>

上述代码中:

  • 首先导入了 AOP 命名空间及其配套的约束,使切面类中的 @AspectJ 注解能够正常工作;
  • 添加了扫描包,使注解生效。
  • 切面开启自动代理。

4、创建测试类

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")   public class AnnotationTest { @Resource(name="customerDao") private CustomerDao customerDao; @Test public void test() { customerDao.add(); customerDao.update(); customerDao.delete(); customerDao.find(); } }

参考: http://c.biancheng.net/view/4275.html

上一篇:Spring JDBCTemplate简介
下一篇:没有了
网友评论