【Java框架】Spring框架(二)——Spring基本核心(AOP)

news2025/1/8 4:53:45

目录

  • 面向切面编程AOP
    • AOP的目标:让我们可以“专心做事”
      • 专心做事
      • 专心做事解决方案1.0
      • 专心做事解决方案2.0
        • 蓝图
    • AOP应用场景
    • AOP原理
    • AOP相关术语
      • 术语理解
  • AOP案例实现
    • 前置/后置/异常/最终增强的配置实现
      • 1.依赖
      • 2.业务类
      • 3.日志类
      • 4.配置
        • 切入点表达式匹配规则举例
    • 环绕增强的配置实现(1替4)
      • 1.service类不变
      • 2.日志类
      • 3.配置
    • Spring AOP配置元素
    • 注解实现AOP
      • 1.service类不变
      • 2.日志类
      • 3.配置

面向切面编程AOP

AOP的目标:让我们可以“专心做事”

专心做事

  • 我们作为开发系统可以分为两大部分:业务功能和辅助业务的隐性功能。
  • 例如开发一个商城系统,商品模块的商品添加业务就是业务功能;
  • 而保证商品添加不出问题,如程序在执行商品添加时的入参/出参记录、事务处理等就属于辅助业务的隐性功能
  • 通常这些隐性的辅助功能一来都比较通用,二来跟业务平没有什么联系。
  • 因此就需要考虑将这些通用功能集中处理,来简化编码、专心做事。

专心做事解决方案1.0

解决方案:把公共代码抽取到一个父类的BaseService,让具体的模块Service继承BaseService,然后调用父类的功能,这样做即:通过继承的方式实现对业务方法的功能的增强。如:
通用功能类

public class BaseService {    
    // 新增事务
    public void transaction(){
        System.out.println("事务功能");
    }   
    // 入参日志
    public void before(){
        System.out.println("记录入参日志功能");
    }
    // 出参日志
    public void afterReturnLog(){
        System.out.println("记录出参日志功能");
    }
    // 资源最终关闭
    public void afterLog(){
        System.out.println("资源close功能...");
    }
}

业务类

public class UserServiceImpl extends BaseService{
    public void save(User user){
        super.before();
        super.transaction();
        // 调用Dao新增用户
        super.afterReturnLog();
        super.after();
    }
}
public class GoodsServiceImpl extends BaseService{
    public void save(Goods goods){
        super.before();
        super.transaction();
        // 调用Dao新增商品
        super.afterReturnLog();
        super.after();
    }
}

但是这样做还有一个问题,虽然我们其他模块也需要此功能时,可以采用继承的方式来做,但是一个很严重的问题是:我们的业务功能在去调用的时候,对业务功能的增强实际上还是硬编码了。还是没有解决方便维护的问题,那我们期望能够解决的问题是:能否“神不知鬼不觉”的在不改变源代码的情况下去扩展功能呢? 答案肯定是可以的,那这就是AOP,同时,这个也是我们学习AOP的原因所在,也是AOP的作用所在。

专心做事解决方案2.0

蓝图

在这里插入图片描述
如果从系统的横向角度来看,我们的日志功能,事务功能、资源关闭功能是把系统的各个模块中的各个业务方法在执行之前从前面拦截了,好像拿了一把刀把一个完整的苹果切成一半,形成了一个切面。这个也就是 "面向切面编程 中切面的含义

AOP应用场景

日志记录、性能统计、安全控制、事务处理、异常处理

AOP原理

  • 将复杂的需求分解出不同方面,将散布在系统中的公共功能集中解决
  • 采用代理机制组装起来运行,在不改变原程序的基础上对代码段进行增强处理,增加新的功能
  • 核心:动态代理
    在这里插入图片描述

AOP相关术语

  • 增强处理/通知(Advice)
    所谓通知/增强是指拦截到 Joinpoint 之后所要做的事情就是通知。说白了,就是一段功能性的代码。
    • 前置增强
    • 后置增强
    • 环绕增强、异常抛出增强、最终增强等类型
  • 切入点(Pointcut):
    切入点就是对连接点中哪些点进行拦截的定义,对连接点(一般所有方法都可做连接点)进行条件筛选。
  • 连接点(Join Point):
    连接点就是可以被拦截的点,在程序中,通常指的是方法,在Spring中只支持方法类型的连接点。在其他的地方可能支持类,这里记住方法就行了。
  • 切面(Aspect):
    是切入点和通知/增强的结合。
  • 目标对象(Target object)
  • AOP代理(AOP proxy)
  • 织入(Weaving):
    指的是在把增强的方法加入到目标对象(切点方法的拥有者)来创建新的对象的过程,spring采用的是动态代理织入(jdk动态代理和子类动态代理都有),AspectJ采用编译期织入和类装载织入。

术语理解

用LOL中远古龙BUFF来理解吧:比如你现在在蓝色方,蓝色方刚刚跟对面打了一波5V5团战,1换5,蓝色方辅助挂了,这个时候已经30分钟后了,你们开始去打远古龙,打掉远古龙的时候辅助还没有复活,因此只有你们四个人获得了远古龙BUFF。

  • Joint point(连接点): 在上面的故事里,你们四个有了远古龙BUFF,就是都能被增强,可以看到远古龙BUFF是可以作用在上面所有人的身上,因为如果辅助不挂他也会有BUFF。在Spring中,这些人就等于可以被增强的方法。
  • Pointcut(切点): 在上面的故事里,只有你们四个有远古龙BUFF,但辅助没有。可以看出来Pointcut(切点)是有条件的Joint point(连接点),活着的人被增强了。
  • Advice(通知/增强): 你们四个在打掉远古龙的时候活着,那么你们四个就被增强了。
  • Aspect(切面): 切点和通知的结合,上面的切点就是活着的你们四个(四个点),通知是打掉远古龙后获得的BUFF,切点有很多,连载一起就像一个面一样。

AOP案例实现

在这里插入图片描述在这里插入图片描述

前置/后置/异常/最终增强的配置实现

1.依赖

	<dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
      <scope>provided</scope>
    </dependency>


    <!-- aop依赖 -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

2.业务类

@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;
    @Override
    public Object saveUser(String userName) {
        //log.info("调用org.aop.service.impl.UserServiceImpl的saveUser(),入参是{}",user);
        boolean result = userMapper.insertUser(userName) > 0;
        //log.info("调用org.aop.service.impl.UserServiceImpl的saveUser()完毕,返回值是{}",result);
        return result;
    }

    @Override
    public boolean updateUser(User user) {
        //log.info("调用org.aop.service.impl.UserServiceImpl的updateUser(),入参是{}",user);
        boolean result = userMapper.updateUser(user) > 0;
        //log.info("调用org.aop.service.impl.UserServiceImpl的updateUser()完毕,返回值是{}",result);
        return result;
    }
}

3.日志类

package org.aop.log;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @author: zjl
 * @datetime: 2024/3/30
 * @desc:
 */
@Slf4j
public class MyServiceLogger {
	//前置增强
    public void before(JoinPoint jp) {
        log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
                getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
    }
	//后置增强
    public void afterReturning(JoinPoint jp, Object result) {
        log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                getName() + " 方法。方法返回值:" + result);
    }
	//异常处理增强
    public void afterThrowing(JoinPoint jp, Throwable e) {
        log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                getName() + " 方法。方法抛出异常:" + e);
    }
	//最终增强
    public void after(JoinPoint jp) {
        log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                getName() + " 方法。方法执行完毕。模拟关闭资源...");
    }

4.配置

在这里插入图片描述

增强处理类型特 点
Before前置增强处理,在目标方法前织入增强处理
AfterReturning后置增强处理,在目标方法正常执行(不出现异常)后织入增强处理
AfterThrowing异常增强处理,在目标方法抛出异常后织入增强处理
After最终增强处理,不论方法是否抛出异常,都会在目标方法最后织入增强处理
Around环绕增强处理,在目标方法的前后都可以织入增强处理
<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <!--                    配置相关的AOP                -->
    <bean id="myServiceLog" class="org.aop.log.MyServiceLogger"/>
    <aop:config>
        <aop:pointcut id="pointcut"
                      expression="execution(* org.aop.service..*.*(..)))"/>
        <aop:aspect ref="myServiceLog">
            <aop:before method="before"
                        pointcut-ref="pointcut"></aop:before>
            <aop:after-returning method="afterReturning"
                                 pointcut-ref="pointcut" returning="result"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
切入点表达式匹配规则举例
public * addNewUser(entity.User): “*”表示匹配所有类型的返回值。
public void *(entity.User): “*”表示匹配所有方法名。
public void addNewUser(..): “..”表示匹配所有参数个数和类型。
* com.zjl.service.*.*(..):匹配com.zjl.service包下所有类的所有方法。
* com.zjl.service..*.*(..):匹配com.zjl.service包及其子包下所有类的所有方法

环绕增强的配置实现(1替4)

1.service类不变

2.日志类

注释掉前面四种增强的方法,加入这个环绕增强的方法

    public Object around(ProceedingJoinPoint jp) throws Throwable {
        Object result = null;
        try {
            log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                    getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
            result = jp.proceed();
            log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                    getName() + " 方法。方法返回值是:" + result);
        }catch (Exception e){
            log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                    getName() + " 方法。发生了异常,异常信息为:" + e);
        }finally {
            log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                    getName() + " 方法,进行最终处理,比如模拟资源关闭");
        }
        return result;
    }

3.配置

    <!--                    配置相关的AOP                -->
    <bean id="myServiceLog" class="org.aop.log.MyServiceLogger"/>
    <aop:config>
        <aop:pointcut id="pointcut"
                      expression="execution(* org.aop.service..*.*(..)))"/>
        <aop:aspect ref="myServiceLog">
            <!--<aop:before method="before"
                        pointcut-ref="pointcut"></aop:before>
            <aop:after-returning method="afterReturning"
                                 pointcut-ref="pointcut" returning="result"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
            <aop:after method="after" pointcut-ref="pointcut"/>-->

            <aop:around method="around" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

Spring AOP配置元素

AOP配置元素描 述
<aop:config>AOP配置的顶层元素,大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:pointcut>定义切点
<aop:aspect>定义切面
<aop:after>定义最终增强(不管被通知的方法是否执行成功)
<aop:after-returning>定义after-returning增强
<aop:after-throwing>定义after-throwing增强
<aop:around>定义环绕增强
<aop:before>定义前置增强
<aop:aspectj-autoproxy>启动@AspectJ注解驱动的切面

注解实现AOP

1.service类不变

2.日志类

package org.aop.log;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @author: zjl
 * @datetime: 2024/3/30
 * @desc:
 */
@Slf4j
@Aspect
@Component
public class MyServiceLogger {
    //1.针对service实现类中所有方法,记录某个方法在被调用时的入参信息
    //@Before("execution(* org.aop.service..*.*(..))")
    public void before(JoinPoint jp) {
        log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().
                getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
    }


    //2.针对service实现类中所有方法,记录某个方法被调用后的返回值信息
    //@AfterReturning(returning = "result", pointcut = "execution(* org.aop.service..*.*(..))")
    public void afterReturning(JoinPoint jp, Object result) {
        log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                getName() + " 方法。方法返回值:" + result);
    }

    //@AfterThrowing(throwing = "e", pointcut = "execution(* org.aop.service..*.*(..))")
    public void afterThrowing(JoinPoint jp, Throwable e) {
        log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                getName() + " 方法。方法抛出异常:" + e);
    }

   // @After("execution(* org.aop.service..*.*(..))")
    public void after(JoinPoint jp) {
        log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                getName() + " 方法。方法执行完毕。模拟关闭资源...");
    }

    @Around("execution(* org.aop.service..*.*(..))")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        Object result = null;
        try {
            log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                    getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
            result = jp.proceed();
            log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                    getName() + " 方法。方法返回值是:" + result);
        }catch (Exception e){
            log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                    getName() + " 方法。发生了异常,异常信息为:" + e);
        }finally {
            log.info("调用 " + jp.getTarget() + " 的 " +  jp.getSignature().
                    getName() + " 方法,进行最终处理,比如模拟资源关闭");
        }
        return result;
    }
}

3.配置

<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <context:component-scan base-package="org.aop"/>
    <aop:aspectj-autoproxy />
</beans>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1601936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【微信小程序】分包

整个小程序所有分包大小不超过 20M&#xff08;开通虚拟支付后的小游戏不超过30M&#xff09; 单个分包/主包大小不能超过 2M在小程序启动时&#xff0c;默认会下载主包并启动主包内页面&#xff0c;当用户进入分包内某个页面时&#xff0c;客户端会把对应分包下载下来&#xf…

13段×5位LED数码管驱动数显驱动IC抗干扰数码屏驱动芯片VK1624 SOP24/DIP24

产品型号&#xff1a;VK1624 产品品牌&#xff1a;永嘉微电/VINKA 封装形式&#xff1a;SOP24/ DIP24 工程服务&#xff0c;技术支持&#xff01; 概述 VK1624是一种数码管或点阵LED驱动控制专用芯片&#xff0c;内部集成有3线串行接口、数据锁存器、LED 驱动等电路。SEG脚…

Java中的容器,线程安全和线程不安全

Java中的容器主要指Java集合框架中的一系列类&#xff0c;它们提供了存储和操作对象的能力。在讨论容器的线程安全性时&#xff0c;我们可以将其分为两大类&#xff1a; 线程安全的容器&#xff1a; Vector: 这是ArrayList的线程安全版本&#xff0c;所有方法都被同步以确保在…

Win10本地更新无法升级win11 的0x80080005解决方法

Win10本地更新无法升级win11 Visual Studio 2022 运行项目时&#xff0c;本文提供了错误“指定的程序需要较新版本的 Windows”的解决方法。 更新时提示&#xff1a;0x80080005 解决方法 1、下载Windows11InstallationAssistant.exe 【免费】Windows11InstallationAssista…

ESP32 S3音频开发

1. 音频硬件框架 Codec&#xff1a;音频编解码芯片&#xff0c;一种低功耗单声道音频编解码器&#xff0c;包含单通道 ADC、单通道 DAC、低噪声前置放大器、耳机驱动器、数字音效、模拟混音和增益功能。它通过 I2S 和 I2C 总线与 ESP32-S3-WROOM-1 模组连接&#xff0c;以提供独…

FFmpeg: 自实现ijkplayer播放器--01项目简介

文章目录 项目介绍流程图播放器实现过程界面展示项目代码 项目介绍 此项目基于FFmeg中 ffplay.c进行二次开发&#xff0c;实现基本的功能&#xff0c;开发软件为Qt 项目优势&#xff1a; 参考ijkplayer播放器&#xff0c;实现UI界面和播放器核心进行解耦&#xff0c;容易添加…

【已解决】CondaError: Downloaded bytes did not match Content-Length

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《AI实战中的各种bug…

树莓派驱动开发--搭建环境篇(保姆级)

前言&#xff1a;树莓派的环境搭建关系到之后的驱动开发&#xff0c;故一个好的环境能让你顺手完成驱动开发&#xff01;我使用的是64位树莓派4b&#xff01;有显示屏的前提&#xff01;&#xff01;&#xff01;&#xff08;因为wifi连接太刁钻了&#xff09; 一、ubantu相关 …

土壤湿度传感器:助力农业现代化

随着科技的飞速发展&#xff0c;越来越多的先进技术被应用到农业生产中。其中&#xff0c;土壤湿度传感器作为现代农业的重要工具&#xff0c;正逐渐改变着传统农业的生产方式&#xff0c;成为农业现代化的秘密武器。 精确监测&#xff1a;土壤湿度传感器能够实时、精确地监测土…

steam小白和新手教学——steam账号注册教程

steam是一个非常流行的游戏平台&#xff0c;提供了丰富的游戏资源和社交功能。访问Steam官方网站并注册一个账户&#xff0c;需要提供个人信息&#xff0c;如邮箱和密码&#xff0c;完成验证后即可登录。下载并安装Steam客户端。浏览和购买游戏。登录后&#xff0c;可以在Steam…

预算有限的中小企业如何挑选财务记账软件?专家建议解读!

对于预算有限的中小企业而言&#xff0c;如何在众多市场上的财务软件中作出明智选择&#xff0c;既能满足基本的记账需求&#xff0c;又能适应企业未来的成长&#xff0c;是一项极具战略意义的任务。 本文探讨了中小企业财务记账软件的关键功能&#xff0c;并为大家推荐了几款…

女生给你分享音乐是啥意思?是不是暗示喜欢!

如果你正在和女生聊天&#xff0c;女生突然给你分享了一首音乐&#xff1b;或者你还没找女生聊天呢&#xff0c;女生就主动给你发了一个音乐分享过来……那这些情况下女生给你分享音乐是啥意思&#xff1f;女生是单纯分享音乐还是想用音乐暗示喜欢你呢&#xff1f; 一、女生单…

第47期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

URLConnection、HttpURLConnection、OKHttpClient分析

URLConnection和HttpURLConnection都是用于建立(应用层的)网络连接的类。 URLConnection是一种通用的连接方式&#xff0c;它支持多种协议&#xff0c;如 HTTP、HTTPS、FTP 等。 HttpURLConnection是URLConnection的子类&#xff0c;提供了更多针对HTTP协议的功能。如果仅是简…

【redis基础01】数据结构-通用命令、String、Hash、List、Set

目录 1 通用命令1.1 keys 检索匹配特定模式的所有键1.2 del 删除指定的key1.3 exists 判断key是否存在1.4 expire 给key设定有效期1.5 ttl 查看key的剩余有效期1.6 key的层级格式 2 String类型2.1 set&get2.2 mset&mget 批量操作2.3 incr&incrbyfloat 自增2.4 setn…

RabbitMQ-交换机

文章目录 交换机fanoutDirecttopicHeadersRPC 交换机 **交换机 **是消息队列中的一个组件&#xff0c;其作用类似于网络路由器。它负责将我们发送的消息转发到相应的目标&#xff0c;就像快递站将快递发送到对应的站点&#xff0c;或者网络路由器将网络请求转发到相应的服务器…

杀死那个名为360安全的软件

背景 2023年底&#xff0c;闲来没事想起了xjun师傅2021年发的procexp驱动利用帖子时在群里讨论的&#xff0c;通过procexp驱动突破PPL后注入到csrss进程中&#xff0c;再通过csrss来结束那些个安全防护软件。于是在当时就有了如下成果&#xff1a; 这些弄完之后&#xff0c;觉…

康谋技术 | 深入探讨:自动驾驶中的相机标定技术

随着自动驾驶技术的快速发展&#xff0c;多传感器的数据采集和融合可以显著提高系统的冗余度和容错性&#xff0c;进而保证决策的快速性和正确性。在项目开发迭代过程中&#xff0c;传感器标定扮演着至关重要的角色&#xff0c;它位于数据采集平台与感知融合算法之间&#xff0…

关于外网后端服务访问内网minio中间件,因连接minio超时,启动失败问题

注&#xff1a;服务器情况&#xff1a;2台服务器&#xff0c;内网服务器包含&#xff08;activemq、minio、nginx、redis、mysql、后端java服务&#xff09;。外网服务器只有后端java服务&#xff0c;访问内网的中间件&#xff08;内网服务器开放了部分指定端口&#xff09; 问…

01_FreeRTOS移植

FreeRTOS移植 FreeRTOS移植FreeRTOS移植-中断文件修改 FreeRTOS移植 内核移植时用到的文件 复制工程模板&#xff0c;不需要的源文件全部删除 复制到工程文件中 放到这里 注释掉这两个重复定义的中断 替换掉工程中的裸机延时 FreeRTOS移植-中断文件修改