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

一、介绍

职责链模式在开发场景中经常被用到,例如框架的过滤器、拦截器、还有 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. 最终代理对象一层层执行后,最后执行的才是那四个对象的原始方法。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

联合体和枚举类型

1.联合体 1.1 联合体类型的声明 像结构体⼀样&#xff0c;联合体也是由⼀个或者多个成员构成&#xff0c;这些成员可以不同的类型。 但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同⼀块内存空间。所以联合体也叫&#xff1a;共用体。 给联合体…

数据并非都是正态分布:三种常见的统计分布及其应用

你有没有过这样的经历&#xff1f;使用一款减肥app&#xff0c;通过它的图表来监控自己的体重变化&#xff0c;并预测何时能达到理想体重。这款app预测我需要八年时间才能恢复到大学时的体重&#xff0c;这种不切实际的预测是因为应用使用了简单的线性模型来进行体重预测。这个…

C++ list链表的使用和简单模拟实现

目录 前言 1. list的简介 2.list讲解和模拟实现 2.1 默认构造函数和push_back函数 2.2 迭代器实现 2.2.1 非const正向迭代器 2.2.2 const正向迭代器 2.2.3 反向迭代器 2.3 插入删除函数 2.3.1 insert和erase 2.3.2 push_back pop_back push_front pop_front 2.4 构…

LLVM Cpu0 新后端3

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1V_tZkt9uvxo5bnUufhMQ_Q?…

173.二叉树:找树左下角的值(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr, right(nullptr) {}* Tree…

强!推荐一款开源接口自动化测试平台:AutoMeter-API !

在当今软件开发的快速迭代中&#xff0c;接口自动化测试已成为确保代码质量和服务稳定性的关键步骤。 随着微服务架构和分布式系统的广泛应用&#xff0c;对接口自动化测试平台的需求也日益增长。 今天&#xff0c;我将为大家推荐一款强大的开源接口自动化测试平台: AutoMete…

【中国开源生态再添一员】天工AI开源自家的Skywork

刚刚看到《AI高考作文出圈&#xff0c;网友票选天工AI居首》&#xff0c;没想到在Huggingface中发现了Skywork大模型。天工大模型由昆仑万维自研&#xff0c;是国内首个对标ChatGPT的双千亿级大语言模型&#xff0c;天工大模型通过自然语言与用户进行问答式交互&#xff0c;AI生…

用c语言实现通讯录

目录 静态简易通讯录 代码&#xff1a; 功能模块展示&#xff1a; 设计思路&#xff1a; 动态简易通讯录&#xff08;本质顺序表&#xff09; 代码&#xff1a; 扩容模块展示&#xff1a; 设计思路&#xff1a; 文件版本通讯录 代码&#xff1a; 文件模块展示&#x…

突破网络屏障:掌握FRP内网穿透技术

1.FRP介绍 1.frp是什么 frp 是一款高性能的反向代理应用&#xff0c;专注于内网穿透。它支持多种协议&#xff0c;包括 TCP、UDP、HTTP、HTTPS 等&#xff0c;并且具备 P2P 通信功能。使用 frp&#xff0c;您可以安全、便捷地将内网服务暴露到公网&#xff0c;通过拥有公网 I…

【C++ STL】模拟实现 string

标题&#xff1a;【C :: STL】手撕 STL _string 水墨不写bug &#xff08;图片来源于网络&#xff09; C标准模板库&#xff08;STL&#xff09;中的string是一个可变长的字符序列&#xff0c;它提供了一系列操作字符串的方法和功能。 本篇文章&#xff0c;我们将模拟实现STL的…

【机器学习】消息传递神经网络(MPNN)在分子预测领域的医学应用

1. 引言 1.1. 分子性质预测概述 分子性质预测是计算机辅助药物发现流程中至关重要的任务之一&#xff0c;它在许多下游应用如药物筛选和药物设计中发挥着核心作用&#xff1a; 1.1.1. 目的与重要性&#xff1a; 分子性质预测旨在通过分子内部信息&#xff08;如原子坐标、原…

汇总 |国内外医疗器械网络安全法规与标准

国内外关于医疗器械网络安全的法规和标准日益完善&#xff0c;旨在确保医疗器械在全生命周期内的网络安全&#xff0c;保障患者信息的安全和隐私&#xff0c;以及医疗器械的正常运行。不同国家和地区的法规和标准各有侧重&#xff0c;但都强调了医疗器械制造商、开发者、经营者…

contos7使用docker安装vulhub

contos7下使用docker安装vulhub 1. 安装docker 1. 更新yum &#xff08;1&#xff09;切换root用户 su root &#xff08;2&#xff09;更新yum yum update 2. 卸载旧版本的docker sudo yum remove docker sudo yum remove docker-client sudo yum remove docker-clien…

反AI浪潮中的新机遇:Cara艺术社区异军突起

近日,一个名为Cara的艺术社区在网络上迅速走红,其独特的反AI定位吸引了大量创意人士。在AI技术日益普及的今天,Cara社区反其道而行之,致力于打造一个无AI侵害的创作和交流环境。这一创新模式不仅赢得了艺术家的青睐,也为国内创业者提供了新的思考角度。 一、精准定位,守…

Linux shell编程基础

Shell 是一个用 C 语言编写的程序&#xff0c;它是用户使用 Linux 的桥梁。Shell 既是一种命令语言&#xff0c;又是一种程序设计语言。Shell 是指一种应用程序&#xff0c;这个应用程序提供了一个界面&#xff0c;用户通过这个界面访问 Linux 内核的服务。 Shell 脚本&#x…

Elasticsearch中各种query的适用场景

Elasticsearch 提供了丰富的 Query 类型&#xff0c;以满足各种搜索需求。以下列举一些常见的 Query 类型&#xff0c;并分析其区别和应用场景&#xff1a; 一、 几个常用的基本Query 1. Term Query 应用场景: 查找包含特定词语的文档&#xff0c;适合精确匹配单个词语的场景…

【C++第九课 - vector】vector介绍、vector使用,vector的底层实现、杨辉三角、全排列、只出现一次的数字

目录 一、vector的介绍二、vector的使用1、vector的构造函数2、vector的插入和三种遍历方式3、开空间4、insert5、find6、erase补充 三、vector的底层实现1、成员变量2、构造函数3、push_back4、访问方式5、pop_back6、insert - pos位置插入x7、resize8、拷贝构造9、赋值10、er…

【第13章】SpringBoot实战篇之项目部署

文章目录 前言一、准备1. 引入插件2. 打包3. 启动4. 后台启动 二、跳过测试模块三、外置配置文件1.引入插件2.忽略配置文件3. 外置配置文件 总结 前言 项目部署需要把项目部署到Linux服务器上&#xff0c;SpringBoot项目通过Maven打包即可快速生成可运行Jar包程序。 一、准备 …

每日一题——Python实现PAT乙级1042 字符统计(举一反三+思想解读+逐步优化)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 优点 缺点和改进建议 时间复杂度分析 空间复杂度分析 改进后的代码 我…

【Androi】安卓发展历程详解

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…