【拦截器、过滤器、springAop】那些不为人知的隐秘

news2024/9/21 10:38:08

首先说到这几个词的时候,大家肯定都很熟悉了,甚至觉得这几个的区别刚刚毕业都能回答了,但是我想大家在实际应用过程中是真得会真正的使用吗?换言之,什么时候用过滤器什么时候使用拦截器,什么时候使用springAop呢?很多时候不觉得这些都可以实现吗?比如日志的打印,我觉得都可以实现,那在设计架构的时候应该如何去考量呢?废话不多说,接下来我会用实际的案例来展示,大家看看是否真的如你们心里所想的呢?或者有么有更好的方案欢迎评论区留言!!!
首先说一些基础的它们之间的执行顺序
在这里插入图片描述

作用域大小不一样。

过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller
中渲染了对应的视图之后请求结束。

过滤器/拦截器与aop获取的信息不一样。过滤器/拦截器获取的是请求级别的信息,如url、传参、header、返回值等信息。

aop不能获取请求的参数等信息,和返回结果。aop能获取到调用的类,方法及方法参数。以及方法的返回值。

如果想通过方法进行拦截/过滤等处理,需要使用aop。如果想通过请求判断,需要使用过滤器。但是aop获取的是方法级别的信息,类、方法、方法的传参等等。

背景

现在有个业务场景就是通过前后端密文加解密实现参数传递,前端都是通过密文传递的并且是通过post请求方式的,后端之间使用对象来映射的,我们想通过上面的方式实现和业务解耦

方案讨论

过滤器可以获取到所有的request和response,是根据url匹配的
拦截器获取不到request和response参数,但是可以获取到所有的方法、类、参数等信息,也是根据url匹配的
springAop拦截器获取不到request和response参数,但是可以获取到所有的方法、类、参数等信息,是根据切点的也就是方法来匹配的
还有一点区别那就是如果是get请求可以很轻松获取但是如果是post那就要注意了,因为body的数据读取是通过流形式读取的,如果你在过滤器中读取到了,那么在controller中mvc参数映射的时候就会报错,因为拿不到body的值,因为流读取只会读取到一次,你在过滤器中读取到了,那么mvc参数映射的时候就无法读取不到,还有就是springAop或者是拦截器最早发生的时机也需要发生在mvc参数映射之后的,否则怎么能拿到参数信息呢,这点就是提下
那么既然这么说了,肯定有办法解决嘛,那就是你获取到了body的数据,就需要重新包装下然后set回去

springAop

import lombok.Data;

/**
 * 责任链接口
 *
 * @author fangh
 * @date 2023-02-09 11:22
 */
@Data
public abstract class ProcessingObject<T> {

    private ProcessingObject<T> successor;

    public T handle(T input, String apiName) {
        T t = handleWork(input, apiName);
        if (successor != null) {
            return successor.handle(t, apiName);
        }
        return t;
    }

    /**
     * 子类的具体处理方法
     *
     * @param input   待处理的参数
     * @param apiName 接口名
     * @return 处理后的参数
     */
    abstract protected T handleWork(T input, String apiName);
}
import com.fh.inter.mgmt.service.GetVehicleInfoService;
import com.fh.inter.mgmt.service.impl.GenerateReturnSign;
import com.fh.standard.config.SystemCommonConfig;
import com.fh.standard.constant.MdcConstant;
import com.fh.standard.dto.BaseDTO;
import com.fh.standard.enums.ApiEnum;
import com.fh.standard.enums.ApiResultEnum;
import com.fh.standard.exception.BusinessException;
import com.fh.standard.vo.BaseVO;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * <p>AOP 类,包含请求/响应日志打印、签名验证、车辆信息校验等</p>
 * <p>需要留意下 AOP 的执行顺序:</p>
 *
 * @author fangh
 * @date 2023-02-09 11:22
 */
@Aspect
@Component
@Order(0)
@Slf4j
@RequiredArgsConstructor
public class RequestAop {

    /**
     * 请求参数日志打印
     */
    private final RequestLogPrint p1;

    /**
     * 签名处理
     */
    private final ValidateSign p2;

    /**
     * 校验请求参数
     */
    private final CheckRequestParam p3;

    private final ThreadLocal<Map<String, String>> threadLocal = new ThreadLocal<>();

    /**
     * 返回报文签名生成
     */
    private final GenerateReturnSign generateReturnSign;

    private final GetVehicleInfoService getVehicleInfoService;

    private final SystemCommonConfig config;

    /**
     * Key:apiName 接口方法名 Value:apiDesc 接口方法名中文描述
     */
    private final static Map<String, String> METHOD_MAP = Maps.newHashMapWithExpectedSize(ApiEnum.values().length);

    static {
        METHOD_MAP.put(ApiEnum.RDS_REGISTER.getMethodName(), ApiEnum.RDS_REGISTER.getApiName());
        METHOD_MAP.put(ApiEnum.RDS_CHECK.getMethodName(), ApiEnum.RDS_CHECK.getApiName());
        METHOD_MAP.put(ApiEnum.RDS_TASK_INFO.getMethodName(), ApiEnum.RDS_TASK_INFO.getApiName());
        METHOD_MAP.put(ApiEnum.RDS_DOWNlOAD_STATE_REPORT.getMethodName(), ApiEnum.RDS_DOWNlOAD_STATE_REPORT.getApiName());
        METHOD_MAP.put(ApiEnum.RDS_REPORT_LOG.getMethodName(), ApiEnum.RDS_REPORT_LOG.getApiName());
    }

    /**
     * 对 Controller 做切点
     */
    @Pointcut("execution(* com.adups.inter.mgmt.controller..*.*(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        // 接口形参列表
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof BaseDTO) {
                BaseDTO baseDTO = (BaseDTO) arg;
                String methodName = currentMethod(joinPoint);
                // 请求接口名称
                String apiName = METHOD_MAP.get(methodName);
                // 请求参数日志打印 -> 非注册接口的车辆信息获取 -> 验签 -> ecuDids 解析 -> ecuPartNum 解析 -> exts 解析
                // 设置调用顺序
                p1.setSuccessor(p2);
                p2.setSuccessor(p3);
                // 开始按顺序链式处理
                p1.handle(baseDTO, apiName);
                break;
            }
        }
        /**
         * 此处因为在后置处理器中获取不到同线程的MDC值,只能通过此种方式传递
         */
        Map<String, String> signMap = Maps.newHashMapWithExpectedSize(1);
        signMap.put(MdcConstant.SIGN_KEY, MDC.get(MdcConstant.SIGN_KEY));
        signMap.put(MdcConstant.ENCRYPT_KEY, MDC.get(MdcConstant.ENCRYPT_KEY));
        threadLocal.set(signMap);
    }

    @AfterReturning(pointcut = "pointcut()", returning = "returnValue")
    public void doAfterReturn(JoinPoint joinPoint, Object returnValue) {
        Map<String, String> signMap = threadLocal.get();
        String methodName = currentMethod(joinPoint);
        String apiName = METHOD_MAP.get(methodName);
        if (apiName == null) {
            log.error("请求 Url 不存在");
            throw new BusinessException(ApiResultEnum.PARAM_VERIFICATION_FAIL, "请求 Url 不存在");
        }

        if (returnValue == null) {
            log.error("{} 接口返回值为 null,请检查代码", apiName);
            throw new BusinessException(ApiResultEnum.UNKNOWN_ERROR);
        }
        BaseVO baseVO = (BaseVO) returnValue;
        String sign = generateReturnSign.initReturnSign(baseVO, signMap.get(MdcConstant.SIGN_KEY), null);
        baseVO.setSign(sign);

        // 正常响应参数日志打印
        log.info(apiName + "响应:" + JSON.toJSONString(baseVO));
    }

    private String currentMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Method[] methods = joinPoint.getTarget().getClass().getMethods();
        Method resultMethod = null;
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                resultMethod = method;
                break;
            }
        }

        return resultMethod == null ? "" : resultMethod.getName();
    }

}


/**
 * 请求参数打印
 *
 * @author Xinling Jing
 * @date 2021-05-17 11:02
 */
@Component
@Slf4j
@Setter
public class RequestLogPrint extends ProcessingObject<BaseDTO> {

    @Override
    protected BaseDTO handleWork(BaseDTO input, String apiName) {

        log(JSON.toJSONString(input), apiName);
        return input;
    }

    private void log(String json, String apiName) {
        log.info(apiName + "请求:" + json);
    }
}

过滤器

package com.adups.inter.mgmt.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fh.inter.mgmt.dto.RegisterDTO;
import com.fh.inter.mgmt.service.GetVehicleInfoService;
import com.fh.standard.config.SystemCommonConfig;
import com.fh.standard.constant.MdcConstant;
import com.fh.standard.entity.VehicleInfoDO;
import com.fh.standard.enums.ApiEnum;
import com.fh.standard.util.AesUtils;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 报文解密过滤器
 *
 * @author fangh
 * @date 2023-02-15 18:27
 */
@Order(0)
@WebFilter(urlPatterns = "/idiagnosis/*")
@RequiredArgsConstructor
public class DecreptFilter implements Filter {

    private final SystemCommonConfig config;

    private final GetVehicleInfoService getVehicleInfoService;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!config.isEncrypt()) {
            chain.doFilter(servletRequest, response);
            return;
        }
        // 获取请求uri
        String uri = ((HttpServletRequest) servletRequest).getRequestURI();
        // 截取注册映射值
        String apiName = StrUtil.subAfter(uri, StrUtil.SLASH, true);
        RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
        ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
        //获取post的值
        String bodyMessage = IOUtils.toString(requestWrapper.getBody(), "UTF-8");
        String decodeBodyMessage;
        if (ApiEnum.RDS_REGISTER.getMethodName().equals(apiName)) {
            // 解密后的报文
            decodeBodyMessage = config.isEncrypt() ? AesUtils.aesDecodeStr(bodyMessage, config.getKey()) : bodyMessage;
            // 解析json
            RegisterDTO registerDTO = JSONUtil.toBean(decodeBodyMessage, RegisterDTO.class);
            // 获取车辆信息
            VehicleInfoDO vehicleInfoDO = getVehicleInfoService.getVehicleInfoByVin(registerDTO.getVin());
            registerDTO.setVehicle(vehicleInfoDO);
            // 覆写request数据
            requestWrapper.setBody(JSONUtil.toJsonStr(registerDTO).getBytes(StandardCharsets.UTF_8));
            MDC.put(MdcConstant.REGISTER_BODY_MESSAGE, decodeBodyMessage);
        } else {
            // 截取到deviceId值
            String deviceId = StrUtil.subAfter(uri, StrUtil.SLASH, true);
            // 根据deviceId获取车辆信息
            VehicleInfoDO vehicleInfoDO = getVehicleInfoService.getVehicleInfoByDeviceId(deviceId);
            // 报文解密
            decodeBodyMessage = config.isEncrypt() ? AesUtils.aesDecodeStr(bodyMessage, vehicleInfoDO.getSecret()) : bodyMessage;
            // 车辆的secret
            MDC.put(MdcConstant.ENCRYPT_KEY, vehicleInfoDO.getSecret());
            // 解密后的报文
            MDC.put(MdcConstant.BODY_MESSAGE, decodeBodyMessage);
            // 车辆信息
            MDC.put(MdcConstant.VEHICLE_INFO, JSONUtil.toJsonStr(vehicleInfoDO));
        }
        chain.doFilter(requestWrapper, responseWrapper);
        // 获取原响应报文
        byte[] content = responseWrapper.getContent();
        // 解析response
        String responseData = IOUtils.toString(content, "UTF-8");
        // 加密响应报文
        String result;
        if (ApiEnum.RDS_REGISTER.getMethodName().equals(apiName)) {
            result = AesUtils.aesEncryptStr(responseData, config.getKey());
        } else {
            // 非注册请求 加密key为车的密钥
            result = AesUtils.aesEncryptStr(responseData, MDC.get(MdcConstant.ENCRYPT_KEY));
        }
        // 覆写response
        content = result.getBytes();
        // 注意 此处是servletResponse 不是responseWrapper,写responseWrapper的话 依旧响应不了
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(content);
        outputStream.flush();
        outputStream.close();
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * RequestWrapper
 *
 * @author fangh
 * @date 2023-02-16 17:46
 */
public class RequestWrapper extends HttpServletRequestWrapper {
    private byte[] body;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = this.toByteArray(request.getInputStream());
    }

    private byte[] toByteArray(ServletInputStream inputStream) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024 * 4];
        int n;
        while ((n = inputStream.read(buffer)) != -1) {
            out.write(buffer, 0, n);
        }
        return out.toByteArray();
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() {
                return bais.read();
            }
        };
    }

    public byte[] getBody() {
        return body;
    }

    public void setBody(byte[] data) {
        body = data;
    }
}
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;

/**
 * ResponseWrapper
 *
 * @authorfangh
 * @date 2023-02-16 17:46
 */
public class ResponseWrapper extends HttpServletResponseWrapper {
    private final ByteArrayOutputStream byteArrayOutputStream;
    private final ServletOutputStream servletOutputStream;
    private final PrintWriter writer;


    public ResponseWrapper(HttpServletResponse response) {
        super(response);
        byteArrayOutputStream = new ByteArrayOutputStream();
        servletOutputStream = new WapperedOutputStream(byteArrayOutputStream);
        writer = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, StandardCharsets.UTF_8));
    }

    @Override
    public ServletOutputStream getOutputStream() {
        return servletOutputStream;
    }

    @Override
    public PrintWriter getWriter() {
        return writer;
    }

    @Override
    public void flushBuffer() {
        if (servletOutputStream != null) {
            try {
                servletOutputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (writer != null) {
            writer.flush();
        }
    }

    @Override
    public void reset() {
        byteArrayOutputStream.reset();
    }

    public String getResponseData(String charset) throws IOException {
        flushBuffer();
        return byteArrayOutputStream.toString(charset);
    }

    private static class WapperedOutputStream extends ServletOutputStream {

        private final ByteArrayOutputStream bos;

        public WapperedOutputStream(ByteArrayOutputStream stream) {
            bos = stream;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {

        }

        @Override
        public void write(int b) {
            bos.write(b);
        }
    }

    public byte[] getContent() {
        flushBuffer();
        return byteArrayOutputStream.toByteArray();
    }
}

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

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

相关文章

Atlassian Server用户新选择 | Data Center产品是否适合您的企业?

2021年&#xff0c;Atlassian宣布停止销售新的Server许可证在业内引起轩然大波。就在今年2月&#xff0c;Atlassian所有Server产品及插件均已停售。2024年2月&#xff0c;也就是一年不到&#xff0c;Atlassian将终止对Server产品及插件的所有支持。 此公告发布后&#xff0c;许…

Flink-DataStream API介绍(源算子、转换算子、输出算子)

文章目录DataStream API&#xff08;基础篇&#xff09;Flink 支持的数据类型执行环境&#xff08;Execution Environment&#xff09;创建执行环境执行模式(Execution Mode)触发程序执行源算子准备工作从集合中读取数据从文件读取数据从 Socket 读取数据从 Kafka 读取数据自定…

Spring Cloud Nacos实战(三)- 服务消费者注册和负载均衡,服务注册中心对比

目录Nacos服务消费者注册和负载均衡服务消费者具体配置新建Modulepomyml主启动测试远程调用与Ribbon什么是Ribbon具体使用验证Nacos自带负载均衡Nacos服务注册中心对比提升各种服务注册中心对比CAP模型CP原则&#xff1a;一致性 分区容错性原则AP原则&#xff1a;可用性原则 …

Unity 基于Netcode for gameObjects实现局域网同步

注意事项&#xff1a; 1.需要将unity升级到2021.3及以后的版本的稳定版本&#xff0c;使用不稳定的2022版本测试过&#xff0c;存在打包问题&#xff1a; 效果&#xff1a; 所需Package&#xff1a; 1.Netcode for gameObjects 2.Multiplayer Tools 该package是附带Netcode…

基于Spring Boot的零食商店

文章目录项目介绍主要功能截图&#xff1a;登录后台首页个人信息管理用户管理前台首页购物车部分代码展示设计总结项目获取方式&#x1f345; 作者主页&#xff1a;Java韩立 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关…

[Spring] 难理解的Aop编程 |入门?

作者&#xff1a;狮子也疯狂 专栏&#xff1a;《spring开发》 坚持做好每一步&#xff0c;幸运之神自然会驾凌在你的身上 目录一. &#x1f981; 前言二. &#x1f981; 常见概念2.1 常见术语2.2 AOP入门Ⅰ. &#x1f407; 功能场景Ⅱ. &#x1f407; 实现过程2.3 通知类型Ⅰ.…

使用frp配置内网机器访问

frp简介 frp 是一个开源、简洁易用、高性能的内网穿透和反向代理软件&#xff0c;支持 tcp, udp, http, https等协议。frp 项目官网是 https://github.com/fatedier/frp&#xff0c;软件下载地址为https://github.com/fatedier/frp/releases frp工作原理 服务端运行&#xf…

【GO】k8s 管理系统项目[前端部分--项目初始化]

【GO】k8s 管理系统项目[前端部分–项目初始化] 1. 项目概述 API部分已经完成了,着手开始前端部分.构建一个页面展示后端数据. 前端会使用到以下依赖 vue3框架element-plusxterm命令行模拟器nprogress进度条jsonwebtoken jwt token生成和校验json-editor-vue3/codemirror-e…

一文带你读懂Dockerfile

目录 一、概述 二、DockerFile构建过程解析 &#xff08;一&#xff09;Dockerfile内容基础知识 &#xff08;二&#xff09;Docker执行Dockerfile的大致流程 &#xff08;三&#xff09;总结 三、DockerFile常用保留字指令 四、案例 &#xff08;一&#xff09;自定义…

有了java基础,迅速学完Python并做了一份笔记-全套Python,建议收藏

面向过程Python简介Python和Java的解释方式对比Java&#xff1a;源代码 -> 编译成class -> Jvm解释运行Python&#xff1a;源代码 -> Python解释器解释运行我经常和身边的Java开发者开玩笑说&#xff1a;“Java真变态&#xff0c;别的语言都是要么直接编译要么直接解释…

Kaldi语音识别技术(六) ----- DTW和HMM-GMM

Kaldi语音识别技术(六) ----- DTW和HMM-GMM 文章目录Kaldi语音识别技术(六) ----- DTW和HMM-GMM前言一、语音识别概况二、语音识别基本原理三、DTW&#xff08;动态时间弯折&#xff09;算法四、GMM-HMM前言 前面的内容中我们完成了特征的提取,那么本章节我们主要进行理论部分…

IDEA全家桶式讲解 | IDEA安装、使用、断点调试、Git、插件 (第二篇)

目录 一&#xff1a;JavaEE阶段需要掌握的IDEA技能 1. 配置Tomcat 2. 配置Maven 3. IDEA连接数据库 4. 方便的特殊功能 5. 断点调试&#xff08;重点&#xff09; 6. IDEA中常用Git协同开发&#xff08;重点&#xff09; 7. 常用插件安装 一&#xff1a;JavaEE阶段需要…

Julia 语言环境安装

Julia 语言支持以下系统&#xff1a; LinuxFreeBSDmacOSWindowsAndroid Julia 安装包下载地址为&#xff1a;Download Julia。 Github 源码地址&#xff1a;GitHub - JuliaLang/julia: The Julia Programming Language。 国内镜像地址&#xff1a;Index of /julia-releases/…

Spring Boot框架基础介绍

Spring Boot 是一款基于 Spring 框架的开源应用程序开发工具&#xff0c;它旨在简化 Spring 应用程序的配置和开发过程。Spring Boot 提供了一种简单的方式来创建可独立运行的、生产级别的应用程序&#xff0c;并在需要时进行部署。Spring Boot 在微服务架构和云计算环境下得到…

nodejs基于vue垃圾回收分类网站

目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发环境 4 2.1 JAVA简介 4 2.2MyEclipse环境配置 4 2.3 B/S结构简介 4 2.4MySQL数据库 5 2.5 SPRINGBOOT框架 5 3 系统分析 6 3.1系统可行性分析 6 3.1.1经济可行性 6 3.…

【C++修炼之路】18.map和set

每一个不曾起舞的日子都是对生命的辜负 map和setmap和set一.关联式容器二.set2.1 set的介绍2.2 set的使用1.set的模板参数列表2.set的构造3.set的迭代器4.set修改操作5.bound函数三.multiset四.map3.1 map的介绍3.2 map的使用1.map的模板参数说明2.pair的介绍3.map的[]重载五.m…

如何构建微服务架构?

相信很多人对微服务架构都会产生这样一些疑问&#xff0c;例如我要何时使用微服务架构?又如何将应用程序分解为微服务?分解后&#xff0c;要如何去搭建微服务架构?同时&#xff0c;在微服务架构中&#xff0c;因为会涉及到多个组件&#xff0c;那么这些组件又可以使用什么技…

[软件工程导论(第六版)]第9章 面向对象方法学引论(复习笔记)

文章目录9.1 面向对象方法学概述要点9.2 面向对象的概念对象9.3 面向对象建模9.4 对象模型9.5 动态模型9.6 功能模型9.7 3种模型之间的关系9.1 面向对象方法学概述要点 面向对象方法学的出发点和基本原则&#xff0c;是尽可能模拟人类习惯的思维方式&#xff0c;使开发软件的方…

CS144-Lab3

概述 在实验0中&#xff0c;你实现了流控制字节流&#xff08;ByteStream&#xff09;的抽象。 在实验1和2中&#xff0c;你实现了将不可靠数据报中的段转换为传入字节流的工具&#xff1a;StreamReassembler和TCPReceiver。 现在&#xff0c;在实验3中&#xff0c;你将实现…

【STM32笔记】低功耗模式配置及避坑汇总

【STM32笔记】低功耗模式配置及配置汇总 文章总结&#xff1a;&#xff08;后续更新以相关文章为准&#xff09; 【STM32笔记】__WFI()&#xff1b;进入不了休眠的可能原因 【STM32笔记】HAL库低功耗模式配置&#xff08;ADC唤醒无法使用、低功耗模式无法烧录解决方案&#x…