Docker · Spring Boot · Kotlin · 微服务SpringBoot极简教程 · Spring Boot JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统

Spring Boot之面向切面编程:Spring AOP

2020-10-22  本文已影响0人  狄仁杰666

前言

来啦老铁!

笔者学习Spring Boot有一段时间了,附上Spring Boot系列学习文章,欢迎取阅、赐教:

  1. 5分钟入手Spring Boot;
  2. Spring Boot数据库交互之Spring Data JPA;
  3. Spring Boot数据库交互之Mybatis;
  4. Spring Boot视图技术;
  5. Spring Boot之整合Swagger;
  6. Spring Boot之junit单元测试踩坑;
  7. 如何在Spring Boot中使用TestNG;
  8. Spring Boot之整合logback日志;
  9. Spring Boot之整合Spring Batch:批处理与任务调度;
  10. Spring Boot之整合Spring Security: 访问认证;
  11. Spring Boot之整合Spring Security: 授权管理;
  12. Spring Boot之多数据库源:极简方案;
  13. Spring Boot之使用MongoDB数据库源;
  14. Spring Boot之多线程、异步:@Async;
  15. Spring Boot之前后端分离(一):Vue前端;
  16. Spring Boot之前后端分离(二):后端、前后端集成
  17. Spring Boot之前后端分离(三):登录、登出、页面认证

之前在刚学习Spring Boot的时候有看到AOP,还是挺容易的,但没有实践一下,而近期由于某些原因,几次被问及AOP,作为系统学习Spring Boot的咱们,当然不能落下Spring AOP!

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,是面向对象编程的补充,它提供了另外一种思路来实现应用系统的公共服务。AOP采用“横切”技术,解剖已封装的对象,将这种公共服务封装到一个可重用的模块中,这模块称之为“Aspect”,即“切面”。“切面”可降低系统代码冗余,降低模块间的耦合度,提升系统的可维护性。

AOP常见的使用场景:

1. 日志功能;

采用AOP之后,不需要在每一处功能中添加日志收集代码,而是在切面中统一完成这一步骤,提升了编程速度和代码整洁度!

2. 业务方法调用的权限管理;

采用AOP在处理权限管理,我们不用在所有业务代码处判断用户是否有权限调用此方法,而是在切面中统一完成这一步骤,减少了这种非核心业务的代码!

3. 数据库事务的管理;

采用AOP可以统一在执行数据库前先开启事务,在执行完成后提交事务,若执行出错,则回滚事务等。

4. 缓存方面;

我们可采用AOP技术,统一对数据进行缓存,在下次调用时,如果参数、条件等未变,则直接获取数据,而不再调取应用方法。

5. 等。

AOP有点拦截的感觉!

AOP有一些术语:

这些术语对我们理解、实践AOP没有太大阻碍,请自行脑补哈,我们直接上代码开始Demo!

项目代码已上传Git Hub仓库,欢迎取阅:

整体步骤

  1. 创建AOP演示项目;
  2. 引入AOP依赖;
  3. 创建演示用API;
  4. 编写AOP切面类;
  5. 验证AOP代码织入效果;

1. 创建AOP演示项目;

Spring Boot项目创建可参考文章:5分钟入手Spring Boot,此处不再介绍。

2. 引入AOP依赖;

在项目pom.xml中添加spring-boot-starter-aop依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
记得安装一下依赖:
mvn install -Dmaven.test.skip=true -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true

3. 创建演示用API;

项目内创建controller包,包内创建一个controller,如HelloWorldController.java,HelloWorldController内创建一个用于演示用的API:

package com.github.dylanz666.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author : dylanz
 * @since : 10/22/2020
 */
@RestController
public class HelloWorldController {
    @GetMapping("/api/hello")
    public String sayHello(@RequestParam String user) {
        return "Hello " + user;
    }
}

此处特地写了一个需要参数的API,我将把API处理过程进行横切,在API请求前后做一些系统级别的操作,但不影响业务过程。

4. 编写AOP切面类;

在项目内创建config包,在包内创建一个config类,如AOPConfig.java,在AOPConfig编写如下代码:

package com.github.dylanz666.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * @author : dylanz
 * @since : 10/22/2020
 */
@Configuration
@Aspect
public class AOPConfig {
    @Around("@within(org.springframework.web.bind.annotation.RestController)")
    public Object simpleAop(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        System.out.println("client ip:" + request.getRemoteAddr());

        Object[] args = proceedingJoinPoint.getArgs();
        System.out.println("args:" + Arrays.asList(args));

        Object object = proceedingJoinPoint.proceed();
        System.out.println("return: " + object);

        return object;
    }
}

稍微解读一下:

1). @Aspect,声明了这个类是个切面类;
2). @Around,声明了一个表达式,描述了要织入的目标特性;

比如本例@within表示目标类型带有注解,且其注解类型为 org.springframework.web.bind.annotation.RestController(如果API的注解用的是@Conrtoller,则此处为 org.springframework.stereotype.Controller),这样系统内所有RestController方法(Rest API,也即带有@RestController注解的controller类中的方法)被调用的时候,都会执行@Around注解的方法,也就是本例的simpleAop方法;
除了@Around(方法执行前后织入代码),还有@Before、@After、@AfterReturning、@AfterThrowing,他们均分别表示该织入代码用于执行方法前、执行方法后、方法返回后、方法抛出异常后,如:

@Before("@within(org.springframework.web.bind.annotation.RestController)")
public void before(JoinPoint joinPoint) throws Throwable {
    Object[] args = joinPoint.getArgs();
    System.out.println("args:" + Arrays.asList(args));

    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    assert attributes != null;
    HttpServletRequest request = attributes.getRequest();
    System.out.println("client ip:" + request.getRemoteAddr());
}

我们在执行方法前打印请求参数和客户端ip;

3). 除了Around注解的方法可以传ProceedingJionPoint类型的参数外,其余的几个都不能传ProceedingJionPoint类型的参数;
4). simpleAop(名字任意)是用来织入的代码,我们可以利用参数ProceedingJoinPoint提供的方法,来对请求前后进行系统级别操作。

例如本例在接收到API请求还未执行业务代码时将客户端ip、请求参数打印出来,然后在业务代码执行完成后未返回给客户端前,将返回结果先打印出来;

5). 通常当切面代码执行完后,我们需要继续执行应用代码,并将返回对象正常返回,Object object = proceedingJoinPoint.proceed();就是为了完成这一过程;
6). 除了@within这种切面目标匹配表达式外,Spring AOP还提供了多种可选的表达式及表达式组合:
(1). within();
(2). @within;
(3). execution(),如:
(4). target();
(5). @target;
(6). args();
(7). @args();
(8). @annotation();
(9). this();
(10). @Transactional;

等,读者可自行展开学习!

5. 验证AOP代码织入效果;

1). 项目整体结构:

项目整体结构

2). 启动项目:

启动项目

3). 访问API:

(手机在局域网内访问我们的应用路径:http://192.168.0.101:8080/api/hello?user=dylanz)

访问API

4). 后端执行切面代码:

后端执行切面代码

我们可以看到,在API执行前,打印了手机的ip地址:192.168.0.100,同时打印了请求参数值:dylanz,在API对应的方法执行后,打印方法返回的Hello dylanz字符串给客户端,然后将该字符串传给客户端,之后我们便能在手机客户端看到Hello dylanz字符串!

不难看出,我们可以将这些打印换成日志打印,就能全局收集详细的信息!
或者也可在切面中做一些缓存操作、数据库事务方面的行为等。

至此,我们完成了一个简单的Spring AOP案例,整个过程简单而不失灵活,灵活而不失优雅,有没有?

如果本文对您有帮助,麻烦点赞+关注!

谢谢!

上一篇 下一篇

猜你喜欢

热点阅读