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

作者: 狄仁杰666 | 来源:发表于2020-10-22 14:30 被阅读0次

    前言

    来啦老铁!

    笔者学习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有一些术语:

    • Aspect;
    • Joint point;
    • Pointcut;
    • Advice;
    • AOP proxy;
    • Weaving;

    这些术语对我们理解、实践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(),如:
    • execution(public * *(...));
    • execution(* set*(...));
    • execution(public set*(...));
    • execution(public com.xyz.service..set(...));
    (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案例,整个过程简单而不失灵活,灵活而不失优雅,有没有?

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

    谢谢!

    相关文章

      网友评论

        本文标题:Spring Boot之面向切面编程:Spring AOP

        本文链接:https://www.haomeiwen.com/subject/cnuemktx.html