65 切面AOP

65 切面AOP

切面基础概念

  • AOP:Aspect Oriented Programming,面向切面编程。是通过预编译方式(aspectj)或者运行期动态代理(Spring)实现程序功能的统一维护的技术。

面试问题:
Spring的两大核心是什么:IoC和AOP
分别有什么作用
IoC:控制反转,目的用于解耦,底层使用的技术是反射+工厂模式
AOP:面向切面编程,目的是在不修改目标对象源码的情况下,进行功能增强,底层使用的是动态代理技术

  • AOP作用:不修改源码的情况下,进行功能增强,通过动态代理实现的;
  • AOP的底层是通过动态代理实现的。在运行期间,通过代理技术动态生成代理对象,代理对象方法执行时进行功能的增强介入,再去调用目标方法,从而完成功能增强。
  • 常用的动态代理技术有:
    • JDK的动态代理:基于接口实现的
    • cglib的动态代理:基于子类实现的
  • Spring的AOP采用了哪种代理方式?
    • 如果目标对象有接口,就采用JDK的动态代理技术
    • 如果目标对象没有接口,就采用cglib技术
  • AOP相关概念

    • 目标对象(Target):要代理的/要增强的目标对象。

    • 代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象

    • 连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法。能增强的方法

    • 切入点(PointCut):要对哪些连接点进行拦截的定义。要增强的方法

    • 通知/增强(Advice):拦截到连接点之后要做的事情。如何增强,额外添加上去的功能和能力

    • 切面(Aspect):是切入点和通知的结合。 告诉Spring的AOP:要对哪个方法,做什么样的增强

    • 织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程
      在这里插入图片描述

  • AOP通知类型

    • @Before 前置通知 通知方法会在目标方法之前执行
    • @After 后置通知 通知方法会在目标方法之后执行
    • @Around 环绕通知 可以在目标方法之前和之后执行,可以更改方法执行之后的结果,自主性最大,需要掌握
    • @AfterReturning 通知方法将在目标方法正常结束之后执行
    • @AfterThrowing 通知方法会在目标方法爆出异常之后执行

切面应用

为了更好对AOP的使用有更深的了解,这里我们以下面这个场景进行实现:

现在在项目中有一些接口需要进行权限校验,即某些用户是无权访问这个接口的,如果无权限应进行拦截。同时这个接口很重要,应该避免某些人员进行恶意的刷接口,所以我们需要对这个接口进行防刷。对某些恶意刷的IP进行封禁,并进行限流,请你基于AOP来完成这个功能。

完整代码请查看:点击查看

注解

那么我们首先就需要写一个注解,来标识这种需要进行处理的接口。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPermission {
    
}

这里介绍一下元注解的概念

@Retention

中文翻译为保留的意思,标明自定义注解的生命周期。

从编写Java代码到运行主要周期为源文件→ Class文件 → 运行时数据,@Retention则标注了自定义注解的信息要保留到哪个阶段,分别对应的value取值为SOURCE →CLASS→RUNTIME。

  1. SOURCE 源代码java文件,生成的class文件中就没有该信息了
  2. CLASS class文件中会保留注解,但是jvm加载运行时就没有了
  3. RUNTIME 运行时,如果想使用反射获取注解信息,则需要使用RUNTIME,反射是在运行阶段进行反射的
  4. 示例:当RentionPolicy取值为SOURCE时,Class文件中不会保留注解信息,而取值为CLASS时,Class反编译文件中则保留了注解的信息
  1. Source:一个最简单的用法,就是自定义一个注解例如@ThreadSafe,用来标识一个类时线程安全的,就和注释的作用一样,不过更引人注目罢了。
  2. Class:这个有啥用呢?个人觉得主要是起到标记作用,还没有做实验,例如标记一个@Proxy,JVM加载时就会生成对应的代理类。
  3. Runtime:反射实在运行阶段执行的,那么只有Runtime的生命周期才会保留到运行阶段,才能被反射读取,也是我们最常用的。
@Target

中文翻译为目标,描述自定义注解的使用范围,允许自定义注解标注在哪些Java元素上(类、方法、属性、局部属性、参数…)

在这里插入图片描述

切面

/**
 * @FileName SecurityAspect
 * @Description 权限校验 限流 防刷 AOP切面
 * @Author yaoHui
 * @date 2024-10-09
 **/
@Aspect
@Slf4j
@Component
public class SecurityAspect {

    // 用于记录每个IP的请求时间戳,用于限流
    private Map<String, List<Long>> requestTimestamps = new ConcurrentHashMap<>();

    // 用于封禁IP的列表
    private Set<String> bannedIps = ConcurrentHashMap.newKeySet();

    // 限流阈值
    private static final int MAX_REQUESTS = 10;  // 最多允许的请求次数
    private static final long TIME_WINDOW_MS = 10 * 1000; // 时间窗口,10秒

    // 切入点 指定被注解CheckPermission标记的就是连接点
    @Pointcut("@annotation(com.fang.screw.communal.annotation.CheckPermission)")
    public void checkPermission(){

    }

    /***
    * @Description 对连接点进行业务扩展 进行鉴权 限流 防刷等操作
    * @param proceedingJoinPoint
    * @return {@link Object }
    * @Author yaoHui
    * @Date 2024/10/9
    */
    @Around("checkPermission()")
    public Object doPermissionCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String ipAddress = request.getRemoteAddr();
        UserPO userPO = CurrentUserHolder.getUser();

        // 检查IP是否被封禁
        if (bannedIps.contains(ipAddress)) {
            return R.failed("该IP已被封禁,请稍后再试!");
        }

        // 非超级管理员不可以访问该接口
        if (userPO.getUserType() != 0){
            return R.failed("无访问权限!");
        }

        // 限流机制
        if (isRequestTooFrequent(ipAddress)) {
            bannedIps.add(ipAddress);  // 封禁该IP
            log.warn("IP {} 被封禁,因短时间内过多请求", ipAddress);
            return R.failed("请求过于频繁,请稍后再试!");
        }

        return proceedingJoinPoint.proceed();
    }

    /***
    * @Description 判断请求是否过于频繁
    * @param ipAddress
    * @return {@link boolean }
    * @Author yaoHui
    * @Date 2024/10/9
    */
    private boolean isRequestTooFrequent(String ipAddress) {
        long currentTime = System.currentTimeMillis();
        requestTimestamps.putIfAbsent(ipAddress, new ArrayList<>());
        List<Long> timestamps = requestTimestamps.get(ipAddress);

        timestamps.add(currentTime);

        // 清理超过时间窗口的请求
        timestamps.removeIf(timestamp -> currentTime - timestamp > TIME_WINDOW_MS);

        return timestamps.size() > MAX_REQUESTS;
    }
}

权限注解的使用

    /***
    * @Description 查询分类文章List
    * @return {@link PoetryResult<Map<Integer,List<ArticleVO>>> }
    * @Author yaoHui
    * @Date 2024/9/22
    */
    @CheckPermission
    @GetMapping("/getListSortArticle")
    public R<Map<Integer, List<ArticleVO>>> getListSortArticle() {
        return articleService.getListSortArticle();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值