当前位置: 首页 > news >正文

SpringSecurity初始化过程

SpringSecurity初始化过程

SpringSecurity一定是被Spring加载的:

web.xml中通过ContextLoaderListener监听器实现初始化

<!--  初始化web容器--><!--设置配置文件的路径--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--配置Spring的监听器,默认只加载WEB-INF目录下的applicationContext.xml配置文件--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>

而spring配置文件中又引入了security的配置文件

<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:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd"><context:component-scan base-package="com.shaoby.service"></context:component-scan><bean name="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>//引入security配置文件<import resource="spring-security.xml"/>
</beans>

所以看ContextLoaderListener是如何加载的

Spring容器初始化

ContextLoaderListener中的configureAndRefreshWebApplicationContext方法

   protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {String configLocationParam;if (ObjectUtils.identityToString(wac).equals(wac.getId())) {configLocationParam = sc.getInitParameter("contextId");if (configLocationParam != null) {wac.setId(configLocationParam);} else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));}}wac.setServletContext(sc);//读取配置文件configLocationParam = sc.getInitParameter("contextConfigLocation");if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);}this.customizeContext(sc, wac);//刷新容器,spring初始化的核心方法wac.refresh();}

refresh方法

    public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {this.prepareRefresh();//创建bean工厂ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();this.prepareBeanFactory(beanFactory);try {this.postProcessBeanFactory(beanFactory);this.invokeBeanFactoryPostProcessors(beanFactory);this.registerBeanPostProcessors(beanFactory);this.initMessageSource();this.initApplicationEventMulticaster();this.onRefresh();this.registerListeners();this.finishBeanFactoryInitialization(beanFactory);this.finishRefresh();} catch (BeansException var9) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);}this.destroyBeans();this.cancelRefresh(var9);throw var9;} finally {this.resetCommonCaches();}}}

如何创建的bean工厂

spring通过loadBeanDefinitions方法完成初始化操作,它的实现方法可以通过xml配置文件或注解完成初始化

xml配置文件方法加载 XmlBeanDefinitionReader的loadBeanDefinitions方法中调用了doLoadBeanDefinitions方法
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {try {//通过流读取xml文件Document doc = this.doLoadDocument(inputSource, resource);//注册beanint count = this.registerBeanDefinitions(doc, resource);if (this.logger.isDebugEnabled()) {this.logger.debug("Loaded " + count + " bean definitions from " + resource);}return count;} catch (BeanDefinitionStoreException var5) {throw var5;} catch (SAXParseException var6) {throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);} catch (SAXException var7) {throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);} catch (ParserConfigurationException var8) {throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);} catch (IOException var9) {throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);} catch (Throwable var10) {throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);}}
解析xml文件
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for(int i = 0; i < nl.getLength(); ++i) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element)node;if (delegate.isDefaultNamespace(ele)) {this.parseDefaultElement(ele, delegate);} else {//解析默认标签delegate.parseCustomElement(ele);}}}} else {//解析自定义标签delegate.parseCustomElement(root);}}private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {/如果是import标签,通过跟踪代码发现会去解析另一个配置文件,依次类推,直到没有import标签if (delegate.nodeNameEquals(ele, "import")) {this.importBeanDefinitionResource(ele);} else if (delegate.nodeNameEquals(ele, "alias")) {this.processAliasRegistration(ele);} else if (delegate.nodeNameEquals(ele, "bean")) {this.processBeanDefinition(ele, delegate);} else if (delegate.nodeNameEquals(ele, "beans")) {this.doRegisterBeanDefinitions(ele);}}

通过这段代码可以发现,spring会先解析默认标签,因此会先执行springsecurity的配置文件引入的操作

解析自定义标签
    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {//获取标签String namespaceUri = this.getNamespaceURI(ele);if (namespaceUri == null) {return null;} else {//解析自定义标签,获得能解析该标签的HandlerResolver解析器NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;} else {return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}}}

springSecurity解析器SecurityNamespaceHandler

/**不同的标签交给不同的解析器去解析*/private void loadParsers() {this.parsers.put("ldap-authentication-provider", new LdapProviderBeanDefinitionParser());this.parsers.put("ldap-server", new LdapServerBeanDefinitionParser());this.parsers.put("ldap-user-service", new LdapUserServiceBeanDefinitionParser());this.parsers.put("user-service", new UserServiceBeanDefinitionParser());this.parsers.put("jdbc-user-service", new JdbcUserServiceBeanDefinitionParser());this.parsers.put("authentication-provider", new AuthenticationProviderBeanDefinitionParser());this.parsers.put("global-method-security", new GlobalMethodSecurityBeanDefinitionParser());this.parsers.put("authentication-manager", new AuthenticationManagerBeanDefinitionParser());this.parsers.put("method-security-metadata-source", new MethodSecurityMetadataSourceBeanDefinitionParser());if (ClassUtils.isPresent("org.springframework.security.web.FilterChainProxy", this.getClass().getClassLoader())) {this.parsers.put("debug", new DebugBeanDefinitionParser());this.parsers.put("http", new HttpSecurityBeanDefinitionParser());this.parsers.put("http-firewall", new HttpFirewallBeanDefinitionParser());this.parsers.put("filter-security-metadata-source", new FilterInvocationSecurityMetadataSourceParser());this.parsers.put("filter-chain", new FilterChainBeanDefinitionParser());this.filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();this.parsers.put("client-registrations", new ClientRegistrationsBeanDefinitionParser());}if (ClassUtils.isPresent("org.springframework.messaging.Message", this.getClass().getClassLoader())) {this.parsers.put("websocket-message-broker", new WebSocketMessageBrokerSecurityBeanDefinitionParser());}}

http标签的解析,HttpSecurityBeanDefinitionParser

    public BeanDefinition parse(Element element, ParserContext pc) {CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));pc.pushContainingComponent(compositeDef);//注册FilterChainProxyregisterFilterChainProxyIfNecessary(pc, pc.extractSource(element));//拿到FilterChainProxyBeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition("org.springframework.security.filterChains");List<BeanReference> filterChains = (List)listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue();//将过滤器放到filterChains中filterChains.add(this.createFilterChain(element, pc));pc.popAndRegisterContainingComponent();return null;}private BeanReference createFilterChain(Element element, ParserContext pc) {boolean secured = !"none".equals(element.getAttribute("security"));if (!secured) {if (!StringUtils.hasText(element.getAttribute("pattern")) && !StringUtils.hasText("request-matcher-ref")) {pc.getReaderContext().error("The 'security' attribute must be used in combination with the 'pattern' or 'request-matcher-ref' attributes.", pc.extractSource(element));}for(int n = 0; n < element.getChildNodes().getLength(); ++n) {if (element.getChildNodes().item(n) instanceof Element) {pc.getReaderContext().error("If you are using <http> to define an unsecured pattern, it cannot contain child elements.", pc.extractSource(element));}}return this.createSecurityFilterChainBean(element, pc, Collections.emptyList());} else {BeanReference portMapper = this.createPortMapper(element, pc);BeanReference portResolver = this.createPortResolver(portMapper, pc);ManagedList<BeanReference> authenticationProviders = new ManagedList();BeanReference authenticationManager = this.createAuthenticationManager(element, pc, authenticationProviders);boolean forceAutoConfig = isDefaultHttpConfig(element);HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, forceAutoConfig, pc, portMapper, portResolver, authenticationManager);AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc, httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager, httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());httpBldr.setEntryPoint(authBldr.getEntryPointBean());httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());httpBldr.setCsrfIgnoreRequestMatchers(authBldr.getCsrfIgnoreRequestMatchers());authenticationProviders.addAll(authBldr.getProviders());List<OrderDecorator> unorderedFilterChain = new ArrayList();unorderedFilterChain.addAll(httpBldr.getFilters());unorderedFilterChain.addAll(authBldr.getFilters());unorderedFilterChain.addAll(this.buildCustomFilterList(element, pc));unorderedFilterChain.sort(new OrderComparator());this.checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));List<BeanMetadataElement> filterChain = new ManagedList();Iterator var13 = unorderedFilterChain.iterator();while(var13.hasNext()) {OrderDecorator od = (OrderDecorator)var13.next();filterChain.add(od.bean);}return this.createSecurityFilterChainBean(element, pc, filterChain);}}
//注册FilterChainProxy
static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {if (!pc.getRegistry().containsBeanDefinition("org.springframework.security.filterChainProxy")) {BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, "org.springframework.security.filterChains"));BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);fcpBldr.getRawBeanDefinition().setSource(source);fcpBldr.addConstructorArgReference("org.springframework.security.filterChains");fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(DefaultFilterChainValidator.class));BeanDefinition fcpBean = fcpBldr.getBeanDefinition();pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, "org.springframework.security.filterChainProxy"));//添加别名springSecurityFilterChainpc.getRegistry().registerAlias("org.springframework.security.filterChainProxy", "springSecurityFilterChain");}}

到这可以看到所有的过滤器被放到filterChain中

相关文章:

SpringSecurity初始化过程

SpringSecurity初始化过程 SpringSecurity一定是被Spring加载的&#xff1a; web.xml中通过ContextLoaderListener监听器实现初始化 <!-- 初始化web容器--><!--设置配置文件的路径--><context-param><param-name>contextConfigLocation</param-…...

Python爬取股票信息-并进行数据可视化分析,绘股票成交量柱状图

为了使用Python爬取股票信息并进行数据可视化分析&#xff0c;我们可以使用几个流行的库&#xff1a;requests 用于网络请求&#xff0c;pandas 用于数据处理&#xff0c;以及 matplotlib 或 seaborn 用于数据可视化。 步骤 1: 安装必要的库 首先&#xff0c;确保安装了以下P…...

秋招突击——7/4——复习{}——新作{最长公共子序列、编辑距离、买股票最佳时机、跳跃游戏}

文章目录 引言复习新作1143-最长公共子序列个人实现 参考实现编辑距离个人实现参考实现 贪心——买股票的最佳时机个人实现参考实现 贪心——55-跳跃游戏个人实现参考做法 总结 引言 昨天主要是面试&#xff0c;然后剩下的时间都是用来对面试中不会的东西进行查漏补缺&#xff…...

udp发送数据如果超过1个mtu时,抓包所遇到的问题记录说明

最近在测试Syslog udp发送相关功能&#xff0c;测试环境是centos udp头部的数据长度是2个字节&#xff0c;最大传输长度理论上是65535&#xff0c;除去头部这些字节&#xff0c;可以大概的说是64k。 写了一个超过64k的数据(随便用了一个7w字节的buffer)发送demo&#xff0c;打…...

电子电气架构 --- 智能座舱万物互联

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…...

笔记本电脑内存不够

笔记本电脑内存不够是众多笔记本用户面临的常见问题&#xff0c;尤其是对于一些需要处理大型文件或者运行复杂软件的用户&#xff0c;这个问题可能会严重影响笔记本的使用体验。那么&#xff0c;我们应该如何解决笔记本电脑内存不够的问题呢&#xff1f;本文将从几个方面进行详…...

Vue项目使用mockjs模拟后端接口

文章目录 操作步骤1. 安装 mockjs 和 vite-plugin-mock2. 安装 axios3. 创建mock路径4. 配置 viteMockConfig5. 编写第一个mock接口6. 创建 createProdMockServer7. 配置 axios8. 编写请求接口9. 在页面中使用 操作步骤 1. 安装 mockjs 和 vite-plugin-mock vite-plugin-mock …...

Linux下使用arping检测IP地址是否冲突

arping简介 在Linux中&#xff0c;arping是一个用来发送ARP请求到一个相邻主机的工具&#xff0c;通常用于检测网络上的IP地址冲突。 使用arping检测IP地址是否冲突的方法 例1&#xff1a;使用如下命令检测10.206.216.95是否冲突 (使用-I参数指定网络接口) # arping -I eth…...

为什么 npm run serve 正常,npm run build 就报错:digital envelope routines::unsupported

这个错误通常与 Node.js 版本和使用的加密算法有关。让我解释一下原因和可能的解决方案&#xff1a; 错误原因 这个错误&#xff08;“error:0308010C:digital envelope routines::unsupported”&#xff09;通常发生在以下情况&#xff1a; 使用较新版本的 Node.js&#xf…...

模电基础 - 简介

目录 零 .简介 一. 学习方法 二. 教材推荐 三. 总结 零 .简介 “模电”即模拟电子技术&#xff0c;是电子信息工程、电气工程及其自动化等相关专业的一门关键基础课程。 首先&#xff0c;在半导体器件方面&#xff0c;二极管是一种具有单向导电性的器件&#xff0c;由 P 型…...

uniapp中实现瀑布流 短视频页面展示

直接上干货 第一部分为结构 <swiper class"list" :currentindex change"swiperchange" scrolltolower"onReachBottom"><swiper-item style"overflow: scroll;" v-for"(item,index) in 2" :key"index"&g…...

python-开关灯(赛氪OJ)

[题目描述] 假设有 N 盏灯&#xff08;N 为不大于 5000 的正整数&#xff09;&#xff0c;从 1 到到 N 按顺序依次编号&#xff0c;初始时全部处于开启状态&#xff1b;第一个人&#xff08; 1 号&#xff09;将灯全部关闭&#xff0c;第二个人&#xff08; 2 号&#xff09;将…...

基于改进高斯-拉普拉斯滤波器的一维时间序列平滑与降噪(MATLAB)

以图像处理为例&#xff0c;拉普拉斯算子是基于图像的二阶导数来找到边缘并搜索过零点&#xff0c;传统的拉普拉斯算子常产生双像素宽的边缘&#xff0c;对于较暗区域中的亮斑进行边缘检测时&#xff0c;拉普拉斯运算就会使其变得更亮。因此&#xff0c;与梯度算子一样&#xf…...

计算组的妙用!!页面权限控制

需求描述&#xff1a; 某些特殊的场景下&#xff0c;针对某页看板&#xff0c;需要进行数据权限卡控&#xff0c;但是又不能对全部的数据进行RLS处理&#xff0c;这种情况下可以利用计算组来解决这个需求。 实际场景 事实表包含产品维度和销售维度 两个维度属于同一公司下面的…...

Self-Instruct构造Prompt的例子

人工构造一批Prompt做种子。&#xff08;Starting with a small seed set of human-written tasks&#xff09;每次把一些种子后来生成的Prompt&#xff0c;放到Input里做few-shot examples&#xff0c;用LLM生成更多的Prompt&#xff1b;&#xff08;Using the LLM to generat…...

友好前端vue脚手架

企业级后台集成方案vue-element-admin-CSDN博客在哔站学习&#xff0c;老师说可以有直接的脚手架&#xff08;vue-element-admin&#xff09;立马去搜索&#xff0c;找到了这博主这篇文章 介绍 | vue-element-admin​​​​​​ 官方默认英文版&#xff1a; git clone https:/…...

SQL Server特性

一、创建表 在sql server中使用create table来创建新表。 create table Customers( id int primary key identity(1,1), name varchar(5) ) 该表名为Customers其中包含了2个字段&#xff0c;分别为id&#xff08;主键&#xff09;以及name。 1、数据类型 整数类型&#xff…...

华为HCIP Datacom H12-821 卷25

1.单选题 Smurf攻击一般使用以下哪种协议 A、TCP B、BGP C、ICMP D、DHCP 正确答案&#xff1a; C 解析&#xff1a; Smurf攻击是一种病毒攻击&#xff0c;以最初发动这种攻击的程序“Smurf”来命名。这种攻击方法结合使用了IP欺骗和ICMP回复方法使大量网络传输充斥目…...

如何在 Selenium Python 中解决验证码 | 2024 完整指南

由于在进行网络自动化时遇到验证码是让许多人感到不知所措的问题。这些验证码专为区分人类用户和自动化脚本而设计&#xff0c;对于使用Selenium进行网络爬虫或自动化任务而言&#xff0c;无疑是一个巨大的挑战。2024年的完全指南将为您提供全面的解决方案&#xff0c;帮助您高…...

ASCII码对照表【2024年汇总】

&#x1f37a;ASCII相关文章汇总如下&#x1f37a;&#xff1a; &#x1f388;ASCII码对照表&#xff08;255个ascii字符汇总&#xff09;&#x1f388;&#x1f388;ASCII码对照表&#xff08;Unicode 字符集列表&#xff09;&#x1f388;&#x1f388;ASCII码对照表&#x…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...

恶补电源:1.电桥

一、元器件的选择 搜索并选择电桥&#xff0c;再multisim中选择FWB&#xff0c;就有各种型号的电桥: 电桥是用来干嘛的呢&#xff1f; 它是一个由四个二极管搭成的“桥梁”形状的电路&#xff0c;用来把交流电&#xff08;AC&#xff09;变成直流电&#xff08;DC&#xff09;。…...

第八部分:阶段项目 6:构建 React 前端应用

现在&#xff0c;是时候将你学到的 React 基础知识付诸实践&#xff0c;构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段&#xff0c;你可以先使用模拟数据&#xff0c;或者如果你的后端 API&#xff08;阶段项目 5&#xff09;已经搭建好&#xff0c;可以直接连…...

书籍“之“字形打印矩阵(8)0609

题目 给定一个矩阵matrix&#xff0c;按照"之"字形的方式打印这个矩阵&#xff0c;例如&#xff1a; 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为&#xff1a;1&#xff0c;…...