Spring 使用AspectJ进行AOP开发(基于注解)

举报
CoderX 发表于 2022/04/16 17:30:58 2022/04/16
【摘要】 基于XML的缺点和解决方案 在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。 (这个XML配置就是逊呐) AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)...

基于XML的缺点和解决方案

在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。 (这个XML配置就是逊呐)

AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。

注解介绍

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

切入点表达式

学习使用Aspect进行AOP开发之前,要先了解一下切入点表达式。 AspectJ最强大的地方就在于他的切入点表达式:

execution() ,用于描述方法 ,语法格式为

  execution([权限修饰符] [返回值类型] [类的完全限定名] [方法名称]([参数列表]) 

其中:

  • 返回值类型、方法名、参数列表是必须配置的选项,权限修饰符、类的完全限定名则为可选配选项。

  • 返回值类型: *表示任意返回值; 如果返回值为对象,则需指定全路径的类名。

  • 类的完全限定名:包名+类名

  • 方法名:  * 代表所有方法, set*代表以set开头的所有方法, *do 代表以do结尾的所有有方法, addUser 代表固定方法

  • 参数列表:  (..)代表所有参数;(*) 代表只有一个参数,且参数类型为任意类型; ( *,String) 代表有两个参数,第一个参数可以为任何值,第二个为 String 类型的值。

举例1:对org.example包下的UserDao类中的add()方法进行增强,配置如下

 execution(* org.example.UserDao.add(..))

举例2: 对org.example 包下 UserDao 类中的所有方法进行增强

 execution(* org.example.UserDao.*(..))

举例 3:对 org.example 包下所有类中的所有方法进行增强

 execution(* org.example.*.*(..))


实现步骤

1.启用@AspectJ注解支持

在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。我们可以通过以下 2 种方式来启用 @AspectJ 注解。

1)使用Java配置类启用

我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。

 @Configuration
 @ComponentScan(basePackages = "org.example") //注解扫描
 @EnableAspectJAutoProxy //开启 AspectJ 的自动代理
 public class AppConfig {
 }

2)基于XML配置启用

在Spring的XML配置文件中,添加以下内容启用@AspectJ注解支持。

 <!-- 开启注解扫描 -->
 <context:component-scan base-package="org.example"/>
 <!--开启AspectJ 自动代理-->
 <aop:aspectj-autoproxy/>

2.定义切面@Aspect

可以通过 @Aspect 注解将一个 Bean 定义为切面。

在启用了 @AspectJ 注解支持的情况下,Spring 会自动将 IoC 容器(ApplicationContext)中的所有使用了 @Aspect 注解的 Bean 识别为一个切面。

1)只需要在定义好的Bean对应的Java类使用一个@Aspect注解 ,将这个 Bean 定义为一个切面

 <bean id = "myAspect" class = "org.example.MyAspect">
    ...
 </bean>
 package org.example;
 import org.aspectj.lang.annotation.*;
 @Aspect //定义为切面
 public class MyAspect {
 }

2)全注解方式定义切面

 package org.example;
 import org.aspectj.lang.annotation.*;
 import org.springframework.stereotype.Component;
 @Component //Spring自动装配 将类的对象定义成 Bean
 @Aspect //将Bean定义为切面
 public class MyAspect {
 }
 ​

3.定义切点@Pointcut

使用 @Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为 void。

 // 要求:方法必须是private,返回值类型为 void,名称自定义,没有参数
 @Pointcut("execution(*org.example..*.*(..))")
 private void myPointCut() {
 }

@Pointcut 注解中有一个 value 属性,这个属性的值就是切入点表达式。

除了可以通过切入点表达式(execution)直接对切点进行定义外,还可以通过切入点方法的名称来引用其他的切入点。在使用方法名引用其他切入点时,还可以使用“&&”、“||”和“!”等表示“与”、“或”、“非”的含义。

 /**
 * 将 org.example.dao包下 UserDao 类中的 get() 方法定义为一个切点
 */
 @Pointcut(value ="execution(* org.example.dao.UserDao.get(..))")
 public void pointCut1(){
 }
 ​
 /**
 * 将 org.example.dao包下 UserDao 类中的 delete() 方法定义为一个切点
 */
 @Pointcut(value ="execution(* org.example.dao.UserDao.delete(..))")
 public void pointCut2(){
 }
 ​
 /**
 * 除了 org.example.dao包下 UserDao 类中 get() 方法和 delete() 方法外,其他方法都定义为切点
 *
 * ! 表示 非 
 * && 表示 与
 * || 表示 或
 */
 @Pointcut(value ="!pointCut1() && !pointCut2()")
 public void pointCut3(){
 }

4.定义通知(Advice)

AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。

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

以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称)。

 @Pointcut(value ="execution(* org.example.dao.UserDao.get(..))")
 public void pointCut1(){
 }
 ​
 @Pointcut(value ="execution(* org.example.dao.UserDao.delete(..))")
 public void pointCut2(){
 }
 ​
 @Pointcut(value ="!pointCut1() && !pointCut2()")
 public void pointCut3(){
 }
 ​
 //使用切入点引用
 @Before("MyAspect.pointCut3()")
 public void around() throws Throwable {
     System.out.println("环绕增强……");
 }
 ​
 //使用切入点表达式
 @AfterReturning(value = "execution(* org.example.dao.UserDao.get(..))" ,returning = "returnValue")
 public void afterReturning(Object returnValue){
     System.out.println("方法返回值为:"+returnValue);
 }

代码示例

  1. 新建Maven项目,在pom.xml文件中导入maven依赖

     //在父级dependencies定义spring版本
     <spring.version>5.3.15</spring.version>
     ​
     <dependencies>
         <!--日志-->
         <dependency>
           <groupId>commons-logging</groupId>
           <artifactId>commons-logging</artifactId>
           <version>1.2</version>
         </dependency>
         <!-- java ee -->
         <dependency>
           <groupId>javax</groupId>
           <artifactId>javaee-api</artifactId>
           <version>7.0</version>
         </dependency>
     ​
         <!-- 单元测试 -->
         <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <version>4.12</version>
         </dependency>
     ​
         <!-- Spring -->
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-core</artifactId>
           <version>${spring.version}</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-beans</artifactId>
           <version>${spring.version}</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>${spring.version}</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-jdbc</artifactId>
           <version>${spring.version}</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-tx</artifactId>
           <version>${spring.version}</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-web</artifactId>
           <version>${spring.version}</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-expression</artifactId>
           <version>5.3.15</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-aop</artifactId>
           <version>5.3.15</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>${spring.version}</version>
         </dependency>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-test</artifactId>
           <version>${spring.version}</version>
         </dependency>
     ​
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-aspects</artifactId>
           <version>${spring.version}</version>
         </dependency>
     ​
         <!--Aspectj-->
         <dependency>
           <groupId>org.aspectj</groupId>
           <artifactId>aspectjweaver</artifactId>
           <version>1.9.7</version>
         </dependency>
     ​
       </dependencies>


  2. 在org.example.dao包下,创建一个名为UserDao的接口

     package org.example.dao;
     ​
     /**
      * 数据访问层 接口
      */
     public interface UserDao {
         public void add();
         public void delete();
         public Integer modfiy();
         public void get();
     }
     ​


  3. 在org.example.dao.imp1包下,创建UserDao的实现类UserDaoImp1

     package org.example.dao.imp1;
     ​
     import org.example.dao.UserDao;
     import org.springframework.stereotype.Component;
     ​
     @Component("userDao")
     public class UserDaoImp1 implements UserDao {
         @Override
         public void add() {
             System.out.println("正在执行UserDao的add()方法");
         }
     ​
         @Override
         public void delete() {
             System.out.println("正在执行UserDao的delete()方法");
     ​
         }
     ​
         @Override
         public Integer modfiy() {
             System.out.println("正在执行UserDao的modfiy()方法");
             return 1;
         }
     ​
         @Override
         public void get() {
             System.out.println("正在执行UserDao的get()方法");
     ​
         }
     }
     ​


  4. 在org.example.dao.config包下,新建名为AppConfig的配置类

     package org.example.dao.config;
     ​
     import org.springframework.context.annotation.ComponentScan;
     import org.springframework.context.annotation.Configuration;
     import org.springframework.context.annotation.EnableAspectJAutoProxy;
     ​
     @Configuration
     @ComponentScan(basePackages = "org.example") //注解扫描
     @EnableAspectJAutoProxy  //开启AspectJ自动代理
     public class AppConfig {
     }
     ​
  5. 在org.example包下,创建名为MyAspect的切面类

    package org.example;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Component //定义成Bean
    @Aspect  //定义为切面
    public class MyAspect {
        @Before("execution(* org.example.dao.UserDao.add(..))")
        public void before(JoinPoint joinPoint) {
            System.out.println("前置增强..." + joinPoint);
        }
        @After("execution(* org.example.dao.UserDao.get(..))")
        public void after(JoinPoint joinPoint){
            System.out.println("最终增强..."+joinPoint);
        }
    
        //将org.example.dao下的UserDao类的get()方法定义为一个切点
        @Pointcut(value = "execution(* org.example.dao.UserDao.get(..))")
        public void pointCut1(){
        }
    
        //将org.example.dao下的UserDao类的delete(()方法定义为一个切点
        @Pointcut(value = "execution(* org.example.dao.UserDao.delete(..))")
        public void pointCut2(){
        }
    
        //使用切入点引用
        @Around("MyAspect.pointCut2()")
        public void around(ProceedingJoinPoint point) throws Throwable{
            System.out.println("环绕增强前...");
            point.proceed();
            System.out.println("环绕增强后...");
        }
    
        //使用切入点表达式
        @AfterReturning(value = "execution(* org.example.dao.UserDao.modfiy(..))",returning = "returnValue")
        public void afterReturning(Object returnValue){
            System.out.println("后置返回增强...返回值为:"+returnValue);
        }
    
    }
    
    
  6. MainApp类

    package org.example;
    
    import org.example.dao.UserDao;
    import org.example.dao.config.AppConfig;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    
    public class App {
        public static void main( String[] args ){
            ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
            UserDao userDao=(UserDao)context.getBean("userDao");
            userDao.add();
            userDao.delete();
            userDao.modfiy();
            userDao.get();
        }
    }
    
    


  7. 运行结果

    前置增强...execution(void org.example.dao.UserDao.add())
    正在执行UserDao的add()方法
    环绕增强前...
    正在执行UserDao的delete()方法
    环绕增强后...
    正在执行UserDao的modfiy()方法
    后置返回增强...返回值为:1
    正在执行UserDao的get()方法
    最终增强...execution(void org.example.dao.UserDao.get())
    


【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。