Spring系列-SpringMvc父子容器启动原理解析
1、Spring整合SpringMVC
特性:
说到Spring整合SpringMVC唯一的体现就是父子容器:
- 通常我们会设置父容器(Spring)管理Service、Dao层的Bean, 子容器(SpringMVC)管理Controller的Bean .
- 子容器可以访问父容器的Bean, 父容器无法访问子容器的Bean。
实现:
相信大家在SSM框架整合的时候都曾在web.xml配置过这段:
<!--spring 基于web应用的启动-->
<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--全局参数:spring配置文件-->
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-core.xml</param-value>
</context-param>
<!--前端调度器servlet-->
<servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!--设置配置文件的路径--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value>
</init-param><!--设置启动即加载--><load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern>
但是它的作用是什么知道吗?
有人可能只知道DispatcherServlet叫前端控制器,是SpringMVC处理前端请求的一个核心调度器
那它为什么能处理请求?处理之前做了什么准备工作呢?又是怎么和Spring结合起来的呢?
为什么有了DispatcherServlet还要个ContextLoaderListener, 配一个不行吗?干嘛要配俩啊?
看完本文你就会有答案!
还有人可能会觉得, 我现在都用SpringBoot开发, 哪还要配这玩意.......
这就是典型的SpringBoot使用后遗症,SpringBoot降低了使用难度,但是从某种程度来说,也让初级的程序员变得更加小白,把实现原理都隐藏起来了而我们只管用,一旦涉及扩展就束手无策。
那当然我们今天不讲SpringBoot,我们今天用贴近SpringBoot的方式来讲SpringMVC。也就是零配置(零xml)的放式来说明SpringMVC的原理!!
此方式作为我们本文重点介绍,也是很多人缺失的一种方式, 其实早在Spring3+就已经提供, 只不过我们直到SpringBoot才使用该方式进行自动配置, 这也是很多人从xml调到SpringBoot不适应的原因, 因为你缺失了这个版本。 所以我们以这种方式作为源码切入点既可以理解到XML的方式又能兼顾到SpringBoot的方式 。
2、零配置SpringMVC实现方式:
那没有配置就需要省略掉web.xml 怎么省略呢?
在Servlet3.0提供的规范文档中可以找到2种方式:
- 注解的方式
- @WebServlet
- @WebFilter
- @WebListener
但是这种方式不利于扩展, 并且如果编写在jar包中tomcat是无法感知到的。
- SPI的方式
在Serlvet3-1的规范手册中:就提供了一种更加易于扩展可用于共享库可插拔的一种方式,参见8.2.4:
也就是让你在应用META-INF/services 路径下 放一个 javax.servlet.ServletContainerInitailizer ——即SPI规范
SPI 我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口, 不要被这个名字唬到了, 其实很好理解的一个东西:
其实就是根据Servlet厂商(服务提供商)提供要求的一个接口, 在固定的目录(META-INF/services)放上以接口全类名 为命名的文件, 文件中放入接口的实现的全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会调用文件中实现类的方法, 从而完成扩展。
ok 那我们知道了SPI是什么,我们是不是可以在Web应用中,在Servlet的SPI放入对应的接口文件:
放入实现类:
通过ServletContext就可以动态注册三大组件:以Servlet注册为例:
public class TulingSpringServletContainerInitializer extends SpringServletContainerInitializer {@Overridepublic void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {// 通过servletContext动态添加ServletservletContext.addServlet("spiServlet", new HttpServlet() {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("spiServlet--doGet");}}).addMapping("/spiServlet.do");}
当然在SpringMVC中, 这个接口文件和实现类都把我们实现好了,甚至ContextLoaderListener和DispatcherServlet都帮我们注册好了,我们只要让他生效,来,看看他是怎么做的:
3、实现基于SPI规范的SpringMVC
TulingStarterInitializer
- 此类继承AbstractAnnotationConfigDispatcherServletInitializer 这是个啥? 待会我们讲原理来介绍
- getRootConfigClasses 提供父容器的配置类
- getServletConfigClasses 提供子容器的配置类
- getServletMappings 设置DispatcherServlet的映射
public class TulingStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {/*** 方法实现说明:IOC 父容器的启动类* @author:xsls* @date:2019/7/31 22:12*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{RootConfig.class};}/*** 方法实现说明 IOC子容器配置 web容器配置* @author:xsls* @date:2019/7/31 22:12*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{WebAppConfig.class};}/*** 方法实现说明* @author:xsls* @return: 我们前端控制器DispatcherServlet的拦截路径* @exception:* @date:2019/7/31 22:16*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};
RootConfig
- 父容器的配置类 =以前的spring.xml
- 扫描的包排除掉@Controller
@Configuration
@ComponentScan(basePackages = "com.tuling",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value={RestController.class,Controller.class}),@ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
})
public class RootConfig {}
WebAppConfig
- 子容器的配置类 =以前的spring-mvc.xml
- 扫描的包:包含掉@Controller
@Configuration
@ComponentScan(basePackages = {"com.tuling"},includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
},useDefaultFilters =false)
@EnableWebMvc // ≈<mvc:annotation-driven/>
public class WebAppConfig implements WebMvcConfigurer{/*** 配置拦截器* @return*/@Beanpublic TulingInterceptor tulingInterceptor() {return new TulingInterceptor();}/*** 文件上传下载的组件* @return*/@Beanpublic MultipartResolver multipartResolver() {CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();multipartResolver.setDefaultEncoding("UTF-8");multipartResolver.setMaxUploadSize(1024*1024*10);return multipartResolver;}/*** 注册处理国际化资源的组件* @return*/
/* @Beanpublic AcceptHeaderLocaleResolver localeResolver() {AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();return acceptHeaderLocaleResolver;}*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tulingInterceptor()).addPathPatterns("/*");}/*** 方法实现说明:配置试图解析器* @author:xsls* @exception:* @date:2019/8/6 16:23*/@Beanpublic InternalResourceViewResolver internalResourceViewResolver() {InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setSuffix(".jsp");viewResolver.setPrefix("/WEB-INF/jsp/");return viewResolver;}@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new MappingJackson2HttpMessageConverter());}
自己去添加个Controller进行测试
OK, 现在可以访问你的SpringMVC了
4、SPI的方式SpringMVC启动原理
接着我们来看看SPI方式的原理是什么:
SpringMVC 大致可以分为 启动 和请求 2大部分, 所以我们本文先研究启动部分
流程图:
源码流程
- 外置Tomcat启动的时候通过SPI 找到我们应用中的/META-INF/service/javax.servlet.ServletContainerInitializer
- 调用SpringServletContainerInitializer.onStartUp()
-
- 调用onStartUp()前会先找到@HandlesTypes(WebApplicationInitializer.class) 所有实现了WebApplicationInitializer的类,传入到OnStartup的webAppInitializerClasses参数中,并传入Servlet上下文对象。
- 重点关注这组类:他们组成了父子容器
- 找到所有WebApplicationInitializer的实现类后, 不是接口、不是抽象则通过反射进行实例化(所以,你会发现内部实现类都是抽象的,你想让其起作用我们必须添加一个自定义实现类,在下文提供我的自定义实现类)
- 调用所有上一步实例化后的对象的onStartup方法
1. 首先来到AbstractDispatcherServletInitializer#onStartup再执行super.onStartup(servletContext);
@Override
public void onStartup(ServletContext servletContext) throws ServletException {//实例化我们的spring root上下文super.onStartup(servletContext);//注册我们的DispatcherServlet 创建我们spring web 上下文对象registerDispatcherServlet(servletContext);
创建父容器——ContextLoaderListener
2.父类AbstractContextLoaderInitializer#onStartup执行registerContextLoaderListener(servletContext);
- createRootApplicationContext()该方法中会创建父容器
- 该方法是抽象方法,实现类是AbstractAnnotationConfigDispatcherServletInitializer
- 调用getRootConfigClasses();方法获取父容器配置类(此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 )
- 创建父容器,注册配置类
- 该方法是抽象方法,实现类是AbstractAnnotationConfigDispatcherServletInitializer
- 会创建ContextLoaderListener并通过ServletContext注册
看完大家是不是感觉跟我们XML的配置ContextLoaderListener对上了:
创建子容器——DispatcherServlet
3.回到AbstractDispatcherServletInitializer#onStartup再执行registerDispatcherServlet(servletContext);
registerDispatcherServlet方法说明:
- 调用createServletApplicationContext创建子容器
- 该方法是抽象方法,实现类是AbstractAnnotationConfigDispatcherServletInitializer
- 创建子容器(下图很明显不多介绍)
- 调用抽象方法:getServletConfigClasses();获得配置类(此抽象方法在我们自定义的子类中实现提供我们自定义的配置类 )
- 配置类除了可以通过ApplicationContext()构造函数的方式传入 , 也可以通过这种方式动态添加,不知道了吧~
- 该方法是抽象方法,实现类是AbstractAnnotationConfigDispatcherServletInitializer
- 调用createDispatcherServlet(servletAppContext);创建DispatcherServlet
- 设置启动时加载:registration.setLoadOnStartup(1);
- 调用抽象方法设置映射路径:getServletMappings()(此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 )
看完大家是不是感觉跟我们XML的配置DispatcherServlet对上了
4. 初始化ContextLoaderListener
ContextLoaderListener加载过程比较简单:
外置tomcat会帮我们调用ContextLoaderListener#contextInitialized 进行初始化
- xml的方式下会判断容器为空时创建父容器
- 在里面会调用父容器的refresh方法加载
- 将父容器存入到Servlet域中供子容器使用
5. 初始化DispatcherServlet
可以看到流程比ContextLoaderListener流程更多
外置tomcat会帮我们调用DispatcherServlet#init() 进行初始化--->重点关注:initWebApplicationContext方法
- getWebApplicationContext(getServletContext())获得父容器(从之前的Servlet域中拿到)
- cwac.setParent(rootContext);给子容器设置父容器
- 调用configureAndRefreshWebApplicationContext(cwac);
-
- 注册一个监听器(该监听会初始化springmvc所需信息)
- ContextRefreshedEvent可以看到该监听器监听的是容器refreshed事件, 会在finishRefresh中发布
- 刷新容器
- 注册一个监听器(该监听会初始化springmvc所需信息)
当执行refresh 即加载ioc容器 完了会调用finishRefresh():
- publishEvent(new ContextRefreshedEvent(this));发布ContextRefreshedEvent事件
- 触发上面的ContextRefreshListener监听器:
---->FrameworkServlet.this.onApplicationEvent(event);
-------->onRefresh(event.getApplicationContext());
-------------->initStrategies(context);
protected void initStrategies(ApplicationContext context) {//初始化我们web上下文对象的 用于文件上传下载的解析器对象initMultipartResolver(context);//初始化我们web上下文对象用于处理国际化资源的initLocaleResolver(context);//主题解析器对象初始化initThemeResolver(context);//初始化我们的HandlerMappinginitHandlerMappings(context);//实例化我们的HandlerAdaptersinitHandlerAdapters(context);//实例化我们处理器异常解析器对象initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);//给DispatcherSerlvet的ViewResolvers处理器initViewResolvers(context);initFlashMapManager(context);
这里面的每一个方法不用太细看, 就是给SpringMVC准备初始化的数据, 为后续SpringMVC处理请求做准备
基本都是从容器中拿到已经配置的Bean(RequestMappingHandlerMapping、RequestMappingHandlerAdapter、HandlerExceptionResolver )放到dispatcherServlet中做准备:
...
但是这些Bean又是从哪来的呢?? 来来来, 回到我们的WebAppConfig
我们使用的一个@EnableWebMvc
- 导入了DelegatingWebMvcConfiguration@Import(DelegatingWebMvcConfiguration.class)
- DelegatingWebMvcConfiguration的父类就配置了这些Bean
- 而且我告诉你SpringBoot也是用的这种方式,
总结
- Tomcat在启动时会通过SPI注册 ContextLoaderListener和DispatcherServlet对象
- 同时创建父子容器
- 分别创建在ContextLoaderListener初始化时创建父容器设置配置类
- 在DispatcherServlet初始化时创建子容器 即2个ApplicationContext实例设置配置类
- 同时创建父子容器
- Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方法, 执行容器refresh进行加载
- 在子容器加载时 创建SpringMVC所需的Bean和预准备的数据:(通过配置类+@EnableWebMvc配置(DelegatingWebMvcConfiguration)——可实现WebMvcConfigurer进行定制扩展)
- RequestMappingHandlerMapping,它会处理@RequestMapping 注解
- RequestMappingHandlerAdapter,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。
- HandlerExceptionResolver 错误视图解析器
- addDefaultHttpMessageConverters 添加默认的消息转换器(解析json、解析xml)
- 等....
- 子容器需要注入父容器的Bean时(比如Controller中需要@Autowired Service的Bean); 会先从子容器中找,没找到会去父容器中找: 详情见AbstractBeanFactory#doGetBean方法
/** * 一般情况下,只有Spring 和SpringMvc整合的时才会有父子容器的概念, * 作用:* 比如我们的Controller中注入Service的时候,发现我们依赖的是一个引用对象,那么他就会调用getBean去把service找出来* 但是当前所在的容器是web子容器,那么就会在这里的 先去父容器找*/
BeanFactory parentBeanFactory = getParentBeanFactory();
//若存在父工厂,且当前的bean工厂不存在当前的bean定义,那么bean定义是存在于父beanFacotry中
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {//获取bean的原始名称String nameToLookup = originalBeanName(name);//若为 AbstractBeanFactory 类型,委托父类处理if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);}else if (args != null) {// 委托给构造函数 getBean() 处理return (T) parentBeanFactory.getBean(nameToLookup, args);}else {// 没有 args,委托给标准的 getBean() 处理return parentBeanFactory.getBean(nameToLookup, requiredType);}
相关文章:
Spring系列-SpringMvc父子容器启动原理解析
1、Spring整合SpringMVC 特性: 说到Spring整合SpringMVC唯一的体现就是父子容器: 通常我们会设置父容器(Spring)管理Service、Dao层的Bean, 子容器(SpringMVC)管理Controller的Bean .子容器可以访问父容器的Bean, 父容器无法访…...
[ssi-uploader插件]解决如何接收服务器返回数据+修改参数名称
前言 ssi-uploader是一款非常好用的多文件上传插件,源码是开源的,在github上面即可下载: https://github.com/ssbeefeater/ssi-uploader 但是源码有些微小的不足,今天我们解决两点问题: 上传文件完成后,…...
InfiniGate自研网关实现思路七
25.网关Nginx负载模型配置 通过模拟多个HTTP服务配置到 Nginx 做负载均衡,以学习API网关负载的配置和使用 API 网关是用于支撑分布式 RPC 接口协议转换提供 HTTP 调用的一套服务,那么 API 网关系统就需要可横向扩展来满足系统的吞吐量诉求。所以这里需…...
277 基于MATLAB GUI火灾检测系统
基于MATLAB GUI火灾检测系统,可以实现图片和视频的火苗检测。火焰识别的三个特征:1个颜色特征,2个几何特征颜色特征:HSV颜色空间下,对三个通道值进行阈值滤波,几何特征1:长宽比,几何…...
【西瓜书】4.决策树
1 递归返回情况 (1)结点包含样本全为同一类别 (2)属性集为空,没有属性可供划分了 或 有属性,但是在属性上划分的结果都一样 (3)结点为空结点 **结束时判定该结点的类别遵循如下规则&…...
区块链--Ubuntu上搭建以太坊私有链
1、搭建私链所需环境 操作系统:ubuntu16.04,开虚拟机的话要至少4G,否则会影响测试挖矿时的速度 软件: geth客户端 Mist和Ethereum Wallet:Releases ethereum/mist GitHub 2、安装geth客户端 sudo apt-get update …...
菜品信息分页查询——后端SpringBoot
1.分页查询的逻辑: 页面发送ajax请求,将分页查询参数(page,pageSize, name)提交到服务端,获取分页数据; 页面发送请求,请求服务端进行图片下载,用于页面图片展示。 开发菜品信息分页查询功能&a…...
利用GPT和PlantUML快速生成UML图用于设计
在软件开发中,设计阶段可是关键的一步。UML(统一建模语言)图能帮我们更清晰地理解和规划系统结构,但手动画UML图有时会很费时费力。好消息是,通过结合使用ChatGPT和PlantUML,我们可以高效地生成UML图&#…...
web-上传项目文件夹到Git远程仓库
Git初识 概念:一个免费开源,分布式的代码版本控制系统,帮助开发团队维护代码 作用:记录代码内容,切换代码版本,多人开发时高效合并代码内容 检验成功 打开bash终端(git专用)命令…...
使用OpenPCDet训练与测试Transformer模型:如何加载自己的数据集
引言 Transformer架构因其强大的序列处理能力和长距离依赖捕捉能力,在自然语言处理领域取得了巨大成功。近年来,这一架构也被引入3D物体检测领域,如Voxel Transformer等,显著提升了模型在复杂场景下的检测性能。OpenPCDet整合了多…...
四舍五入问题
单纯输出四舍五入可以用 printf("%.nf",num); 但是这个方法有时候会出错 代表输出n位四舍五入小数 而将数四舍五入赋值给变量可以用round()函数 a round(num); 表示将num四舍五入赋值给a 但是这么些只能转换位四舍五入的整数 可以改…...
零基础入门学用Arduino 第一部分(三)
重要的内容写在前面: 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。个人把这个教程学完之后,整体感觉是很好的,如果有条件的可以先学习一些相关课程,学起来会更加轻松,相关课程有数字电路…...
C++标准库random
random 完整文档看这里 三步走: 选择一种随机数种子选择一个随机数引擎选择一个随机数分布输出 随机数种子 //生成随机数种子,在Linux的实现中,是读取/dev/urandom设备 std::random_device rd; unsigned seed1 rd();// 获取当前时间点作为随机数种子 unsigned seed2 std:…...
电子电气架构——车载诊断DTC一文通
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他人的角度来反对自己。人生在世,最怕的就是把别人的眼光当成自己生活的唯一标…...
Golang | Leetcode Golang题解之第129题求根节点到叶节点数字之和
题目: 题解: type pair struct {node *TreeNodenum int }func sumNumbers(root *TreeNode) (sum int) {if root nil {return}queue : []pair{{root, root.Val}}for len(queue) > 0 {p : queue[0]queue queue[1:]left, right, num : p.node.Left, …...
工业信息化SCI期刊,中科院1区TOP,IF=12.3,收稿范围广泛
一、期刊名称 IEEE Transactions on Industrial Informatics 二、期刊简介概况 期刊类型:SCI 学科领域:工程工业 影响因子:12.3 中科院分区:1区TOP 三、期刊征稿范围 IEEE工业信息学汇刊是一本多学科期刊,发表技…...
Spring Boot整合Redis
Spring Boot整合Redis Spring Boot 整合 Redis 是一种常见的做法,用于在 Spring Boot 应用程序中添加缓存、会话管理分布式锁等功能。 浅谈Redis Redis用于存储数据,且在内存当中进行存储。 但是在日常编写代码时,定义一个变量也就是属于在内…...
kafka的leader和follower
leader和follower kafka的leader和follower是相对于分区有意义的,不是相对于broker。 因为每个分区都有leader和follower, leader负责读写数据。 follower负责复制leader的数据保存到自己的日志数据中,并在leader挂掉后重新选举出leader。 kafka会再…...
git 空仓库笔记
标识身份,建议先完成 Git 全局设置 git config --global user.name “账号” git config --global user.email “email” 方式一:克隆仓库 git clone https://url/your.git cd your_path touch README.md git add README.md git commit -m "add …...
字母异位词分组(charyw)
字母异位词分组 题目描述 给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。 输入格式 第一行正整数n,表示有n个字符串(1<n<1000) 第二行n个字符串,空格隔开 输出格式 多组字母…...
力扣 41.缺少的第一个正整数
题目描述: 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1: 输入:nums [1,2,0] 输出:3 解释:范围 …...
Git从入门到放弃
由于我的Git学的不太好,所以为了能够将以后我的学习笔记能够整理的更好,我先要系统的学习一下git,文章由此产生。 文章笔记源自尚硅谷Git入门到精通全套教程视频内容 1 进入官网 学习新技术的第一步需要熟悉官网,Git也不例外。ht…...
003.数据分析_PandasSeries对象
我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈 入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈 虚 拟 环 境 搭 建 :👉&…...
【介绍下什么是Kubernetes编排系统】
🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共…...
linux防止nmap扫描
1、首先关闭Centos7自带的firewalld [rootnode ~]# systemctl disable firewalld.service && systemctl stop firewalld.service 2、安装iptables服务 [rootnode ~]# yum install iptables-services iptables-devel -y [rootnode ~]# systemctl enable iptables …...
基于SpringBoot的装饰工程管理系统源码数据库
如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统装饰工程项目信息管理难度大,容错率低,管…...
2024前端面试准备2-JS基础知识回顾
变量类型和计算 1.值类型和引用类型的区别 常见值类型:undefined(定义undefined只能用let,不能用const)、字符串、bool、number、 Symbol; 常见引用类型: 对象, 数组、null(特殊引用类型,指针指向为空地址) 、function(特殊引用类型); 值类型的值直接存储在栈中;引用类型值存储…...
C++ 环形链表(解决约瑟夫问题)
约瑟夫问题描述: 编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。下一个人继续从 1 开始报数。n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少? 约瑟夫问题例子:…...
【微信小程序】模板语法
数据绑定 对应页面的 js 文件中 定义数据到 data 中: 在页面中使用 {{}} 语法直接使用: 事件绑定 事件触发 常用事件: 事件对象的属性列表(事件回调触发,会收到一个事件对象 event,它的详细属性如下&…...
深入了解 C 语言 Bug
目录 一、引言二、Bug的定义三、Bug的由来四、Bug的影响五、应对 Bug 的方法六、结论 一、引言 1、在 C 语言的编程世界中,Bug 是一个我们无法回避的话题。 2、Bug,简单来说,就是程序中存在的错误或缺陷。它可以表现为程序运行结果的异常、崩…...
机械行业网站 方案/免费创建属于自己的网站
题库来源:安全生产模拟考试一点通公众号小程序 2022年危险化学品经营单位主要负责人国家题库为危险化学品经营单位主要负责人考试题目精选题库!2022年危险化学品经营单位主要负责人考试题模拟考试题库及在线模拟考试依据危险化学品经营单位主要负责人最…...
武汉外贸网站建设/搜索引擎优化技巧
在最新的MIUI V5中的短信界面,如果我们按“菜单”键已经看不到曾经在这里出现的“私密短信”字样了。那它到底跑哪里去了呢?既然是私密,当然要在更隐蔽更不容易被发现的地方了。官方日志中给出的答案是----“在短信界面努力下拉即可开启”。说白了&…...
公司网站可以做无形资产么/专业seo网站
Attribute简介Unity使用cs作为开发语言,而cs有一项非常重要的特性就是attribute。attribute可以将元数据或声明性信息与代码(程序集,类型,方法,属性等)相关联。将attribute与程序实体相关联后,可以使用反射技术在运行时…...
建设网站联系方式/seo工作流程图
QByteArray ba("Hello world");char *data = ba.data();while (*data) {cout << "[" << *data...
如何做网站的逻辑结构图/哪个模板建站好
本文实例为大家分享了python实现烟花小程序的具体代码,供大家参考,具体内容如下FIREWORKS SIMULATION WITH TKINTER *self-containing code *to run: simply type python simple.py in your console *compatible with both Python 2 and Python 3 *Depen…...
室内设计公司取名字大全集/长春seo技术
文章目录一:无约束条件二:等式约束条件--拉格朗日乘子2.1拉格朗日乘子法的定义以及推导2.2一个简单的例子:引入: 优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值(因为最小值与最大值可以很容易转…...