Spring中的AOP(三)——基于Annotation的配置方式(一)

简介:

    AspectJ允许使用注解用于定义切面、切入点和增强处理,而Spring框架则可以识别并根据这些注解来生成AOP代理。Spring只是使用了和AspectJ 5一样的注解,但并没有使用AspectJ的编译器或者织入器,底层依然使用SpringAOP来实现,依然是在运行时动态生成AOP代理,因此不需要增加额外的编译,也不需要AspectJ的织入器支持。而AspectJ采用编译时增强,所以AspectJ需要使用自己的编译器来编译Java文件,还需要织入器。

    为了启用Spring对@AspectJ切面配置的支持,并保证Spring容器中的目标Bean被一个或多个切面自动增强,必须在Spring配置文件中配置如下内容(第4、9、10、15行):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<? 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-3.0.xsd
         http://www.springframework.org/schema/aop 
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
         http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context-3.0.xsd">
             
     <!-- 启动@AspectJ支持 -->
     < aop:aspectj-autoproxy />
</ beans >

    所谓自动增强,指的是Spring会判断一个或多个切面是否需要对指定的Bean进行增强,并据此自动生成相应的代理,从而使得增强处理在合适的时候被调用。如果不打算使用XML Schema的配置方式,则应该在Spring配置文件中增加如下片段来启用@AspectJ支持(即上面的<aop:aspectj-autoproxy />和下面创建Bean的方式选择一种即可启用@AspectJ支持):

?
1
< bean  class = "org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"  />

    上面配置的是一个Bean后处理器,该处理器将会为容器中Bean生成AOP代理。

    为了在Spring应用中启动@AspectJ支持,还需要在用用的类加载路径下增加两个AspectJ库:aspectweaver.jar和aspectjrt.jar,直接使用AspectJ安装路径下的lib目录下的这两个Jar文件即可,当然,也可以在Spring解压缩文件夹的lib/aspectj路径下找到它们。下面是项目内容的截图:

203856_WviS_1434710.jpg


定义切面Bean

    当启用了@AspectJ支持后,只要我们在Spring容器中配置一个带@AspectJ注释的Bean,Spring将会自动识别该Bean,并将该Bean作为切面处理。下面是一个例子:

?
1
2
3
4
@Aspect
public  class  LogAspect {
 
}

    切面类(用@Aspect修饰的类)和其他类一样可以有方法和属性的定义,还可能包括切入点、增强处理的定义。当我们使用@Aspect来修饰一个Java类后,Spring将不会把该Bean当成组件Bean处理,因此当Spring容器检测到某个Bean使用了@AspectJ标注之后,负责自动增强的后处理Bean将会忽略该Bean,不会对该Bean进行任何增强处理


使用Before增强处理

    当我们在一个切面类里使用@Before来标注一个方法时,该方法将作为Before增强处理。使用@Before标注时,通常需要指定一个value属性值,该属性值指定一个切入点表达式(既可以是一个已有的切入点,也可以直接定义切入点表达式),用于指定该增强处理将被织入哪些切入点。看例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
package  com.abc.advice;
 
import  org.aspectj.lang.annotation.Aspect;
import  org.aspectj.lang.annotation.Before;
 
@Aspect
public  class  BeforeAdviceTest {
     //匹配com.abc.service下的类中以before开始的方法
     @Before ( "execution(* com.abc.service.*.before*(..))" )
     public  void  permissionCheck() {
         System.out.println( "模拟权限检查" );
     }
}

    上面的程序使用@Aspect修饰了BeforeAdviceTest类,这表明该类是一个切面类,在该贴面里定义了一个permissionCheck方法——这个方法本来没有什么特殊之处,但因为使用了@Before来标注该方法,这就将该方法转换成一个Before增强处理。这个@Before注解中,直接指定了切入点表达式,指定com.abc.service包下的类中以before开始的方法的执行作为切入点。现假设我们在com.abc.service下有一个这样一个类:

?
1
2
3
4
5
6
7
8
9
10
package  com.abc.service;
import  org.springframework.stereotype.Component;
 
@Component
public  class  AdviceManager {
     //这个方法将被BeforeAdviceTest类的permissionCheck匹配到
     public  void  beforeAdvice() {
         System.out.println( "方法: beforeAdviceTest" );
     }
}

    从上面的代码来看,这个AdviceManager是一个纯净的Java类,它丝毫不知道将被谁来增强,也不知道将被进行怎样的增强——正式因为AdviceManager类的这种“无知”,才是AOP的最大魅力:目标类可以被无限的增强。

    在Spring配置文件中配置自动搜索Bean组件,配置自动搜索切面类,SpringAOP自动对Bean组件进行增强,下面是Spring配置文件代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<? 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-3.0.xsd
         http://www.springframework.org/schema/aop 
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-3.0.xsd">
             
     <!-- 启动@AspectJ支持 -->
     < aop:aspectj-autoproxy />
     
     <!-- 指定自动搜索Bean组件,自动搜索切面类 -->
     < context:component-scan  base-package = "com.abc.service,com.abc.advice" >
         < context:include-filter  type = "annotation" 
             expression = "org.aspectj.lang.annotation.Aspect"  />
     </ context:component-scan >
</ beans >

    主程序非常简单,通过Spring容器获取AdviceManager Bean,并调用Bean的beforeAdvice方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package  com.abc.main;
 
import  org.springframework.context.ApplicationContext;
import  org.springframework.context.support.ClassPathXmlApplicationContext;
 
import  com.abc.service.AdviceManager;
 
@SuppressWarnings ( "resource" )
public  class  AOPTest {
     public  static  void  main(String[] args) {
         ApplicationContext context = 
             new  ClassPathXmlApplicationContext( "applicationContext.xml" );
         AdviceManager manager = context.getBean(AdviceManager. class );
         manager.beforeAdvice();
     }
}

    执行主程序,将看到以下结果:

210050_5Yt8_1434710.jpg

    使用Before增强处理只能在目标方法执行之前织入增强,使用Before增强处理无需理会目标方法的执行,所以Before处理无法阻止目标方法的执行。Before增强处理执行时,目标方法还未获得执行机会,所以Before增强处理无法访问目标方法的返回值。


使用AfterReturning增强处理

    和使用@Before注解的使用类似,使用@AfterReturning来标注一个AfterReturning增强处理,该处理将在目标方法正常完成后被织入。使用@AfterReturning时可以指定两个属性:

  • pointcut/value:这两个属性的作用是一样的,都用于指定该切入点对应的切入表达式。同样的,既可以是一个已有的切入点,也可以是直接定义的切入点。当指定了pointcut属性后,value的属性值将会被覆盖

  • returning:指定一个返回值形参名,增强处理定义的方法可以通过该形参名来访问目标方法的返回值。

    在com.abc.advice包下面增加AfterReturningAdviceTest,这个类定义了一个AfterReturning增强处理:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package  com.abc.advice;
 
import  org.aspectj.lang.annotation.AfterReturning;
import  org.aspectj.lang.annotation.Aspect;
 
@Aspect
public  class  AfterReturningAdviceTest {
     //匹配com.abc.service下的类中以afterReturning开始的方法
     @AfterReturning (returning= "returnValue"
         pointcut= "execution(* com.abc.service.*.afterReturning(..))" )
     public  void  log(Object returnValue){
         System.out.println( "目标方法返回值:"  + returnValue);
         System.out.println( "模拟日志记录功能..." );
     }
}

    并在AdviceManager类中增加以下内容:

?
1
2
3
4
5
//将被AfterReturningAdviceTest的log方法匹配
public  String afterReturning() {
     System.out.println( "方法:afterReturning" );
     return  "afterReturning方法" ;
}

    正如上面程序中看到的,程序中使用@AfterReturning注解时,指定了一个returning属性,该属性的返回值是returnValue,这表明允许在增强方法log中使用名为returnValue的形参,该形参代表目标方法的返回值。在测试类AOPTest的main方法中增加调用本方法的语句,运行测试类,可以看到以下结果:

211343_4tOx_1434710.jpg

    @AfterReturning注解的returning属性所指定的形参名必须对应增强处理中的一个形参名,当目标方法执行以后,返回值作为相应的参数传入给增强处理方法。

    需要注意的是,使用@AfterReturning属性还有一个额外的作用,它可用于限定切入点之匹配具有对应返回值类型的方法——假设上面的log方法的参数returnValue的类型为String,那么该切入点只匹配com.abc.service.impl包下的返回值为String的所有方法。当然,上面的log方法返回值类型为Object,表明该切入点可匹配任何返回值的方法。除此之外,虽然AfterReturning增强处理可以访问到目标方法的返回值,但它不可改变这个返回值。


使用AfterThrowing增强处理

    使用@AfterThrowing注解可用于标注一个AfterThrowing增强处理,这个处理主要用于处理陈旭中未处理的异常。使用这个注解时可以指定两个属性:

  • pointcut/value:这两个属性的作用是一样的,都用于指定该切入点对应的切入表达式。同样的,既可以是一个已有的切入点,也可以是直接定义的切入点。当指定了pointcut属性后,value的属性值将会被覆盖

  • throwing:指定一个返回值形参名,增强处理定义的方法可通过该形参名来访问目标方法中所抛出的异常对象。

     在com.abc.advice包下面增加AfterThrowingAdviceTest,这个类定义了一个AfterThrowing增强处理:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package  com.abc.advice;
 
import  org.aspectj.lang.annotation.AfterThrowing;
import  org.aspectj.lang.annotation.Aspect;
 
@Aspect
public  class  AfterThrowingAdviceTest {
     @AfterThrowing (throwing= "ex" ,
         pointcut= "execution(* com.abc.service.*.afterThrow*(..))" )
     public  void  handleException(Throwable ex) {
         System.out.println( "目标方法抛出异常:"  +ex);
         System.out.println( "模拟异常处理" );
     }
}

    并在AdviceManager类中增加以下内容:

?
1
2
3
4
5
6
7
8
9
10
11
//将被AfterThrowingAdviceTest的handleException方法匹配
public  void  afterThrowing() {
     System.out.println( "方法: afterThrowing" );
     try  {
         int  a =  10  0 ;
     catch  (ArithmeticException ae) {
         System.out.println( "算术异常已被处理" );
     }
     String s =  null ;
     System.out.println(s.substring( 0 , 3 ));
}

    正如上面程序中看到的,程序中使用@AfterThrowing注解时,指定了一个throwing属性,该属性的值是ex,这表明允许在增强方法log中使用名为ex的形参,该形参代表目标方法的抛出的异常对象。运行测试类,可以看到以下结果:

212807_Pneh_1434710.jpg

    需要注意的是:如果一个异常在程序内部已经处理,那么Spring AOP将不会处理该异常。只有当目标方法抛出一个未处理的异常时,该异常将会作为对应的形参传给增强处理的方法。和AfterReturning类似的是,正确方法的参数类型可以限定切点只匹配指定类型的异常——假如上面的handleException方法的参数类型为NullPointerException,那么如果目标方法只抛出了ArithmaticException,则Spring AOP将不会处理这个异常。当然,handleException的参数类型为Throwable,则匹配了所有的Exception。

    从测试结果中可以看到,AfterThrowing处理虽然可以对目标方法的异常进行处理,但这种处理与直接使用catch捕捉不同:catch捕捉意味着完全处理该异常,如果catch块中没有重新抛出新异常,则该方法可以正常结束;而AfterThrowing处理虽然处理了该异常,但它不能完全处理该异常,这个异常依然会传播到上一级调用者(本例中为JVM,故会导致程序终止)。

目录
相关文章
|
4天前
|
安全 Java 开发者
深入理解Spring Boot配置绑定及其实战应用
【4月更文挑战第10天】本文详细探讨了Spring Boot中配置绑定的核心概念,并结合实战示例,展示了如何在项目中有效地使用这些技术来管理和绑定配置属性。
10 1
|
8天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
1天前
|
XML Java 数据格式
Spring高手之路18——从XML配置角度理解Spring AOP
本文是全面解析面向切面编程的实践指南。通过深入讲解切面、连接点、通知等关键概念,以及通过XML配置实现Spring AOP的步骤。
21 6
Spring高手之路18——从XML配置角度理解Spring AOP
|
3天前
|
消息中间件 开发框架 Java
什么是Spring Boot 自动配置?
Spring Boot 是一个流行的 Java 开发框架,它提供了许多便利的功能和工具,帮助开发者快速构建应用程序。其中一个最引人注目的特性是其强大的自动配置功能。
7 0
|
5天前
|
Java Spring
Spring文件配置以及获取
Spring文件配置以及获取
12 0
|
8天前
|
XML Java 数据格式
Spring使用AOP 的其他方式
Spring使用AOP 的其他方式
15 2
|
8天前
|
XML Java 数据格式
Spring 项目如何使用AOP
Spring 项目如何使用AOP
21 2
|
12天前
|
Java 微服务 Spring
Spring Boot中获取配置参数的几种方法
Spring Boot中获取配置参数的几种方法
21 2
|
13天前
|
Java 开发者 Spring
Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
【5月更文挑战第1天】Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
24 5
|
13天前
|
XML Java 数据格式
Spring AOP
【5月更文挑战第1天】Spring AOP
27 5