【设计模式】行为型设计模式之 职责链模式,探究过滤器、拦截器、Mybatis插件的底层原理

news2024/12/24 10:03:23

一、介绍

职责链模式在开发场景中经常被用到,例如框架的过滤器、拦截器、还有 Netty 的编解码器等都是典型的职责链模式的应用。

标准定义

GOF 定义:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求,将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链条上的某个对象能够处理这个请求为止;

更常见的变体

实际上,职责链的实际应用中往往会更多的使用另一种变体,就是职责链上的每个对象都将请求处理一遍而不是遇到能处理的就终止!

二、代码举例

2.1 第一种:使用链表保存职责链

//抽象的处理对象
public abstract class Handler {
    //链条的下一个处理对象
    protected Handler successor = null;

    public abstract void handle();
}


//处理对象实现 HandlerA
public class HandlerA extends Handler{

    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        //如果自己不能够处理,则交由链条的下一个处理对象处理
        if(!handled && successor!=null){
            successor.handle();
        }
    }
}

//处理对象实现 HandlerB
public class HandlerB extends Handler{

    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        //如果自己不能够处理,则交由链条的下一个处理对象处理
        if(!handled && successor!=null){
            successor.handle();
        }
    }
}

//职责链的链表实现
public class HandlerChain{
    private Handler head=null;
    private Handler tail=null;

    public void addHandler(Handler handler){
        handler.setSuccessor(null);
        if(head==null){
            head=handler;
            tail=handler;
            return;
        }
        tail.setSuccessor(handler);
        tail=handler;
    }

    
    public void handle () {
        if(head !=null){
            head.handler;
        }
    }
}
public class Application {
    public static void main (String[] args){
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handler();
    }
}

**存在的问题:**handler 函数中存在对下一个处理器的调用,一旦被忘掉就链条就断了。
**改进:**使用模板方法模式改进

使用模板方法改进版本
//抽象的处理对象
public abstract class Handler {
    //链条的下一个处理对象
    protected Handler successor = null;
    //模板方法
    public final void handle(){
        boolean handled = doHandle();
        if(successor!=null && !handled){
            successor.handle();
        }
    }
    //抽象方法,由子类重写
    public abstract boolean doHandle();
}


//处理对象实现 HandlerA
public class HandlerA extends Handler{
    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        ....
        return handled;
    }
}


//处理对象实现 HandlerB
public class HandlerB extends Handler{
    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        ....
        return handled;
    }
}

2.2 第二种:使用数组保存职责链

public interface IHandler{
    boolean handle();
}


//处理对象实现 HandlerA
public class HandlerA implements IHandler{
    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        ....
        return handled;
    }
}


//处理对象实现 HandlerB
public class HandlerB extends IHandler{
    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        ....
        return handled;
    }
}
public class HandlerCHain{

    private List<IHandler> handlers = new ArrayList<>();

    public void addHandler(Handler handler){
        this.handlers.add(handler);
    }

    public void handle(){
        for(Ihandler handler : handlers){
            boolea handled = handler.handle();
            if(handled){
                break;
            }
        }
    }
}

三、应用举例

3.1 职责链模式过滤敏感词

处理敏感词有两种方式第一种是包含敏感词直接禁止发布,可以使用职责链模式的标准模式。
第二种则是包含敏感词后直接替换为其他符号后扔给下一个处理器处理,可以使用职责链的常见变体。

3.2 职责链模式在 Servlet Filter 中的应用剖析

Servlet Filter 介绍
  1. Servlet Filter 过滤器可以实现对 Http 请求的过滤功能,可以实现鉴权、限流、记录日志、参数验证等功能。
  2. Servlet Filter 是 Servlet 规范的一部分,只要支持 Servlet 规范的 Web 容器,例如 Tomcat、Jetty 等都支持过滤器功能。
过滤器使用举例
public class LogFilter implements Filter{
    @Override
    public void init (FilterConfig filterConfig) throws ServletException{
        //Filter创建时自动调用    
    }

    @Override
    public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{
        System.out.println("请求路径上的处理");
        chain.doFilter(request,response);
        System.out.pringln("响应路径上的处理");
    }
    @Override
    public void destory(){
        //销毁filter时自动调用
    }
}
  1. 过滤器的使用非常简单,实现 Filter 接口后添加过滤器配置即可,这完美符合开闭原则.
  2. 过滤器中使用了职责链模式,其中 Filter 就是职责链的 Handler,FilterChain 对应着就是 HandlerChain
过滤器 FilterChain 简化源码简单解析
// org.apache.catalina.core.ApplicationFilterChain 类的简化表示
public class ApplicationFilterChain {

    // 过滤器数组,存储了配置在某个servlet前的所有过滤器
    private ApplicationFilterConfig[] filters;
    
    // 链中的下一个实体,通常是目标Servlet
    private Servlet servlet;

    // 当前请求在过滤器链中的位置
    private int pos = 0; // 注意:实际实现中可能不会直接暴露此字段作为公共状态
    
    public ApplicationFilterChain(Filter[] filters, Servlet servlet) {
        this.filters = filters;
        this.servlet = servlet;
    }

    // 处理请求的主要方法
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // 如果还有过滤器待执行
        if (pos < filters.length) {
            // 获取当前要执行的过滤器
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter currentFilter = filterConfig.getFilter();
            // 调用过滤器的doFilter方法,将责任传递给下一个过滤器或最终的Servlet
            currentFilter.doFilter(request, response, this); // 注意这里将自身作为参数传递,形成链式调用
        } else {
            // 所有过滤器都已执行完毕,调用目标Servlet处理请求
            servlet.service(request, response);
        }
    }
}
  1. 这里的 doFilter 方法其实是一个递归调用,一直调用到最后一个 Filter 后,会调用真正的 Servlet 的 Service 方法执行具体的业务逻辑
  2. servlet 的 service 方法执行之后,会开始执行 Filter.doFilter 方法中chain.doFilter(request,response);之后的部分。也就是说,Servlet 的 filter 机制使用了 doFilter 方法的递归调用实现了一个请求和响应的双向拦截,非常巧妙。

3.3 职责链模式在 SpringMVC Interceptor 中的应用剖析

SpringMVC 拦截器介绍

SpringMVC 的拦截器 Interceptor 和上述的过滤器的作用十分相似,他们都是用来实现对 Http 请求实现拦截处理的功能。
不同之处在于

  1. Servlet 的过滤器是 Servlet 规范的一部分,由 Web 容器提供代码实现
  2. Spring Interceptor 是 SpringMVC 框架的一部分,由 SpringMVC 框架提供代码实现。拦截器只能拦截被扫描注册到 SpringMVC 的 dispatch 中的 Handler。对于一些静态资源,直接由 Web 容器管理的是无法被拦截器拦截的。

Spring MVC 的 Interceptor 使用举例
@Component
public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在处理请求前执行
        System.out.println("Request URL: " + request.getRequestURL() + ", Start Time: " + System.currentTimeMillis());
        return true; // 返回true表示继续执行后续的Interceptor和Handler
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在Handler方法调用后,渲染视图前执行
        // 这里可以对ModelAndView进行操作,但本例中不做任何处理
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在整个请求处理完成之后执行,可以做清理工作
        long endTime = System.currentTimeMillis();
        System.out.println("Request URL: " + request.getRequestURL() + ", End Time: " + endTime + ", Duration: " + (endTime - request.getAttribute("startTime")));
    }
}

Spring MVC Interceptor 职责链实现分析

Interceptor 也是基于职责链模式实现的,其职责链的实现中的处理器链为 HandlerExecutionChain ,其简化源代码如下

package org.springframework.web.servlet;

import java.util.ArrayList;
import java.util.List;

public class HandlerExecutionChain {

    private final Object handler;
    private List<HandlerInterceptor> interceptors;

    public HandlerExecutionChain(Object handler) {
        this.handler = handler;
        this.interceptors = null;
    }

    public HandlerExecutionChain(Object handler, List<HandlerInterceptor> interceptors) {
        this.handler = handler;
        this.interceptors = (interceptors != null ? new ArrayList<>(interceptors) : null);
    }

    public void addInterceptor(HandlerInterceptor interceptor) {
        if (this.interceptors == null) {
            this.interceptors = new ArrayList<>();
        }
        this.interceptors.add(interceptor);
    }

    public Object getHandler() {
        return this.handler;
    }

    public List<HandlerInterceptor> getInterceptors() {
        return this.interceptors;
    }

    // 应用所有拦截器的preHandle方法
    public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (this.interceptors != null) {
            for (HandlerInterceptor interceptor : this.interceptors) {
                if (!interceptor.preHandle(request, response, this.handler)) {
                    return false; // 如果任意拦截器返回false,则短路后续处理
                }
            }
        }
        return true;
    }

    // 应用所有拦截器的postHandle方法
    public void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
        if (this.interceptors != null) {
            for (HandlerInterceptor interceptor : this.interceptors) {
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

    // 触发所有拦截器的afterCompletion方法
    public void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
        if (this.interceptors != null) {
            for (int i = this.interceptors.size() - 1; i >= 0; i--) {
                this.interceptors.get(i).afterCompletion(request, response, this.handler, ex);
            }
        }
    }
}

简化后的 DispatchServlet 代码

public class DispatcherServlet extends HttpServletBean {

    // 省略了其他属性和方法...

    // 主要的请求分发方法
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 获取HandlerExecutionChain,这包括了Handler和一系列的Interceptor
        HandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);

        // 尝试应用所有拦截器的preHandle方法
        if (!handlerExecutionChain.applyPreHandle(request, response)) {
            return; // 如果有拦截器返回false,直接返回,不再继续处理
        }

        // 此处省略了根据Handler实际执行业务逻辑的部分
        // 例如,通过反射调用Controller方法,处理异常等
        
        ModelAndView modelAndView = null; // 假设这是Controller方法执行后的结果
        
        // 应用所有拦截器的postHandle方法
        if (modelAndView != null) {
            handlerExecutionChain.applyPostHandle(request, response, modelAndView);
        }

        // 渲染视图的逻辑省略...

        // 最后,触发所有拦截器的afterCompletion方法
        triggerAfterCompletion(request, response, null); // 假设没有异常发生
    }

    // 获取HandlerExecutionChain的简化方法
    private HandlerExecutionChain getHandlerExecutionChain(HttpServletRequest request) throws Exception {
        // 实际上这里会通过HandlerMapping查找合适的Handler并创建HandlerExecutionChain
        Object handler = // ... 省略了查找逻辑
        List<HandlerInterceptor> interceptors = // ... 省略了获取拦截器的逻辑
        return new HandlerExecutionChain(handler, interceptors);
    }

    // 触发所有拦截器的afterCompletion方法的简化实现
    private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
        HandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);
        handlerExecutionChain.triggerAfterCompletion(request, response, ex);
    }
}

SpirngMVC 的 HandlerExecutionChain 没有使用递归,而是将前置处理和后置处理拆成两个方法分别执行代码更加的直观。
DispatchServlet 类的在 doDispatch 中,会在调用 Handler 处理逻辑前后,分别调用过滤器链的 preHandler 方法

3.4 职责链模式在 MyBatis plugin 中的应用剖析

Mybatis 的插件机制和上面的过滤器、拦截器机制都很相似,都是在不修改原有流程代码的情况下拦截某些方法的调用进行链式处理。

MyBatis 插件的举例
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlExecutionTimeInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(SqlExecutionTimeInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            long end = System.currentTimeMillis();
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            Object parameter = null;
            if (invocation.getArgs().length > 1) {
                parameter = invocation.getArgs()[1];
            }
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String sql = boundSql.getSql();
            long time = (end - start);
            logger.info("SQL: {} 耗时: {} ms", sql, time);
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以在这里读取自定义配置,如果有的话
    }
}

Mybatis 插件通过@Intercepts注解来指定拦截器拦截的范围,这个注解嵌套@Signature注解 该注解通过三个参数,type、method、args 来指明要拦截的类,要拦截的方法名、要拦截的方法对应的参数。通过制定这三个元素我们就能完全确定要拦截的具体是哪个方法了。

Mybatis 插件机制原理

Mybatis 使用 Executor 类执行 SQL 语句,Executor 类会创建 StatementHandler、ParameterHandler 和 ResultSetHandler 三个类的对象,Executor 执行 sql 时这三个对象按照下方顺序被调用。只要拦截这几个类的方法就可以实现非常多的功能了。

  1. 首先使用 ParameterHandler 类来解析替换 SQL 中的占位符
  2. 使用 StatementHandler 来执行 SQL 语句
  3. 最后使用 ResultSetHandler 来封装 SQL 的结果

插件的执行过程

  1. Mybatis 解析配置后,将所有的拦截器加载到 InterceptorChain 中
//这里包含着一个方法的反射调用
public class Invocation {
    privatre final Object target;
    privatre final Method method;
    privatre final Object[] args;
    //省略构造函数和getter方法
    public Object proceed() throws InvocatrionTargetException,IllegalAccessException{
      return method.invocation(target,args);  
    }
}

//这是拦截器的接口代码
public interface Interceptor{
    Object intercept(Invocation invocation) throw Throwable;
    Object plugin(Object target);
    void setProperties(Properties properties);
}


//InterceptorChain Mybatis解析配置后,所有注册的Interceptor都保存到这儿
public class InterceptorChain{
    private final List<Interceptro> interceptors = new ArrayList<Interceptor>();

    public Object pluginAll(Object target){
        //这里循环对代理对象进行重复代理
        for(Interceptor interceptor : interceptors){
            //这里的plugin方法调用的Plugin.wrap方法,生成了一个代理对象
            target = interceptor.plugin(target);
        }
        return target;
    }
}
  1. Mybatis 执行 SQL 的过程中,会创建 Executor、ParameterHandler、StatementHandler、ResultSetHandler 等四个对象创建的代码在 Configuration 中。这四个对象的创建代码都会调用 InterceptorChain 的pluginAll 方法。
  2. pluginAll 方法的逻辑很简单,就是循环调用 Interceptor 的 plgin 方法,这个方法一般我们都会直接调用 Plugin 的静态 wrapper 方法
  3. Plugin 的 wrapper 方法的逻辑
    1. 检测 Interceptor 上的签名中是否包含当前 target 的类
    2. 如果不包含,则原样返回 target 也就是上面四个对象
    3. 如果包含,则生成代理对象,代理逻辑如下
      1. 判断当前方法是否是拦截器拦截的方法,如果是则调用拦截器的 Interceptor 方法。拦截器的 intercept 方法传入的 Invocation 则是使用代理对象传入的方法也就是被多层代理的对象。
      2. 如果当前方法不是拦截器拦截的方法,那么则直接调用被代理的原始方法。
    4. 最终代理对象一层层执行后,最后执行的才是那四个对象的原始方法。

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

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

相关文章

Leetcode1161. 最大层内元素和

Every day a Leetcode 题目来源&#xff1a;1161. 最大层内元素和 解法1&#xff1a;层序遍历 每次以「层」为单位进行拓展&#xff0c;统计该层的元素和&#xff0c;维护处理过程中的最大值层数和&#xff0c;以及层深度。 代码&#xff1a; /** lc appleetcode.cn id116…

高通开发系列 - 制作非root用户权限的debian镜像

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 返回:专栏总目录 目录 概述环境配置制作Debian base文件系统root权限构建Debian文件系统非root权限制作Debian文件系统制作image并运行概述…

Objective-C 学习笔记 | 基础

Objective-C 学习笔记 | 基础 参考书&#xff1a;《Objective-C 编程&#xff08;第2版&#xff09;》 第1部分 入门 Objective-C语言是以C语言为基础的&#xff0c;但增加了对面向对象编程的支持。Objective-C语言是用来开发在苹果iOS以及OS X操作系统上运行的应用的编程语…

算法导论实战(三)(算法导论习题第二十五、二十六章)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;算法启示录 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 第二十五章 25.1-10 25.2-5 25…

C++ | Leetcode C++题解之第140题单词拆分II

题目&#xff1a; 题解&#xff1a; class Solution { private:unordered_map<int, vector<string>> ans;unordered_set<string> wordSet;public:vector<string> wordBreak(string s, vector<string>& wordDict) {wordSet unordered_set(w…

【Jenkins】Jenkins - 节点

选择系统设置 - 节点设置 -添加节点 下载对应的 jar包 &#xff0c;执行命令 测试运行节点生效 1. 创建测试项目 test1 2. 选择节点执行&#xff1a; 在配置页面的“General”部分&#xff0c;找到“限制项目的运行节点”&#xff08;Restrict where this project can be run…

Kali Linux 2024.2 释出

渗透测试发行版 Kali Linux 释出了最新的 2024.2。 主要新特性包括&#xff1a;桌面环境更新到 GNOME 46&#xff0c;Xfce 环境加入 HiDPI 模式&#xff0c;更新了网络侦察工具 AutoRecon&#xff0c;监视 Linux 进程的命令行工具 pspy&#xff0c;提取和显示 CVE 信息的 Splo…

现代密码学-基础

安全业务 保密业务&#xff1a;数据加密 认证业务&#xff1a;保证通信真实性 完整性业务&#xff1a;保证所接收的消息未经复制、插入、篡改、重排或重放 不可否认业务&#xff1a;防止通信双方的某一方对所发消息的否认 访问控制&#xff1a;防止对网络资源的非授权访问&…

Mysql使用中的性能优化——索引数对INSERT性能的影响

表的索引可以给数据检索提升效率&#xff0c;但是也给表的增删改操作带来代价。本文我们将关注&#xff0c;索引数量对INSERT操作的影响。 结论 索引数的新增会造成INSERT操作效率下降&#xff0c;约每增一个索引会降低10%效率。 实验数据 可以看到0个索引的效率是7个索引效…

12-C语言的内存管理

12-C语言的内存管理 文章目录 12-C语言的内存管理一、C语言进程的内存布局1.1 程序与进程1.2 虚拟内存与物理内存1.2.1 虚拟内存布局 二、栈空间的特点与管理三、静态变量3.1 全局静态变量3.2 局部静态变量3.3 为什么需要静态变量&#xff1f; 四、数据段与代码段4.1 数据段4.2…

VSCode数据库插件

Visual Studio Code (VS Code) 是一个非常流行的源代码编辑器&#xff0c;它通过丰富的插件生态系统提供了大量的功能扩展。对于数据库操作&#xff0c;VS Code 提供了几种插件&#xff0c;其中“Database Client”系列插件是比较受欢迎的选择之一&#xff0c;它包括了对多种数…

Django 连接mysql数据库配置

1&#xff0c;提前创建注册的app1应用 Test/Test/settings.py python manage.py startapp app1 2&#xff0c;配置mysql数据库连接 Test/Test/settings.py DATABASES {default: {ENGINE: django.db.backends.mysql,# 数据库名字NAME: db1,# 连接mysql数据库用户名USER: ro…

力扣 240.搜素矩阵II

题目描述&#xff1a; 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9…

【自动部署】4.阿里云ECS服务器 IDEA自动部署项目

如何在IDEA中,自动部署项目到阿里云ECS服务器?今天我们就来实现一键部署功能。 执行maven命令打包免密登录,在IEDA中连接服务器执行stop服务上传jar包到服务器执行start脚本查看运行日志1.安装Alibaba Cloud Toolkit 2.配置host 3.自动化部署后端 右击项目,选择Alibaba CL…

Java Web学习笔记28——Element案例

案例&#xff1a; 根据页面原型完成员工管理页面开发&#xff0c;并通过Axios完成数据异步加载。 服务端数据获取地址&#xff0c;也就是API接口。 这个URL返回的是JSON格式的数据。 表格就是把JSON格式的数据渲染显示在页面中。 页面分为三个部分: 页头&#xff1a; 菜单…

学习笔记——路由网络基础——直连路由(direct)

二、直连路由(direct) 直连路由(direct)&#xff1a;直接相连&#xff0c;接口配置好ip地址并up后自动生成的路由。默认优先级为0 Destination&#xff1a;表示路由的目的地址。用来标识IP包的目的地址或目的网络。 Mask&#xff1a;表示目的地址的子网掩码长度。 与目的地址…

【创作活动】面对层出不穷的AI大模型产品我们应该怎么选择?

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Java | Leetcode Java题解之第139题单词拆分

题目&#xff1a; 题解&#xff1a; public class Solution {public boolean wordBreak(String s, List<String> wordDict) {Set<String> wordDictSet new HashSet(wordDict);boolean[] dp new boolean[s.length() 1];dp[0] true;for (int i 1; i < s.len…

[Vue3:axios]:实现实现登陆页面前后端请求,并用Vite解决跨域问题

文章目录 一&#xff1a;前置依赖查看依赖安装 axios&#xff1a;npm install axios 二&#xff1a;配置文件&#xff1a;创建一个用于全局使用的axios实例&#xff0c;并在main.js或main.ts文件中将其配置为全局属性。根目录mainjs文件引入axios 三&#xff1a;登录页面发送登…

C++中的一些困惑(长期更新中)

C中的一些困惑 文章目录 C中的一些困惑1. using std::具体命名与using namespace std;2. 【int \*p[10] 】与 【int (\*p)[10]】3. main()函数可带参&#xff0c;参从何来&#xff1f;4. constexpr函数的返回值可不为常量&#xff0c;那这时constexpr关键字作用是什么&#xff…