面向切面的Spring

简介: 写在前面  本文是博主在看完面向切面的Spring(《Spring实战》第4章)后的一些实践笔记。  为什么要用AOP呢?作者在书中也明确提到了,使用AOP,可以让代码逻辑更多的去关注自己本身的业务,而不用混杂和关注一些其它的东西。

写在前面

  本文是博主在看完面向切面的Spring(《Spring实战》第4章)后的一些实践笔记。
  为什么要用AOP呢?作者在书中也明确提到了,使用AOP,可以让代码逻辑更多的去关注自己本身的业务,而不用混杂和关注一些其它的东西。包括:安全,缓存,事务,日志等等。

名词概念

  • 通知(Advice)

  定义了切面做什么和什么时候去做。简单点来说,就是AOP执行时会调用的方法,通知除了定义切面要完成的工作(What),还会定位什么时候(When)去履行这项工作,是在方法调用前,还是调用之后,还是前后都是,还是抛出异常时

在切面定义中,一共有以下五种通知类型

类型 作用
Before 某方法调用之前发出通知
After 某方法完成之后发出通知,不考虑方法运行的结果
AfterReturning 将通知放置在被通知的方法成功执行之后
AfterThrowing 将通知放置在被通知的方法抛出异常之后
Around 通知包裹在被通知的方法的周围,在方法调用之前和之后发出(环绕通知 = 前置 + 目标方法执行 + 后置通知)
  • 切点,也叫切入点(Pointcut)

  上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法

  • 连接点,也叫参加点(JoinPoint)

  连接点是切面在应用程序执行过程中插入的地方,可能是方法调用(前、后)的时候,也可能是异常抛出的时候。连接点如果可以说是切点的全集,那么切点就是连接点的子集

  • 切面(Aspect)

  切面其实就是通知和切点的结合。通知说明了干什么和什么时候干(通过方法上使用@Before、@After等就能知道),则切点说明了在哪干(指定到底是哪个方法),这就组成了一个完整的切面定义

Spring对AOP的支持

  • Spring建议在Java中编写AOP,虽然用XML也可以实现
  • Spring通过使用代理类,在运行阶段将切面编织进bean中
  • Spring只支持方法级别的连接点,不像AspectJ还可以通过构造器或属性注入

切点表达式

  切点表达式算是一些比较概念性的知识,下面截了两个图供大家参考参考

切点表达式1
切点表达式1
切点表达式2
切点表达式2

  看得头晕了吧,不过好在只有execution()是用来执行匹配的,剩下的都是为了限制或定制连接点要匹配的位置
  以下是execution()定义的格式(其中,带?号的为可选,否则必须给出) :

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) 

  还是举个真实栗子模仿一下吧

execution(* com.example.aspectj.UserDao.updateName(..))
  • execution:用于定义什么方法执行时会被触发,这里是指com.example.aspectj包下的UserDao接口中的updateName方法执行时触发
  • * :忽略方法返回值类型
  • (..) :匹配任意参数

实战测试(SpringBoot + JPA)

  1. Create Entity
@Table(name = "tb_user")
@Entity
@Data
public class User {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

}
  1. Create Dao
public interface UserDao extends JpaRepository<User, Integer> {

    @Modifying
    @Transactional
    @Query("update User u set u.name = ?1 where u.id = ?2")
    int updateName(String name, int id);

}

  1. Create Service
@Service
public class UserService {

    @Resource
    private UserDao userDao;

    @Transactional
    public void save(User user) {
        userDao.save(user);
    }

    public void update(String name, int id) {
        userDao.updateName(name, id);
    }

}

第一种风格的切面

  1. Create Aspect(使用了@Before、@After、@AfterReturning和@AfterThrowing这四个注解)
@Aspect
public class UserAspectjOne {

    @Resource
    private UserService userService;

    @Before("execution(* com.example.aspectj.UserDao.updateName(..))")
    public void before() {
        System.out.println("1.------------before()");
    }

    @After("execution(* com.example.aspectj.UserDao.updateName(..))")
    public void after() {
        System.out.println("1.------------after()");
    }

    @AfterReturning("execution(* com.example.aspectj.UserDao.updateName(..))")
    public void afterReturning() {
        System.out.println("1.------------afterReturning()");
        User user = new User();
        user.setName("afterReturning1");
        userService.save(user);
    }

    @AfterThrowing("execution(* com.example.aspectj.UserDao.updateName(..))")
    public void afterThrowing() {
        System.out.println("1.------------afterThrowing()");
        User user = new User();
        user.setName("afterThrowing1");
        userService.save(user);
    }

}
  1. Create Configuration
@Configuration
// @EnableAspectJAutoProxy //实测可以不添加该注解,因为SpringBoot中已经默认开启了AOP功能
public class AspectjConfiguration {

    @Bean
    public UserAspectjOne userAspectjOne() {
        return new UserAspectjOne();
    }

}
SpringBoot已经默认开启了aop
SpringBoot已经默认开启了aop
  1. Test updateName() with UserAspectjOne

    • 6.1 先往数据库里添加一条数据
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("jared");
        userDao.save(user);
    }
    
    添加User
    添加User
- 6.2 测试正常执行updateName()
``` java
@Test
public void testUpdateName() {
    userService.update("jared qiu", 1);
}
```

- 6.3.1 打印结果 

![输出结果][5]

- 6.3.2 数据库结果

![数据库结果][6]

- 6.4 测试非正常执行updateName(),只需要把UserDao类中updateName()上的@Modifying或者@Transactional注解去掉即可
``` java
@Test
public void testUpdateName() {
    userService.update("error jared qiu", 1);
}
```

- 6.5.1 打印结果 

![输出结果][7]

- 6.5.2 数据库结果

![数据库结果][8]

第二种风格的切面

  1. Create Aspect(依旧使用了@Before、@After、@AfterReturning和@AfterThrowing这四个注解,但新增了@Pointcut注解,把切面的定义抽离了出来进行统一)
@Aspect
public class UserAspectjTwo {

    @Resource
    private UserService userService;

    @Pointcut("execution(* com.example.aspectj.UserDao.updateName(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void before() {
        System.out.println("2.------------before()");
    }

    @After("pointcut()")
    public void after() {
        System.out.println("2.------------after()");
    }

    @AfterReturning("pointcut()")
    public void afterReturning() {
        System.out.println("2.------------afterReturning()");
        User user = new User();
        user.setName("afterReturning2");
        userService.save(user);
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        System.out.println("2.------------afterThrowing()");
        User user = new User();
        user.setName("afterThrowing2");
        userService.save(user);
    }

}
  1. Create Configuration
@Configuration
public class AspectjConfiguration {

    @Bean
    public UserAspectjTwo userAspectjTwo() {
        return new UserAspectjTwo();
    }

}
  1. Test updateName() with UserAspectjTwo

    • 6.1 先往数据库里添加一条数据
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("jared");
        userDao.save(user);
    }
    
    添加User
    添加User
- 6.2 测试正常执行updateName()
``` java
@Test
public void testUpdateName() {
    userService.update("jared qiu", 1);
}
```

- 6.3.1 打印结果 

![输出结果][10]

- 6.3.2 数据库结果

![数据库结果][11]

- 6.4 测试非正常执行updateName(),只需要把UserDao类中updateName()上的@Modifying或者@Transactional注解去掉即可
``` java
@Test
public void testUpdateName() {
    userService.update("error jared qiu", 1);
}
```

- 6.5.1 打印结果 

![输出结果][12]

- 6.5.2 数据库结果

![数据库结果][13]

第三种风格的切面

  1. Create Aspect(使用了@Around这个环绕注解)
@Aspect
public class UserAspectjThree {

    @Resource
    private UserService userService;

    /**
     * 方法的返回值类型须与切面所在方法的返回值类型保持一致
     */
    @Around("execution(* com.example.aspectj.UserDao.updateName(..))")
    public int around(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("3.------------before()");
            System.out.println("3.------------after()");
            joinPoint.proceed();//用于启动目标方法执行(必须)
            System.out.println("3.------------afterReturning()");
            User user = new User();
            user.setName("afterReturning3");
            userService.save(user);
        } catch (Throwable e) {
            System.out.println("3.------------afterThrowing()");
            User user = new User();
            user.setName("afterThrowing3");
            userService.save(user);
        }
        return 1;
    }

}
  1. Create Configuration
@Configuration
public class AspectjConfiguration {

    @Bean
    public UserAspectjThree userAspectjThree() {
        return new UserAspectjThree();
    }

}
  1. Test updateName() with UserAspectjThree

    • 6.1 先往数据库里添加一条数据
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("jared");
        userDao.save(user);
    }
    
    添加User
    添加User
    • 6.2 测试正常执行updateName()
    @Test
    public void testUpdateName() {
        userService.update("jared qiu", 1);
    }
    
    • 6.3.1 打印结果
    输出结果
    输出结果
    • 6.3.2 数据库结果
    数据库结果
    数据库结果
    • 6.4 测试非正常执行updateName(),只需要把UserDao类中updateName()上的@Modifying或者@Transactional注解去掉即可
    @Test
    public void testUpdateName() {
        userService.update("error jared qiu", 1);
    }
    
    • 6.5.1 打印结果
    输出结果
    输出结果
    • 6.5.2 数据库结果
    数据库结果
    数据库结果

第四种风格的切面

  1. Create Aspect(依旧使用了@Around这个环绕注解,但加入了@Pointcut注解和传递了参数)
@Aspect
public class UserAspectjFour {

    @Resource
    private UserService userService;

    @Pointcut("execution(* com.example.aspectj.UserDao.updateName(String,*)) && args(name,*)")
    public void pointcut(String name) {
    }

    @Around(value = "pointcut(name)", argNames = "joinPoint,name")
    public int around(ProceedingJoinPoint joinPoint, String name) {
        try {
            System.out.println("4.------------before()");
            System.out.println("4.------------after()");
            Object proceed = joinPoint.proceed();
            System.out.println(proceed);
            System.out.println("4.------------afterReturning()");
            User user = new User();
            user.setName("afterReturning4" + name);
            userService.save(user);
        } catch (Throwable e) {
            System.out.println("4.------------afterThrowing()");
            User user = new User();
            user.setName("afterThrowing4" + name);
            userService.save(user);
        }
        return 1;
    }

}
  1. Create Configuration
@Configuration
public class AspectjConfiguration {

    @Bean
    public UserAspectjFour userAspectjFour() {
        return new UserAspectjFour();
    }

}
  1. Test updateName() with UserAspectjFour

    • 6.1 先往数据库里添加一条数据
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("jared");
        userDao.save(user);
    }
    
    添加User
    添加User
    • 6.2 测试正常执行updateName()
    @Test
    public void testUpdateName() {
        userService.update("jared qiu", 1);
    }
    
    • 6.3.1 打印结果
    输出结果
    输出结果
    • 6.3.2 数据库结果
    数据库结果
    数据库结果
    • 6.4 测试非正常执行updateName(),只需要把UserDao类中updateName()上的@Modifying或者@Transactional注解去掉即可
    @Test
    public void testUpdateName() {
        userService.update("error jared qiu", 1);
    }
    
    • 6.5.1 打印结果
    输出结果
    输出结果
    • 6.5.2 数据库结果
    数据库结果
    数据库结果

扩展@EnableAspectJAutoProxy

  • 表示开启AOP代理自动配置,如果配@EnableAspectJAutoProxy表示使用cglib进行代理对象的生成;设置@EnableAspectJAutoProxy(exposeProxy=true)表示通过aop框架暴露该代理对象,使得aopContext能够直接访问
  • 从@EnableAspectJAutoProxy的定义可以看出,它引入AspectJAutoProxyRegister.class对象,该对象是基于注解@EnableAspectJAutoProxy注册了一个AnnotationAwareAspectJAutoProxyCreator,通过调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry),注册了一个aop代理对象生成器
@EnableAspectJAutoProxy
@EnableAspectJAutoProxy
AspectJAutoProxyRegistrar
AspectJAutoProxyRegistrar

参考链接

AspectJ
Spring AOP系列
Spring AOP中JoinPoint的表达式定义描述

目录
相关文章
|
6月前
|
前端开发 Java 数据库
浅谈Spring AOP 面向切面编程 最通俗易懂的画图理解AOP、AOP通知执行顺序~
浅谈Spring AOP 面向切面编程 最通俗易懂的画图理解AOP、AOP通知执行顺序~
|
7月前
|
XML 监控 安全
Spring特性之一——AOP面向切面编程
Spring特性之一——AOP面向切面编程
83 1
|
4月前
|
Java Spring XML
掌握面向切面编程的秘密武器:Spring AOP 让你的代码优雅转身,横切关注点再也不是难题!
【8月更文挑战第31天】面向切面编程(AOP)通过切面封装横切关注点,如日志记录、事务管理等,使业务逻辑更清晰。Spring AOP提供强大工具,无需在业务代码中硬编码这些功能。本文将深入探讨Spring AOP的概念、工作原理及实际应用,展示如何通过基于注解的配置创建切面,优化代码结构并提高可维护性。通过示例说明如何定义切面类、通知方法及其应用时机,实现方法调用前后的日志记录,展示AOP在分离关注点和添加新功能方面的优势。
71 0
|
5月前
|
Java Spring 容器
Spring问题之Spring AOP是如何实现面向切面编程的
Spring问题之Spring AOP是如何实现面向切面编程的
|
5月前
|
设计模式 缓存 程序员
Spring6(三):面向切面AOP(1)
Spring6(三):面向切面AOP(1)
44 1
|
6月前
|
缓存 Java 测试技术
Spring 框架,不只是依赖注入和面向切面那么简单!
【6月更文挑战第25天】Spring框架超越DI和AOP,涵盖事务管理、数据访问抽象如`JdbcTemplate`、消息驱动支持如`@JmsListener`、缓存管理和测试工具。示例代码展示了其简化复杂性的能力,如自动事务处理、数据库操作及消息队列监听。Spring是构建高效企业级应用的全面解决方案。
45 4
|
5月前
|
Java Spring
Spring AOP(面向切面编程)详解
Spring AOP(面向切面编程)详解
|
5月前
|
XML Java 数据格式
Spring6(三):面向切面AOP(3)
Spring6(三):面向切面AOP(3)
37 0
|
5月前
|
SQL 安全 Java
Spring6(三):面向切面AOP(2)
Spring6(三):面向切面AOP(2)
34 0
|
7月前
|
XML 监控 安全
18:面向切面编程-Java Spring
18:面向切面编程-Java Spring
177 5