SpringBoot使用AOP

简介: 众所周知AOP(Aspect Oriented Programming)是Spring的核心之一,是OOP面向对象编程的延续和补充,是面向切面编程,他的底层实现是代理模式,简单来说,代理模式分为静态代理模式和动态代理模式,而代理模式又分为JDK动态代理和CGLib代理,AOP则是基于动态代理实现,.

众所周知AOP(Aspect Oriented Programming)是Spring的核心之一,是OOP面向对象编程的延续和补充,是面向切面编程,他的底层实现是代理模式,简单来说,代理模式分为静态代理模式和动态代理模式,而代理模式又分为JDK动态代理和CGLib代理,AOP则是基于动态代理实现,默认是使用JDK动态代理,若没有接口则会使用CGLib代理,前者基于接口,后者基于子类,若兴趣深入了解代理模式的,可参考Java代理模式一文,下面简单说下AOP的基本概念.

AOP的适用场景:

  • 日志记录
  • 事务处理
  • 异常处理
  • 性能统计
  • 拦截鉴权
  • 缓存
  • 等等..

AOP的组成

  • Aspect(切面):通常是一个类,存放公共功能,可以在里面定义切入点和通知
  • JoinPoint(连接点):程序执行过程中可以插入的切面的点,一般是方法调用,异常抛出
  • Advice(通知):是切面的具体实现,在切入点上执行的逻辑处理,以目标方法为参照点,根据放置位置的不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around).
  • PointCut(切入点):带有通知的连接点

AOP通知类型(Advice)的介绍

  • @before(前置通知):在目标方法执行前先执行此方法
  • @after(后置):在目标方法执行后执行
  • @AfterReturning(最终返回):在目标方法正常完成之后
  • @AfterThrowing(异常通知):在目标方法抛出异常时执行
  • @Around(环绕通知):在目标方法执行前后都执行

下面是SpringBoot使用AOP实现的鉴权案例

先贴代码

 * @author :zoe
 * Target 的注解类型    适用场景
 *    TYPE              类(包括Enum)接口
 *    PACKAGE           包
 *    METHOD            方法
 *    FIELD             成员域(包括Enum常量)
 *    CONSTRUCTOR       构造器
 *    PARAMETER         方法或构造器参数
 *    LOCAL_VARIABLE    本地变量
 *    ANNOTATION_TYPE   注解类型声明
 *    java 8 新加
 *    TYPE_PARAMETER    类型参数声明
 *    TYPE_USE          类型的使用
 * Retention的保留策略
 *    保留规则           描述
 *    SOURCE             注释将被编译器丢弃,不包括在类文件中
 *    CLASS              注释由编译器记录在类文件中,但是不需要在运行时被虚拟机(VM)保留。默认策略
 *    RUNTIME            注释由编译器记录在类文件中,并在运行时由VM保存,因此可以反射可读取它们
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Login {
}

 * @author zoe
 **/
@Component
@Aspect
@Slf4j
public class LoginAop {

    @Pointcut("@annotation(com.zoe.aop.Login))")
    public void cut() {
    }

    @Before("cut()")
    public void before(){
        log.info("==================== 进入登录验证 ==================");
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert servletRequestAttributes != null;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String token = request.getHeader("token");
        if (ParamUtil.isEmpty(token)){
            throw new HttpException("当前未登录!");
        }
    }

    /**
     *
     */
    @After("cut(),execution(*com.zoe.aop.TestController.test(..))")
    public void after(){
        log.info("========================= 方法执行完毕,开始打印=========================");
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert servletRequestAttributes != null;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String method = request.getMethod();//请求方式
        log.info("请求方式: "+method);
        //获取请求资源
        String requestURI = request.getRequestURI();
        log.info("请求资源requestURI : "+requestURI);
        StringBuffer requestURL = request.getRequestURL();
        log.info("该方法的请地址是 :"+requestURL);
        //获取get请求参数
        String queryString = request.getQueryString();
        log.info("get请求参数 :"+queryString);
        //获取当前web应用名称
        String contextPath = request.getContextPath();
        log.info("当前web应用名称 :"+contextPath);
        //获取所有请求头名称
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String string =  headerNames.nextElement();
            log.info("请求头参数:"+string);
        }
        log.info("请求的IP地址为: " + getRealIp(request));
    }
    private static String getRealIp(HttpServletRequest request) {
        // 这个一般是Nginx反向代理设置的参数
        String ip = request.getHeader("X-Real-IP");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 处理多IP的情况(只取第一个IP)
        if (ip != null && ip.contains(",")) {
            String[] ipArray = ip.split(",");
            ip = ipArray[0];
        }
        return ip;
    }

}

public class LoginController {

    @GetMapping("/login")
    @Login
    public ResponseEntity login(){
        return ResponseEntity.ok("login success");
    }
}

以上案例是使用注解的方式用AOP实现的登录拦截,发起请求需要带token的请求头,若不带token的请求头,就会被拦截,提示当前未登录.
程序运行结果

1.不带token请求

image
image

2.带token请求

image
image

以上是使用注解的方式实现AOP,AOP也可用Execution表达式实现,注解方式适合切割较为分散的,如果一大片还是需要用Excution表达式来实现.,有兴趣,可自行实现.

分享在写案列时候的一个Java的坑

在判断请求头时,最先使用了java中的isEmpty()判空方法,结果异常的时候直接报500,不抛出定义的异常信息,最后发现,Java的isEmpty()方法判断是length长度,而判断的值可能是null.
image
image

isEmpty()方法的源码

image

我在以上案例使用判空方法是一个开源的工具包,有兴趣可以了解一下,附上链接tools-common.

目录
相关文章
|
6天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
36 8
|
5月前
|
SQL 监控 Java
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
这篇文章介绍了如何在IDEA和Spring Boot中使用AOP技术实现日志信息的记录到数据库的详细步骤和代码示例。
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
84 5
|
2月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
56 2
|
3月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
105 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
59 1
|
3月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
96 2
|
5月前
|
Java Spring 容器
SpringBoot整合AOP实现打印方法执行时间切面
SpringBoot整合AOP实现打印方法执行时间切面
57 1
|
8月前
|
XML 安全 Java
深入实践springboot实战 蓄势待发 我不是雷锋 我是知识搬运工
springboot,说白了就是一个集合了功能的大类库,包括springMVC,spring,spring data,spring security等等,并且提供了很多和可以和其他常用框架,插件完美整合的接口(只能说是一些常用框架,基本在github上能排上名次的都有完美整合,但如果是自己写的一个框架就无法实现快速整合)。
|
5月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
144 1