博客列表 - 第 3 页

共 92 篇文章

Hadoop 知识整理

# hadoop知识点整理 ## 第一章 大数据的概念 大数据是指无法用现有的软件工具提取、存储、搜索、共享、分析和处理的海量的复杂的数据图集。 ### 特征 **4个V**: - Volume:数据体量巨大 - Variety:数据种类繁多 - Value:数据价值密度低 - Velocity:处理速度快 ### hadoop生态圈 大数据工具主要包括:Hadoop、Hbase、ZooKeeper、Hive、Mahout、Sqoop、Storm等 #### Hadoop **Doug Cutting**开发,受到**Map/Reduce**启发,核心是**MapReduce编程模型和HDFS分布式文件系统**。 采用分而治之的思想,Map用来切分大的数据,Reduce用来合并Map计算的结果。 HDFS 分布式文件系统,为海量数据提供存储服务,将大文件拆分为块,多节点存放,具有高吞吐量、高容错性的特点。 #### HBASE HBASE是Apache开源的KV型数据库,是建立在HDFS之上,提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统。 仅支持单行事务。 主要用来存储非结构化和半结构化的松散数据。 #### Hive Apache Hive数据仓库软件提供对存储在分布式中的大型数据集的查询和管理,它本事是建立在Hadoop之上的 #### Storm Apache Storm是一个免费、开源的分布式实时计算机系统,简化了数据流的可靠处理。 #### ZooKeeper zooKeeper是一个高性能、分布式的开源分布式应用协调服务,他是storm、hbase的重要组件,它是一个为分布式应用提供一致性服务的软件。 服务端跑在JAVA上 ZooKeeper有两个角色,一个是leader,负责写服务和数据同步,剩下的是follower,提供读服务。 **特点**: - 顺序一致性:按照客户端发送请求的顺序更新数据 - 原子性 - 单一性:无论客户端连接哪个server都看到同一个视图 - 可靠性:一旦数据更新成功将一直保持,直到新的更新 - 及时性:客户会在一个确定的时间内得到最新的数据 **运用场景**: - 数据发布订阅 - 名空间服务 - 分布式通知 - 分布式锁 - 集群管理 #### sqoop sqoop是Apache顶级项目,允许用户将数据从关系型数据库中抽取数据到Hadoop中 #### mahout mahout是一个强大的数据挖掘工具,是一个分布式机器学习算法的集合,包括分布式协同过滤的实现、分类、聚类等 ### Hadoop历史和版本 历史: - 2011年12月,Apache基金会发布了Apache Hadoop 版本1.0 - 2013年8月,版本2.0.6可用 - 2017年12月发布Apache Hadoop3 发行版: Hadoop有许多变体: - Cloudera Hadoop分布:是Coludera Enterprise的核心,包括Apache Hadoop、Apache Spark,Apache Kafka 以及十多个其他紧密继承的领先开源项目 - Hortonworks Hadoop分布:是基于YARN的安全性强、企业就绪的开源版本 - MapR Hadoop分布:是Hadoop的完成整企业级发行版 - PivotalHD:是领先的基于标准的Hadoop该发行版,为Business Data Lake架构奠定了基础 优势: - 高可靠性 - 高拓展性 - 高效性 - 高容错性 ## 第二章 Hadoop 组成与结构 Hadoop1的三大核心模块: - Common模块:支持其他模块的工具模块 - HDFS模块:一个高可靠、高吞吐量的分布式文件系统 - MapReduce模块:一个分布式的资源调度和离线并行计算系统 Hadoop2的组成: MapReduce模块仅作为分布式计算框架存在,资源调度功能交给YARN来调度处理 ### HDFS 一个分布式文件系统。 HDF的设计适合一次写入多次读出的场景且不支持文件修改。适合用来做数据分析,并不适合做网盘使用。 Master-Slave结构,Master是NameNode,Slave是DataNode client职责如下: - 文件切分 - 与NameNode交互获取文件的位置信息 - 与DataNode交互读取或写入数据 - 提供一些明恋来管理HDFS,比如启动或者关闭HDFS - 可以通过一些命令来访问HDFS NameNode职责如下: - 配置副本策略 - 处理client读写请求 - 管理block(数据块)映射信息,以元数据的形式存储在Fsimage镜像文件中 - 管理HDFS命名空间 DataNode的职责: - 执行实际的数据块 - 执行数据块的读写操作 SecondaryNameNode,第二名称节点,并非名称节点的热备,晋档NameNode重启或者热备NameNode激活时将宕机前所保留集群的快照发送给NameNode以恢复此前集群的状态。具体功能为: - 存辅NameNode,分担其工作量 - 定期合并Fsimage和Edits,并推送给NameNode - 在紧急情况下可辅助恢复NameNode 优点: - 高容错性 - 适合大数据处理 - 支持流式数据访问 - 可构建在廉价机器上 缺点: - 不适合低延时数据访问 - 无法高效的对大量小文件进行存储 - 不支持并发写入文件和随机修改 ### YARN架构 MRv1的局限: - 扩展性差 - 可靠性差 - 资源利用率低 - 无法支持多种计算机框架 YARN是一个弹性计算平台,他的目标已经不局限于支持MapReduce一种计算框架,而是朝着对多种框架的统一管理前进 优点: - 资源利用率高 - 运维成本低 - 数据共享 对比: | | V1 | V2 | | ----------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------- | | 基本框架 | JobTracker由资源管理和作业控制两部分组成 | 将JobTracker的两个功能拆分成两个独立的进程,资源管理进程负责整个集群的资源,而作业控制则是直接与应用程序相关的模块,每个进程只负责一个作业 | | 编程模型与数据处理引擎 | | MRv2重用了v1中的编程模型与数据处理引擎 | | 运行时环境 | 由JobTracker和TaskTracker两类服务组成,JT负责资源和任务的管理与调度,TT负责单个节点的资源管理和任务进行 | 将资源部管理与应用程序管理分开,分别又YARN和ApplicationMaster负责 | #### YARN基本架构 总体上仍然是Master/Slave架构 YARN的组成成分如下: - ResourceManager:一个全局的资源管理器,负责整个系统的资源管理与分配。它由两个组件构成: - 调度器(Scheduler):根据容量、队列等限制条件将资源分配给各个正在运行的应用程序 - 应用程序管理器(Application Manager ASM):负责整个系统中所有应用程序 - ApplicationMaster(AM)的主要功能有: - 与RM调度器协商以获取资源(Container) - 将得到的任务进一步分给内部任务 - 与NM通信以启动/停止任务 - 监控所有任务运行状态 - NodeManager:是每个节点上资源和任务管理器 - Container:是YARN山中的资源抽象,它封装了某个节点上的多维度资源 ## 第三章 Hadoop运行模式与大数据技术框架 Hadoop的运行模式主要有四种: - 本地模式 - 伪分布式 - 全分布式 - 高可用模式 ### 伪分布式模式 Hadoop可以运行在单个节点上,其中每一个Hadoop守护进程运行在单独的Java进程中,这个模式称之为伪分布式模式。Hadoop所有进程都运行在一台服务器以模拟全分布式模式,常用于学习阶段。 后台的五个进程为: - NameNode - DataNode - SecondaryNameNode - ResourceManager - NodeManager ### 高可用模式 Hadoop是一种主从式架构,这样就会有**单点故障**的问题 ## HDFS - 数据块(block) - HDFS默认的最基本的存储单位是128MB的数据块 - 128M为一块 - 一个文件如果小于一个数据块的大小,并不占用整个数据块的空间 - 存放策略(3副本) - 第一个和client同node - 第二个放在与第一个节点的不同机架中的随机的一个node - 第三个放在与第一个节点不同的机架中与第二个不同的随机node中 - NameNode 和DataNode - HDFS体系结构中有两类节点,一类是NameNode ( Master) ,又叫"元数据节点";另一类是DataNode (Slave) ,又叫"数据 节点”。 - 元数据节点用来管理文件系统的命名空间,作用如下: - 其将所有的文件和文件夹的元数据保存在一个文件 系统树中 - 这些信息也会在硬盘上保存成以下文件:命名空间镜像(namespace image)及修改日志(edit log) - 还保存了一个文件包括哪些数据块,分布在哪些数据节点上,然而这些信息并不存储在硬盘上,而是在系统启动的时候从数据节点收集而成的 - 数据节点是文件系统中真正存储数据的地方,作用如下: - 客户端(clien)或者 元数据信息(namenode)可以向数据节点请求写入或者读出数据块 - 周期性的向元数据节点回报其存储的数据块信息 - `hadoop.tmp.dir`,临时目录,其他临时目录的父目录,默认 `/tmp/hadoop-${user.name}`,在`core-site.xml`中配置 - 元数据节点目录结构,在`hdfs-site.xml`中配置`dfs.name.dir`参数,以`,`分隔,默认在`{hadoop.tmp.dir}/dir/name` - 数据节点目录结构 - 在`hdfs-site.xml`中配置参数`dfs.data.dir`,以`,`分隔 - HDFS通信协议 - 所有HDFS通信协议都是构建在TCP/IP协议上的 - HDFS安全模式 - Namenode启动后会进入一种称为安全模式的特殊状态。处于安全模式的Namenode是不会进行数据块的复制的。Namenode从所有的DataNode接受心跳信号和块状态报告 **Name Node、DataNode 和Client** - Namencodte 是分布式文件素统中的管理者, 主要负责管理 文件系统的命名空间、集群配置信息和存储块的复制等。NameNode 会将文件系统的Meta-data 存储在内存中,这些信息主要包括了文件信息,每个文件对应的文件块的信息和每个 文件块DataNode的信息等。 - DataNode是文件存储的基本单元, 它将Block 存储在本地文件系统中,保存了Block 的meta-data,同时 周期性地将所有存在的Block信息发送给NameNode. - Client 就是需要获取分布式文件系统文件的应用程序。 - Client读取文件信息 ### Hadoop Shell命令 实际上是属性,命令为:`hadoop fs -xx` - `cat`: - `chgrp`: change group - `chmod`: - `chown` - `copyFromLocal` - `copyToLocal` - `cp`: copy - `du`:显示目录中所有文件的大小 - `dus`:显示单个文件大小 - `expunge`:清空回收站 - `get`:复制到本地 - `getmerge`:将 source dir 中的文件链接成 local target dir - ls - lsr:递归ls - mkdir - movefromLocal - mv - put:本地到远程 - rm - rmr:递归rm - setrep:改变副本数 - stat:返回指定路径的统计信息 - tail:将尾部1kb的字节输出到stdout - test:检测文件是否存在 - text:将源文件输出为文本格式 - touchz:新建一个0自己的文件 Hadoop管理命令: - distcp:分布式拷贝(集群之间) - fsck:检查整个文件系统的健康情况 - jar:运行java文件 - job:用于和MapReduce交互 - balancer:运行集群平衡工具 - dfsadmin:运行一个dfs admin客户端 - namenode: 运行namenode ### java接口 Hadoop中关于文件操作类基本都在`org.apache.hadoop.fs`包中 Hadoop类库中最终面向用户提供接口是`FileSystem` ```java // 获取FileSystem具体类 static FileSystem get(Configuration conf); // 写文件 public FSDataOutputStream create(Path f) throws IOException; //读文件 // 上传文件到HDFS public void copyFileLocalFile(Path src,Path dist); // 重命名文件 public abstract boolean rename(Path src, Path dist); // 删除文件&目录 public abstract boolean delete(Path f, boolean recursive) throws IOException; // 创建目录 public boolean mkdirs(Path f) throws IOException; // 遍历目录 public abstract FileStatus[] listStatus(Path f) throws FileNotFoundException, IOException ``` ## MapReduce ```java public void map(Object Key,Text value,Context context) throws IOExcetion, InterruptedException{ } ``` ### MapReduce工作原理 MapReduce框架的运作完全基于“键值对”,即数据的输入是一批“键值对” (key-value) ,生成的结果也是批“键值对”,只是有时候它们的类型不一样而已。Key和value的类由于需要支持被序列化 (Serealire) 操作,所以它们必须要实现`Writable` 接口,而且key的类还必须实现`WirtableComparable`接口,使得可以让框架对数据集的执行排序操作,MapRedtre运行机制,按照时间顺序包括:输入分片(input split)、map 阶段、combiner 阶段、shuffle阶段和reduce阶段。 在进行map计算之前,MapReduce会根据输入文件计算输入分片 ### YARN运行流程 1. JobClient 向YARY中提交应用程序,其中 包括ApplicationMaster 程序、启动ApplicationMaster的命令、用户程序、环境变量、作业信息、文件位置信息等 2. RecourseManager为该应用程序分配第一个 Container. 并与对应的 Node-Manager 通信(通过心跳方式),更求它在这个Container中启动应用程序的ApplicationMaster 3. ApplicationMaster首先向ReoourceManager注册,这样用 户可以直接通过ResourceManager查看应用程序的运行状态。然后它将为各个任务申请资源,并监控它的运行状,直到运行结束 4. ApplicationMaster 采用轮询的方式通过RPC协议向ResourceManager申请和领取资源 5. 一旦ApplicationMaster申请到资源后,便与对应的NodeManager通信,要求它启动任务 6. NodeManager为任务设置好运行环境(包括环境变量、JAR包、二进制程序等)后,将任务启动命令写到一个脚本中, 并通过运行该脚本启动任务。 7. 各个任务通过某个RPC协议向AplcationMaster汇报自己的状态和进度,以让ApplicationMaster随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务。 8. 应用程序运行完成后, ApplicationMaster向ResourceManager注销并关闭自己 ### 作业关键过程详解 **map**:map任务最终是交给Map任务执行器 **Reduce**:从所有map节点取到属于自己的map输出 **Partitioner**:当Mapper处理好数据后,需要使用Partitioner(分区器)确定怎样合理地将Mapper输出分配到Reduce上 **Combiner**:相当于一个本地的Reduce,主要是对Mapper输出的大量本地文件进行一次合并。Combiner函数执行时机可能是在map的merge操作完成之前 ### MapReduce各种输入输出 **InputFormat**:负责处理MR的输入部分,来决定Map的数量,InputFormat **FileInputFormat**:是所有以文件作为数据源的InputFormat实现的基类,FileInputFormat保存作为job输入的所有文件 ## MapReduce 设计模式 - **过滤器模式**:设定某种条件,当负责条件时保留数据,不符合条件时丢弃数据 - **Top N**:根据数据集的排名,获取前N条记录 - **去重模式**:去重 - **数据重组**:按照一定的规划整理数据。数据重组要求划分的分区数量已经确定,划分分区的条件已经确定 -

SpringCloud 知识整理

# SpringCloud 知识点梳理 ## 第二章 构建spring cloud ### SpringBoot目录结构 - `src/main/java`:主程序入口 - `src/main/resources`:配置目录 - `src/test`:单元测试目录 ### Springboot依赖 Starter POMs 是一系列轻便的依赖包,是一套一站式的Spring相关技术的解决方案。 SpringBoot的StarterPOMs采用 `spring-boot-starter-*`命名,`*`表示一个特别的功能模块。 我们用到的POMs有: - `spring-boot-starter-web`:全栈Web开发模块 - `spring-boot-starter-test`:通用测试模块 ### 配置详解 springboot 的默认配置文件位置为 `src/main/resources/application.properties` #### 自定义参数 在application.yaml中 ```yaml book: name: springboot author: charles ``` 在java中 ```java @Component @Data public class Book{ @Value("${book.name}) private String name; } ``` `@value` 属性在加载配置时支持两种表达式: - PlaceHolder方式,格式如上,为`${*}` - SpEL表达式,格式为`#{*}` #### 多环境配置 多环境配置的文件名需要满足 `application-{profiles}.yaml`的格式,其中profile对应你的环境表示: - dev:开发 - test:测试 - prod:生产 加载那个环境需要在`application.yaml`中设置`spring.profile.active=*`属性 #### 加载顺序 springboot属性加载顺序如下: 1. 在命令行中传入的参数。 2. SPRING APPLICATION JSON中的属性。SPRING APPIATION JSON是以JSON格式配置在系统环境变量中的内容 3. Java.comp/env中的JNDI属性 4. Java的系统属性,可以通过`System.getProperties ()`获得的内容 5. 操作系统的环境变量 6. 通过`random.*`配置的随机属性 7. 位于当前应用 jar包之外,针对不同{profile}环境的配置文件内容,例如`application- {profile} .properties`或是YAML定义的配置文件。 8. 位于当前应用jar包之内,针对不同{profile}环境的配置文件内容,例如`aplication- {profilel} .properties` 或是YAML定义的配置文件。 9. 位于当前应用jar包之外的`application.properties`和YAML配置内容 10. 位于当前应用jar包之内的`application. properties`和YAML配置内容。 11. 在`@Configuration `注解修改的类中,通过`@PropertySource` 注解定义的属性。 12. 应用默认属性,使用`SpringApplication setDefautProperties`定义的内容 优先级按上面的顺序由高到低 ### actuator 要想使用actuator需要添加依赖:`spring-boot-starter-actuator`,然后在application添加如下配置以暴露全部端点: ```yaml management: endpotints: web: exposure: include: '*' ``` actuator的原生断点分为三类: - 应用配置类 - `/beans`:获取上下文所有的Bean,每个Bean都包含以下信息 - scope:作用域 - type:java类型 - resource:class文件的具体路径 - dependencies:依赖的bean的名称 - `/configprops`:配置属性 - profix:属性前缀 - properties:各个属性的名称和值 - `/env`:用来获取应用所有可用的环境属性报告 - `/mapping`:用来返回所有springMVC控制器的映射关系 - 度量指标类 - `/metrics`:返回当前应用的各种重要指标 - `/health`:用来获取应用的各类健康指标信息,当项目较为简单时,只标识出应用的状态 - UNKNOWN:未知状态,503 - UP:正常,200 - DOWN:失败,503 - OUT_OF_SERVICE:不提供对外服务,200 - 操作控制类 - `/shutdown`:只提供了这一个,不支持get请求 ## 第三章 服务治理 Alibaba Nacos ### 常见方案 `Spring Cloud Eureka`:既包含了服务端组件,又包含了客户端组件,并且服务端与客户端均通过java编写 `zookeeper`:一个开源的分布式应用程序协调服务,是Chubby的一个开源实现,是Hadoop和Hbase的重要组件 `Consul`:所以一个服务网络的解决方案,他是一个分布式的高可用的系统,而且开发使用都很简便。它提供了一个功能齐全的控制平面,它的主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心 `Nacos`:帮助发现、配置和管理微服务 ### Nacos 修改端口:默认端口8848,可以进入nacos/conf目录修改`application.propertoes`的`server.port`属性 启动:运行`startup.cmd -m standalone`(单例模式运行) 访问:默认账号密码都是nacos #### 注册服务提供者 ```yml spring: application: name: #要注册到注册中心的服务名称 cloud: nacos: server-addr: localhost:8848 #服务端地址 ``` #### 服务发现与消费 ```java @Bean @LoadBalanced public RestTemplete restTemplete(){ // ... } ``` ## 第四章 负载均衡 Ribbon SpringCloud Ribbon是一个基于HTTP和TCP的**客户端**负载均衡工具,他基于Netflix Ribbon实现。 虽然它只是一个工具类框架,不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在在每一个SpringCloud构建的微服务和基础设施中。 客户端负载均衡与服务端负载均衡最大的不同点在于服务清单所存储的位置。 - 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心 - 服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用 ### 分析 负载均衡器应该具备这样几种能力: - `ServiceInstance choose(String serviceId)`:根据传入的服务名从负载均衡器中挑选一个对应服务的实例 - `T excute(String serviceId, LoadBalancerRequest request)`:使用从负载均衡器中挑选出的服务实例来执行请求内容 - `URI reconstuctURI(ServiceInstance instance, URI origianl)`:为系统构建合适的hostzport形式的URI ### 负载均衡器 - AbstractLoadBalancer 是 ILoadBanlancer 接口的抽象实现,在该抽象类中定义了一个关于服务实例的分组枚举类ServerGroup - BaseLoadBalancer 是 Ribbon负载均衡器的基础实现类 - DynamicServerListLoadBalancer类是继承于BaseLoadBalancer类,他是对基础负载均衡器的扩展 - ZoneAwareLoadBalancer 负载均衡器是对 DynamicServerListLoadBalancer的扩展 ### 负载均衡策略 - AbstractLoadBalancerRule,负载均衡器策略抽象类,在该抽象类中定义了负载均衡器ILoadBalancer对象 - ClientConfigEnabledRoundRobinRule,该策略较为特殊,一般不直接使用它,因为它本身并没有实现什么特殊的处理逻辑,他在内部定义了一个RoundRobinRule策略 - PredicateBaseRule,是一个抽象策略,继承了ClientConfigEnabledRoundRobinRule,从其命名中可以看出来这个一个基于Predicate实现的策略 | 内置负载均衡规则类 | 规则描述 | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | RoundRobinRule | 简单轮询服务列表来选择服务器。它是Rbbon默认的负载均衡规则。 | | AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1) 在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。(2) 并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabiliyFilteringRule规则的客户端也会将其忽略。 | | WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。 服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 | | ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zome可以理解为个机房、一个机架等。而后再对Zome内的多个服务做轮询 | | BestvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 | | RandomRule | 随机选择一个可用的服务器 | | RetryRule | 重试机制的选择逻辑 | | NacosRule | 支持优先调用同个集群实例的 rbbon负载均衡规则 | ### 配置详解 #### 自动化配置 Ribbon能够自动配置下面这些接口的实现 - IClientConfig: Ribon的客户端配置, 默认采用com.netflix.client.config.DefaultlCientCnfigimpl实现 - IRule: Ribbon的负载均衡策略,默认采用com.netflix.loadbalancer,ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。 - IPing: Ribbon的实例检查策略,默认采用com.netfix.loadbalancer.DummyPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服上面务实例都是可用的。 - ServerList: 服务实例清单的维护机制,默认采用com.netfix.loadbalancer.ConfigurationBasedServerList实现。 - ServerListFilter: 服务实例清单过滤机制,默认采用org.springrameworkcloud.netflix.ribbon.ZomeAwareKiadBakabcer实现,该策略能够优先过滤出与请求调用方处于同区域的服务实例。 - lLoadBalancer: 负载均衡器,默认采用com.netflix.loadbalancer.ZoneAwareloadBalancer实现,它具备了区域感知的能力。 针对一些个性化需求可以在springboot中创建对应的实现实例即可覆盖默认配置,比如 ```java @Bean public Irule rule(){ return new NacosRule(); } ``` #### 参数配置 对于RIbbon的配置有两种: - 全局配置:使用`ribbon.<key> = <value>`格式进行配置 - 指定客户端配置:`<client>.ribbon.<key>=<value>`的格式进行配置 **IRule配置**: ```yaml userservice: ribbon: NFLoadBalancerRuleClassName: <value> # 负载均衡规则类全名 ``` **饥饿加载**: ```yml ribbon: eager-load: enabled:true client: - userservice ``` **与Nacos结合** ```yaml spring: application: name: userservice #服务名称 cloud: nacos: server-addr: <value> #服务端地址 discovery: cluster-name: <value> # 区域集群名称 ``` **重试机制** ```yaml userservice: ribbon: ConnectTimeout: 200 #链接超时时间 ReadTimeout: 200 #请求超时时间 OkToRetryOnAllOperations: true #是否对所有操作都允许重试 MaxAutoRetriesNextServer: 2 # 切换实例的重试次数 MaxAutoRetries: 1 # 当前实例的重试次数 ``` ## 第五章 服务容错保护 Hystrix 快速入门: ```java @EnableCircuitBreaker //启动断路器 ``` ### 分析 #### 命令执行 - `execute()` 同步执行,从依赖的服务返回个单一的结果对象,或是在发生错误的时候抛出异常 - `queue()` 异步执行, 直接返回一个Future对象,其中包含了服务执行结束时要返回单一结果对象。 - `observe()` 返回来自依赖项的响应的Observable对象,它代表了操作的多个结果,它是一个hot Observable - `toObservable()` 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个cold Observable。 #### 断路器是否打开 - 如果断路器是打开的,那么Hystrix不会执行命令,而是转到fallback处理逻辑 - 如果断路器关闭,则会检测是否有可用资源执行命令 #### 请求方法 - `HystrixCommand.run()` 返回一个单一的结果或抛出异常 - `HystrixObservableCommand,constuct()` 返回一个Observable 对象来发射多个结果或通过 onError 发送错误通知 #### 计算断路器健康度 Hystrix会将 成功 、 失败 、 拒绝 、 超时等信息报告给断路器 #### fallback处理 - `execute()` : 抛出异常。 - `queue()`: 正常返回Future对象,但是当调用get 0来获取结果的时候会抛出异常。 - `observe()`: 正常返回Observable 对象,当订阅它的时候,将立即通过调用订阅者的onError方法来通知中止请求。 - `tobservable()`: 正常返回Obsevable对象,当订阅它的时候, 将通过调用订阅者的onError方法来通知中止请求。 #### 成功的响应 - `exeoute()` 以与queue 相同的方式获取一个Future,然后在这个Future 上调用get()获取由Observable发出的单个值 - `queue()` 将Obervable转换为一个BlockingObservable,以便它可以被转换为一个Future然后返回这个Future - `observe ()` 在toObservable()产生原始Observable 之后立即订阅它,让命令能够马上开始异步执行,井返回一个 Observable对象,当调用它的subscribe时,将重新产生结果和通知给订阅者。 - `toObservable()` 返回最原始的Observable,必须通过订阅它才会真正触发命令的执行流程。 ### 断路器 三个抽象方法: - `allowRequest()`:每个Hystrix的命令的请求都通过他去判断是否被执行 - `isOpen()`:返回当前断路器是否打开 - `markSuccess()`:闭合断路器 #### 创建请求命令 ```java @GetMapping("/{param}") @HystrixCommand(fallbackMethod = "showAppInfoFallback") public String showAppInfo(@PathVariable("param") String params){ String url = "..."; return restTemplate.getForObject(url,String.class); } ``` #### 定义服务降级 ```java public String showAppInfoFallback(String param){ return "busy"; } ``` ```java /** * 服务降级方法 */ @HystrixCommand(fallbackMethod = "showAppInfoCallback") public String ShowAppInfoFallback(String param){ ... } ``` #### 异常获取 注解配置方式的实现非常简单,只需要fallback实现方法的参数中增加Throwable对象的定义,这样在方法内部就可以获取除法服务降级的具体异常内容了 ```java public String showAppInfoFallBack(String param,Throwable exception){ // ...something } ``` #### @Hystrix注解 ```java @HystrixCommand(fallbackMethod = "..." ignoreException = {Exception.class} commandKey = "..." groupKey = "..." threadPoolKey = "..."                 ) ``` #### 请求缓存 | 注解 | 描述 | 属性 | | ------------ | ---------------------------------------------------------------------------------------------------------------------- | ------------------------- | | @CacheResult | 该注解用来标记请求命令返回的结果cacheKeyMethod应该被缓存,它必须与@HystrixCommand注解结合使用 | cacheKeyMethod | | @CacheRemove | 该注解用来让请求命令的缓存失效,失效的缓存根据定义的Key决定 | commandKey,cacheKeyMethod | | @CacheKey | 该注解用来在请求命令的参数上标记,使其作为缓存的Key值,如果没有标注则会使用所有参数。如果同时还使用了@CacheResult和@CacheRemove注解的cacheKeyMethod方法指定缓存Key的生成,那么该注解将不会起作用 | value | #### 设置请求缓存 ```java @CacheResult ``` 定义一个filter ```java HystrixRequestContext context = HystrixRequestContext.initializeContext(); ``` #### 定义缓存key ```java @HystrixCommand(fallbackMethod = "showAppInfoCallback") @CacheResult(CacheKeyMethod = "cacheKey") public User getUserInfo(@PathVariable("id") @CacheKey String id){ // something } public String cacheKey(String id){ return id; } ``` #### 缓存清理 `@CacheRemove`注解的`commandKey`属性是必须要指定的,它用来指明需要使用请求缓存的请求命令,因为只有通过该属性的配置,Hystrix才能知道正确的请求命令缓存位置、 #### 注解请求合并 `@HystrixCollapser` #### 请求合并的额外开销 - 请求命令本身的延迟 - 延迟时间窗内的并发量 ### 属性详解 4个不同优先级别的配置(优先级由低到高): - 全局数认值:如果没有设置下面三个级别的属性,那么这个属性就是默认值。由于该属性通过代码定义,所以对于这个级别,我们主要关注它在代码中定义的默认值即可。 - 全同配置属性:通过在配置文件中定义全局属性值,在应用启动时或在与SpringCloud Comfig和Spring Cloud Bus实现的动态刷新配置功能配合下,可以实现对“全局默认值”的覆盖以及在运行期对“全局默认值”的动态调整 - 实例默认值。通过代码为实例定义的默认值。通过代码的方式为实例设置属性值来覆盖默认的全同配置。 - 实例配置属性。通过配置文件来为指定的实例进行属性配置,以覆盖前面的三个默认值 **Command属性** | 属性级别 | 默认值、配置方式、配置属性 | | ------ | -------------------------------------------------------------------------- | | 全局默认值 | THRAD | | 全局配置属性 | Hystrix.command.default.execution.isolation.strategy | | 实例默认值 | 可通过@HystrixProperty(name = "excution.isolation.strategy" value = "THREAD") | | 实例配置属性 | hystrix.command.HystrixCommandKey.execution.isolation.strategy | ### Hystrix 仪表盘 为启动类加上@EnableHystrixDashboard, 启用Hystrix DashBoard功能 Hystrix DashBoard 支持三种不同的监控方式,分别是: - 默认的集群监控,通过URL `http://hostname:port/turbine.stream`开启 - 指定的集群监控,通过URL `http://hostname:port/turbine.stream?cluster=[clusterName]`开启 - 单体应用的监控,通过URL `http://hostname:port/hystrix.stream`开启 监控界面各项元素的意义: 实心圆:颜色表示健康程度,大小表示请求流量 曲线:用来记录两分钟内流量的相对变化 ### Turbine集群监控 通过启动类加`@EnableTurbine`注解开启 ## 第六章 声明式服务调用 Feign 启用Feign:在启动类添加`@EnableFeignClients`注解 定义服务接口,定义一个接口(Interface),通过`@FeignClient`注解指定服务名来绑定服务,然后再使用Spring MVC注解来绑定具体该服务提供的REST接口(这里服务名不区分大小写) ### 参数绑定 ```java // 获取参数 public String hello(@RequestParam("name") String name){} // 获取路径变量 public String hello(@PathVariable("id") String name){} // 获取header public String hello(@RequestHeader String name){} // 获取body public String hello(String name){@RequestBody User user} ``` ### 配置 #### Ribbon 全局配置 ```yaml ribbon: NFLoadBalancerRuleClassName: * #负载均衡规则全类名 ConnectTimeout: 200 #链接超时时间 ReadTimeout: 200 #请求超时时间 OkToRetryOnAllOperations: true #是否对所有操作都允许重试 MaxAutoRetriesNextServer: 2 # 切换实例的重试次数 MaxAutoRetries: 1 # 当前实例的重试次数 ``` 指定服务配置 ```yaml userservice: ribbon: NFLoadBalancerRuleClassName: * #负载均衡规则全类名 ConnectTimeout: 200 #链接超时时间 ReadTimeout: 200 #请求超时时间 OkToRetryOnAllOperations: true #是否对所有操作都允许重试 MaxAutoRetriesNextServer: 2 # 切换实例的重试次数 MaxAutoRetries: 1 # 当前实例的重试次数 ``` #### Hystrix 全局配置 ```yaml hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 5000 ``` **禁用Hystrix** 通过`feign.hystrix.enable = false` 来关闭Hystrix或 `hystrix.command.default.execution.timeout.enabled = false`来关闭熔断功能 如果要局部禁用,需要使用`@Scope("protoype")`注解来指定配置实例 **服务降级配置** ```java @FeignClient(value = "userservice", fallback= UserClientFallback.class) public interface UserClient{ ... } ``` #### 请求压缩 ```yaml feign: compression: request: enable: true response: enable: false ``` 可以针对请求的数据类型以及触发压缩的大小下限进行设置 ```yaml feign: compression: request: enable: true mime-types: text/html, application/xml. application/json min-request-size: 1024 # 设置触发压缩的大小下限 ``` #### 日志配置 Feign的Logger级别有下面四种: - NONE:不记录任何信息 - BASIC:金鸡路请求方法、URL以及响应码和执行时间 - HEADERS:除了BASIC之外还记录请求和响应头 - FULL:记录所有请求与响应的明细 全局配置 ```yaml feign: client: config: metaDataClient: connect-timeout: 3000 read-timeout: 3000 default: loggerLevel: FULL ``` 局部配置 ```yaml feign: client: config: metaDataClient: connect-timeout: 3000 read-timeout: 3000 userservice: # 服务名称 loggerLevel: BASIC ``` Java配置类 ```java public class UserClientConfig(){ @Bean public Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } } ```

Android 60问60答 (一篇复习整个Android)

# Android 60问60答 (一篇复习整个Android) ## 有哪些移动端平台? 厂商开发平台: - los - 黑莓(blackBerry) 第三方私有平台:第三方开发供移动设备厂商使用 - WindowsMobile 免费开源平台: - Android - Symbian ## Android中Linux内核的作用 - 充当用户和设备之间的接口。 - 管理所用活动内存和资源共享 - 充当设备上所安装的应用的主机 - 常由硬件制造商使用,因为它提供一个硬件抽象层,可在硬件发生变化时,确保上层保持不变 ## Android使用的数据库是? SQLite,提供可用于所有应用的功能强大的轻量级关系数据库 ## Android有哪些构建块? - 活动:专为一个清晰的目的提供独特的可视化UI - 服务:始终在后台,完成特定的任务 - 内容提供者:存储和检索存储在文件、SQLite、web或任何其他持久化存储位置的数据 - 广播接收器:相应系统范围内广播通知的应用的组成部分 ## Android项目的目录结构 - `/src/main/java`:项目使用的java源文件 - `/build`:编译后生成文件 - `libs`:专有库 - `/src/main/resourse`:应用资源文件,有 - Drawable:位图文件或者绘制对象资源类型的xml文件 - Mipmap:使用于不同启动器图标密度的可会址对象文件 - Layout:用于定义用户界面布局的XML文件 - Menu:定义应用菜单的XML文件 - Raw:需要以原始形式保存的任意文件 - Values:包括字符串、整数、颜色等简单值的xml文件 ## 小部件的常用属性 | 属性名称 | 关联方法 | 描述 | | ---------------------- | --------------- | ------------------------------ | | android:layout_gravity | | 定义如何在布局容器内对齐小部件 | | android_gravity | setGravity(int) | 设置对象在容器中的放置 | | android:layout_weight | | 指定小部件的大小比例。如果不拉伸则设置为0,否则根据权重拉伸 | ### Android颜色表示 AARRGGBB 透明度|红|绿|蓝 ## ListView小部件的作用 此小部件向用户显示一个列表,它与Java中的列表框组件类似,ListView默认支持垂直滚动。要想使用小部件,如下所示: ```xml <ListView android:id="@id/listview" android:layout_width="match_parent" android:layoutheight="warp_content" > </ListView> ``` ## ImageView小部件 用于显示图像,可以从其他资源(例如drawable目录、因特网、内容提供者)加载图像,它根据源图像的大小调整自己的尺寸。要想使用该小部件,如下: ```xml <ImageView android:id="@id/imageview" android:layout_width="match_parent" android:layoutheight="warp_content" android:src="@drawable/map" > </IamgeView> ``` ## WebView小组件 用于显示网页,使用如下 ```xml <WebView android:id="@id/webview" android:layout_width="match_parent" android:layoutheight="warp_content" > </WebView> ``` 要想加载网页需要在Java中调用: ```java WebView view = (WebView)findViewById(R.id.webview); view.loadUrl("https://google.com"); ``` ## Android布局有那些? ### 表格布局 此布局以行和列的形式排列其子视图。表格航对象创建可容纳小部件的行,表格布局的最大列数取决于用最大列数分割的行。 使用表格布局如下: ```xml <TableLayout xmlns:android="略" android:layout_height="fill_parent" android:layout_height="fill_parent" android:stretchColumns="1" > <TableRow> <TextView /> <TextView /> <TextView /> <!-- 这里有三个元素,故是三列 --> </TableRow> </TableLayout> ``` ### 框架布局 以堆栈格式从上往下添加视图,项位置用`android:gravity`属性设置,布局的大小取决于最大视图的大小。 使用框架布局如下: ```xml <FrameLayout xmlns:android:"略" android:layout_height="fill_parent" android:layout_height="fill_parent" > <ImageView /> <ImageView /> <ImageView /> <!-- 这里有三个元素,故是三行 --> </FrameLayout> ``` ## 使用那个方法来关联活动? `setContentView()`方法用于将UI与活动关联,用法如下: ```java public void onCreate(Bundle saveInstanceState){ super.onCreate(sabeInstanceState); setContentView(R.layout.main); } ``` ## 如何注册活动为主活动? 只有在`manifest.xml`中注册过的活动才能被系统访问,要注册活动如下: ```xml <manifest ...> <application ...> <activity android:name="..." /> </application> </manifest> ``` activity标签中也可以添加一些属性,比如可以指定某活动为主活动: ```xml <activity android:name=".mainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <categort android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> ``` ## 活动生命周期? - 正在运行:活动对于用户是在屏幕上可见的 - 已暂停:焦点在另一个活动上,但是此活动依然可见 - 已停止;活动被另一个活动安全覆盖,且现处于后台中。停止的活动也是活动的,其对象依然保留在内存中 ### 活动生命周期方法 - **onResume**:此方法在活动开始与用户交互之前调用。当活动位于栈顶且准备好接收用户输入就会调用。 ![在这里插入图片描述](../../../assets/default.png) ## Intent的作用 意图(intent)是用于激活应用组件(活动、服务和广播接收器)的消息 ## 如何使用显式intent启动活动? 显式Intent指定目标组件的名称以激活组件。 ```java Intent intent = new Intent(MyFirstActivity.this,MySecondActicity.class); startActivity(intent); ``` ## 如何使用隐式intent启动活动? 隐式intent用于激活其他应用中的组件,不通过名称指定目标组件。要使得隐式的intent生效,**需要为被调用的组件设置intent过滤器,`intent-filter`在`manifest.xml`中设置**。之后可以使用隐式intent表述action,Android会自动与现有组件的intent过滤器比较,找到合适的组件 ```java Intent intent = new Intent(); intent.setAction("com.anna.chapter6.a6"); intent.addCategory(Intent.CATEGORY_DEAFULT); startActivity(intent); ``` ## 如何使用Intent在组件之间传递数据 ### 使用Intent 写入方: ```java intent.putExtra("name","Nicole") Intent intent = new Intent(MainActivity.this,OtherActivity.class); intent.putExtra("name", "Nicole"); intent.putExtra("age", 25); intent.putExtra("address", "Shenzhen"); ``` 读取方: ```java Intent intent = getIntent(); String nameString = intent.getStringExtra("name"); int age = intent.getIntExtra("age",0); String addressString = intent.getStringExtra("address"); ``` ### 使用Bundle 写入方: ```java Intent intent = new Intent(MainActivity.this,OtherActivity.class); Bundle bundle = new Bundle(); bundle.putString("name", "Ben"); bundle.putInt("age", 28); bundle.putString("address", "China"); intent.putExtras(bundle); //将bundle传入intent中。 ``` 读取方: ```java Intent intent = getIntent(); Bundle bundle = intent.getExtras(); String nameString = bundle.getString("name"); int age = bundle.getInt("age"); String addressString = bundle.getString("address"); ``` ## ADB是什么 Android Debug Bridge(ADB)充当开发硬件与设备/仿真器之间的通信媒介 ## ADB有哪些常用命令? - device:生成已连接的设备列表 - pull:将指定文件从设备复制到计算机 - push:将指定文件从计算机复制到设备 - `install<path>`:在设备上安装应用(指定路径) ## Logcat有哪些日志级别? - 错误,使用Log.e()记录 - 警告,使用Log.w()记录 - 信息,使用Log.i()记录 - 调试,使用Log.d()记录 - 详细信息,使用Log.v()记录 ## 内部存储区与外部存储器的概念 设备内部的储存,以文件存储,属于应用私有 外部存储器存放在SD卡,所有应用与用户都可以访问 ## 共享首选项是什么? 共享首选项是轻量级机制,用于存储基本数据类型的键值对,是快速存储默认值、类实例变量、用户界面状态以及用户偏好的理想方式 ## 一个程序的数据库在哪? `/data/data/<package name>/databases` ## 如何以编程的方式创建数据库? 在Android中,可以使用`android.database.sqlite.SQLiteDatabase`以编程方式创建数据库。SQLiteDatabase类公开各种方法来管理SQLite数据库。 ## 操作数据库的方法有哪些? - `openOrCreateDatabase(String path,SQLiteDatabase.CursorFactory factory,DatabaseErrorHandler errorHandler)` - `updata(String table,ContentValue values,String whereClause,String[] whereArgs)` - `query(SQLiteDatabase db,String[] columns, String selection, Srtring[] selections,String groupby,String having,String sortOrder)` - `getColumnIndex(String ColumnName)` 返回给的列名的基于0的索引,或1 - `getColumnName(int ColumnIndex)` - `excute()` 如果不是SELECT、INSERT、DELETE或UPDATE,则执行SQL语句 ## 如何检索受SQL语句影响的行数? 使用`excuteUpadataDelete()`或`excuteInsert()`方法 ## 如何访问内容提供者公开的数据? 使用`android.content.ContentResolver`类 ```java ContentResolver resolver = getContentResolver(); ``` ## 如何读写内部存储区 **写文件**: 1. 使用`android.content.Context`类的`openFileOutput()`方法打开或创建文件 2. 使用`java.io.FileOutputStream`类的`write()`方法将数据写入文件 3. 使用`java.io.FileOutputStream`类的`close()`方法关闭文件 **读文件** 1. 使用`android.content.Context`类的`openFileOutput()`方法打开或创建文件 2. 使用`java.io.FileOutputStream`类的`read()`方法从文件读取数据 3. 使用`java.io.FileOutputStream`类的`close()`方法关闭文件 ## getFIleDir与getDir的作用 - `getFileDir()`:获取用于保存的内部文件的文件系统目录的绝对路径 - `getDIr(String dirname,int node)`:在您的内部存储空间创建或打开一个现有目录,接收以下参数: - dirname:要检索的文件夹名称 - mode:文件的创建模式 ## 外部环境Enviroment的有哪些状态? | 常量 | 描述 | | ----------------------- | ------------ | | MEDID_MOUNTED | 已经在加载,可以读写访问 | | MEDID_REMOVED | 不存在 | | MEDID_UNMOUNTED | 已存在但是没有加载 | | MEDID_MOUNTED_READ_ONLY | 已存在但只读 | ## `getExternalStorageDirectory(String type)`:检索外部存储目录 `getExternalStorageState()`:检索外部存储设备当前的状态,返回Environment中的某个常量 ## 如何读写外部设备 需要使用`getExternalStorageDirectory`方法获取到外部存储的路径,然后读写过程与内部存储的读写方式相同 ⚠️:写入之前需要在manifest文件中指定所需的权限 ```xml <user-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"> </user-permission> ``` ## 如何检索公共首选项 需要使用`SharedPerferences`接口提供的方法 - `contains(String key)`:检查首选项是否包含特定首选项,参数指定要检查的key - `getXXX(String key,XXX defValue)`:它从首选项中检索指定数据类型的值 ## 如何获取网络权限 ```xml <user-permission android:name="android.permission.INTERNET"> </user-permission> ``` ## 如何启动一个服务 可以使用`startService)`方法从引用组建启动服务 ```java Intent intent = new Intent(this,myService.class); startService(intent); ``` ## 启动的服务与绑定的服务的区别 ### 启动的服务 启动的服务**生命周期**如下: `onCreate()`--->`onStartCommand()`(`onStart()`方法已过时) ---> `onDestory()` **特点**: 一旦服务开启跟调用者(开启者)就没有任何关系了。 开启者退出了,开启者挂了,服务还在后台长期的运行。 开启者**不能调用**服务里面的方法。 ### 绑定的服务 绑定的服务生命周期如下: `onCreate()` --->`onBind()`--->`onunbind()`--->`onDestory()` **注意**:绑定服务不会调用`onstart()`或者`onstartcommand()`方法 **特点**: bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。 绑定者可以调用服务里面的方法。 ## 什么是广播接收器? 广播以intent的形式传达。要接收和响应广播,应用要包括一个称为广播接收器的组件。广播接收器不提供UI。不过,它可以创建状态栏通知,以在收到播时向用户发出提示 ## 创建广播接收器要调用哪个方法? - 创建一个类来拓展BroadcastReceiver类 - 在这个派生类中重写onReceive()方法 ## 如何注册广播接收器? 以下两种方法都可以: **在订单文件中配置** ```xml <application ...> <receiver android:name="./myReceiver"> <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter> </receiver> <user-permission android:name="android.permisson.ACCESSS_NETWORK_STATE" /> </application> ``` **以动态的方式** 使用与主应用线程中的过滤器匹配的广播intent调用`registerReceiver`方法,这个方法的签名是:`public abstract Intent registerReceiver(BroadcastReceiver reveiver, IntentFilter filter)` ## 创建一个菜单资源要用哪些元素? - `<menu>`:菜单资源的根结点 - `<item>`:用于创建菜单项 - `<group>`:是一个可选元素,允许对菜单项进行分类,从而允许共享属性 ## 创建菜单要使用哪两个方法? - 要重写`onCreateIotinsMenu()`方法 - 在这个方法中创建`android.view.MenuInflator`类的`expand`方法来拓展菜单 ## ViewPage是什么? 可以使用AndroidX的ViewPage小部件创建滑动视窗 ## 如何将主题引用到整个引用? 在manifest中将`android:thene`属性添加到application标签 ## 自定义控件要拓展哪个类? View ## setGravity怎么使用? 可以使用`setGravity(gavity,xOffset,yOffset)`方法将非常灵活地将消息条放在屏幕的任何位置 ```java Toast toast = Toast,makeText(...); toast.setGravity(Gravity.BOTTOM|Gravity.LEFT,0,0); toast.show(); ``` ## 设置闹铃到几种方法的区别? - set:设置闹铃 - setRepeating:设置重复闹铃 - setInexactRepeating:按照定义的时间间隔重复闹铃,但如果设备处于休眠状态则不会唤醒,这样更省电,即不准确的重复闹铃。 ## 常见的定位技术有哪四种? - GPS - WiFi定位 - IP地址定位 - 三角测量法 ## 如何侦听传入的SMS ```java Bundle bundle = intent.getExtras(); if(buddle!=null){ Object[] pdus = (Object[]) bundle.get("pdus); SmsMessage[] messages = new SmsMessage[pdus.length]; for(int i=0;i<pdus.length;i++){ messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]) } } ``` ## 如何监听电话状态? ```java String nameService = Content.TELEPHONE_SERVICE; TelephonyManager teleManager = (TelephoneManager)getSystemService(nameService); ``` ## Canvas与Paint的区别 Canvas(画布)表示可以绘制图形和图像的表面。提供了多种方法来绘制简单的图形、路径、位图和文本。 Paint(画笔)提供绘制富豪、文本和图形的样式和颜色的相关信息。 ## 如何画一个圆? ```java cavas.onDraw(){ // drawCircle(float cx,float cy,float radius,Paint paint); cavas.drawCircle(50,50,30,paint); } ``` ## MediaPlayer的getCurrentPosition与setOnPerparedListener方法的作用 - `getCurrentPosition()`:返回当前播放的位置,单位毫秒 - `setOnPerparedListener(MediaPlayer.onPerparedListener listener)`:注册一个回调函数,当媒体源准备好播放时,可调用该函数 ## 在哪里声明权限? AndroidManifest.xml ## 如何运行时申请权限? 调用`checkSelfPermisson(String perm)`来检查权限,以保证用户没有把权限撤销 使用`requestPermissions(String permissions,int requestCode)`来请求权限,参数一是请求的权限,参数二随便给一个数字 ## Android打包成什么? .APK

我的大学

# 我的大学 自己的大学生活居然已经结束了,从此以后,便不再是学生了。自己做学生,已经做了近20年,没想到这居然也有结束的一天,而且这一天来地如此漫不经心,一如平日。没想到埋葬我学生时代的日子,居然是那么普通,那么平淡,那么不动人心弦。以至于在那一天过去好长一段时间之后,我才猛地反应过来:哦。原来已经不再是学生了。 虽然已经到了这个地步了,已经结束了一段时间了,已经晚了。但是我还是想在这里,回忆一下我的那一段,没有任何值得说道,但又值得一说的大学生活。毕竟再晚的话,就全都忘光了。 ## Ⅰ 不算好的起点 经过三年地狱般的高中生活,我,一个非常刻苦但又并不十分努力的学生,以一个非常不理想又不算低的成绩,在一个不算好也不算坏的城市,上了一个在双非中顶尖的学校。 哈,真实矛盾又老套。 在高一的时候,我常常幻想自己即是天选,时不时做着不知所谓的清北梦。到了高二,见识过强者,认清了现实的自己把目标定为了北邮——一所CS非常好的学校。而在高三的时候,心灰意冷的我将目标定为了北交——至少是一所位于北京的211。 但是很遗憾,正所谓求之上者得之中,求之中者得之下。而像我这样的求之下者,最终当然是什么都没有得到。 *最低だ* 想想高中时候的自己,真是一个无耻的可怜虫。这些年来,我一直在说自己的注意力是多么的不集中,因此多么不擅长解决复杂的数学和理科问题。而那一年的物理和数学又是多么困难,因此我的发挥是如何如何的不好,最终成绩是多么多么的不理想云云。 但是真的只是如此吗?我把一切都归结于自己的性格是多么的不适合应付数学和物理的考试。但是我却从来没有说我细腻的感情和广阔的思维在英语和语文这两门科目上给予了我多大的优势。性格与运气只是一部分原因,我频繁的提及它们只是想掩饰另一部分原因。 我在高中的时候情不自禁的喜欢上了一个人。当然,她并不喜欢我。要否认这件事对学习没有影响,即使是对我这样的人来说也还是太难了。但是喜欢上这样一个人我并没有任何怨言。 而让我感到不安的事情是,因为被她拒绝,我神使鬼差的接受了另一个女生的示好,并与她开始了恋爱。就在高中快要结束的最后一段时间。当然,那时候的我脆弱、哀伤,没有谈过恋爱,并且处于青春期。但是这都不该成为借口,不该成为爱上一个自己本不喜欢的人的借口。 当然,在高考结束后,这段恋爱也顺延到了我的大学。现在想想,那一段时间是对自己的不负责任,但更多的是对另一个人的不负责任。她是如此的敏感、多疑、软弱、暴躁。但是这都不应该是我伤害她的理由。许多人的第一段感情都是模糊地就结束了,但是这也不能将我对她的伤害正当化。我对她的伤害不需要刻意,接受她的示好那一刻,就已经在开始了。 当然,她也在伤害着我,但是这不是我应该反思的事情,而是她应该反思的事情。直到现在,我还是讨厌谈及这段感情,但是至少经历过一段感情之后,总不至于像一只饿狗一样对待感情,这也算是少有的一点好处吧。 在大学的上半年,带着刚刚走出高中的稚嫩,我进入了大学,充满迷茫。那个时候我想要考研,目标是北邮,准备一雪前耻。而20年年底,疫情正浓。那个时候,想出校门几乎是不可能的。尽管如此,我依然绞尽脑汁通过大学的围墙。我是一个多么爱好自由的人,那个时候我觉得:墙的那边,是自由。 对于大学,至少有一件事我做对了,我选择了一个适合我的专业。当开始上C语言这门课的时候,我是多么的兴奋,我觉得自己很幸福。摸索着写下第一段`hello world`,我仿佛也感悟到了世界的美好。大一上半年让我印象最深的,无疑就是C语言这门课了。感谢教我这门课的吴老师,她对我有充分的信任。信任!对我来说,信任这种东西,是师生之间最难得、最美好、最珍贵的羁绊。她解答我奇诡的问题,放任我自由的探索自己的兴趣,以及用超出常规的手段解决她的问题。 我像一匹脱缰的野马奔驰在知识的草原上,我踏过每一寸草地,嫌弃后面的驽马跑的太慢。我迈开马蹄,远远的甩开了同伴,却也不担心迷路。在学期的一半,我就已经自学完了整本教材(按照课表,这本教材要在这个学期学完大部分,留下几章到下学期学,以及几章不需要学习),并且自己看完了《C与指针》,以及在假期看完了《C++ primer plus》。《C与指针》是一本很难理解的书,但当我在未来解决关于C语言中的指针问题时,脑海中总会突然浮现出书中内容,并在那一刻理解那段文字的意义。 另一个让我记忆颇深的是王老师的选修课:中国古典诗词鉴赏。这又是一个我十分擅长的领域,我在课堂上尽情的出风头,回答老师的问题,与他交换对诗词的理解以及交换双方的诗作,最后在下课后,八点五十分,我和他一起走出教学楼,在美丽的剑湖前散散步,然后目送他开车离开,我去711买一颗饭团。他是一个高材生,也是一个好老师。 虽然有一些美好,但是在半个学期结束之后,我还是对大学大失所望。我以为大学应该是自由的地方,学生与老师自由的交换的自己的想法,大家都有一些崇高的理想,在努力做一些很酷的事情。但是到了大学之后,我发现我能看见的只有没有什么学术氛围的教室与图书馆,没有什么理想的同学和老师,以及令人作呕的官僚又僵化的管理模式。我承认,青大的管理已经算是国内众多大学中的佼佼者了。但是即便如此,我也必须得挑明,这与我的理想存在太大的差距。这让我相信,研究生的生活大概也不会好太多,学校其实并不适合我,因此,我选择了放弃读研。 在大学的上半年结束后,我终于和她分手了。在那之后,我用了一个星期在W3CSchool学完了HTML、CSS以及JavaScript,然后大病了一场。 痊愈之后,我的大学生活仿佛正式开始了。 ## Ⅱ 最牛逼的大艺团 但是回想我的大一生活,我也还是做出了一些正确的决定的。比如,加入了大艺团。当初想要加入大艺团仅仅是因为我觉得人在大学理当加入一些社团。当大艺团到操场上进行军训慰问的时候,我被他们吸引了。 真有活力啊,如果我加入他们,我也能变得这么有活力吗? 这么想着,我递交了申请表,最终成功加入了大艺团的新媒体中心和微电影剧组。最开始接到的任务都来自微电影剧组的lt姐和yw姐(因为平时都是称呼名字,这种敏感信息就不盒了,如果不知道外号的就用首字母拼音代替了,实在抱歉)。她们两个是相当美丽、温柔又可靠的学姐。 但是刚开始接到任务的时候,我的想法却很单纯,因为当时在东校区,而大艺团的活动大多在中心校区,所以每次去参加活动大概都需要开具请假条——找到了一个通往自由的好办法!通过P请假条,我获得了自由出入学校的特权。在疫情期间,这是多么伟大的权力。 此时我还不知道,在我大一的下学期,当我开始认真的对待自己的大学生活时,我将会在这里遇到足以让我怀念一生的好朋友:小黄、聪姐、万妈妈、yn、kt、一滑、csj、hyj、wjy以及令人尊敬的童哥。 在那半年,发生了很多很有趣的事情。+7姐带我们去city walk,吃海鲜,zy姐在清明带我们去中山公园拍樱花,wjy借给我一部相机,这是我的摄影启蒙。那一天的zy姐和yn,是比樱花更美的模特。在那之后,我带着借来的相机参与了一场又一场的拍摄,从每一张都是糊照的丑照,到渐渐有几张能够被选上推文,那段时间摄影技术也算有些进步呢。 而在一场场的活动中,我与这些伙伴们的羁绊也在一点点的加深着。zy姐、+7姐和xy姐都是很好的学姐,能够很好的引领我们。而和wjy,我们两个男生私下里有很好交情,时不时约着来一场city walk。至于其他人,我们大都在大二的时候选择留在团里当部长,继续配合着工作。 小黄和聪姐,我们是经典的《我们仨》组合。当有人说”不相信男女之间有纯洁的友谊“时,我们三个人一块站出来,抛出另外两人的名字以作反驳。我们一起约了很多顿饭,一起配合着完成了很多工作,一起在去轰趴,一起喝过好几次酒。我仍然记得那年冬天雪后,我们一起去石老人海滩,在地铁站对着玻璃比划着身高,一起在积雪未消的沙滩上散步,那天的云粉粉的、蓝蓝的,那么洁白。 yn,是我喜欢过的女生。在大一的末尾,我和她曾是那么默契。我们在图书馆、在食堂那么碰巧的遇到。我曾经说过,她是那么像我曾经喜欢的那个女生。给yn看过她的照片,她也惊叹彼此的相像。但当最终被她察觉到我的心意的时候,她也像她一样,拒绝了我。理由是:“我觉得你喜欢我是因为我像她,而不是真的喜欢我。”其实啊,我自己又何曾了解过她呢?她就像我心中的一抹魅影,是我心目中最美好的伴侣的投射。我喜欢她只是因为她符合这个投射,因而她也就代表了这一抹投射。而我呢,喜欢的大概真的是你。不过呢,被拒绝了也算是毫不意外,**自己最想要的永远也得不到,这件事在过去、在未来,在我身上已经也还将无数次的发生**。不过幸而这件事没有影响我们之间的关系,我们最后还是成为了朋友,如此自然,犹如当初。后来,我们还成了搭档,而且意外的还挺默契的呢。 万妈妈,虽然和我们同级,但是她如此成熟可靠,像是靠谱的“大人”。csj呢,虽然也曾是新媒体中心的伙伴,但是她果然还是更适合声乐部。当她站在舞台中央的那一刻,整个人的风貌与气质都得到了升华。有些人就是为了站到舞台中央而生的。hyj,中途加入的超级E人,帅气又有趣,有他在饭桌上从来不会无聊,舞蹈部的“青大黄子韬”,我的同乡,也是同专业的同学,是让我爹感叹“很少有跟我儿子那么帅的人”,我知道我爹之美我者,私我也,我自己其实很羡慕你的外貌、身材、性格、智商……你只是缺了一点运气罢了,总有一天会混的比我好的。以及我们的童哥,作为常务副团长,真的是台前幕后样样精通,唱过歌、跳过舞、写过稿、拍过照、剪过片、画过图……从一个外行人到十项全能,童哥是真正的热爱并努力。 当然,还有很多人,此刻不能一一列出来回忆了,真的很感谢大家给我留下如此美好的记忆。 我还记得在大一的时候,毕业歌会结束后,和毕业的学长一起去吃饭,我和大艺团的伙伴们纵情畅饮,找自己最喜欢的伙伴们一次又一次的干杯,仿佛怎么也喝不醉。 到了大二的时候,又是毕业结束后,这次一别,可能大多数人都不会有太多的交集了。大家开始忙于考研、忙于自己的事情,只有少数人会去竞选团长的职位。而我,忙于各种各样的事务,在那个时候居然感到松了一口气。那天晚上,大家依旧推杯换盏。有了更深的羁绊的大家,想着即将到来的别离,只有将杯中酒一杯杯的饮尽,一次又一次热烈的拥抱,伤感的流泪。那时候我面对这个场景,没有落下一滴泪。只有肩上的担子卸下时的放松。 我真傻,真的。如果那时我知道那是我最后一次参加毕业歌会,我一定再喝一打酒,痛哭流涕着和每一个小伙伴都热烈的拥抱一番。可惜,我那时什么也不知道。 如你所见,我没有参加自己的毕业歌会。那一天,我在外面实习,没有办法赶回学校。就这样,错过了。我这一生,很少会为自己的决定感到后悔,但是想到这件事,只能感叹天命无常,痛苦不已。 “大艺团,牛逼!” 我多想再和大家一块喊出这句口号。但是啊……渠会永无期。 ## Ⅲ 我的舍友们 在软工系,最不缺的就是有个性的人。但是即使是在这群有个性的人当中,我也是最有个性、最乖张的那位。 我很感谢我的舍友,他们让我在宿舍里过的很不错。那些在网上见过的对舍友的吐槽没有出现在我身上。 我记得大一刚到宿舍的时候,最开始是与mc君相遇。当时RE:0正在连载,我发现我们都在追番。于是,我们成为了好朋友——短暂的好朋友。虽然有相同的兴趣,而且我也相当喜欢他,但是我们却无法相处的很融洽。 归根结底,是因为他是一个阳光、外向、开朗、兴趣广泛又正常的人。与我这种阴暗逼合不来大概也正常。但他却与宿舍另外两个人玩的很好,一个是cl君,他也与我大不相同,开朗、温柔、外向又正常,喜欢健身和运动,身上充满肌肉,大概是大部分女生都会喜欢的类型。他们两个经常约着一起健身。说实话他们两个实在是很适合做朋友。另一个人是我们的班长,说实话,我讨厌他,因为他太阳光、太开朗、太正确、太正常,简直是我这种人的反面。所以,我讨厌他。 我是一个怪胎,所以跟怪胎更合得来一些。比如宿舍里面有一个老哥,几乎不学习呢,整天要么在宿舍里躺着,要么就是在玩游戏。但是我觉得他这样挺好的,这才是大学生该有的样子。与他相近的是朋哥,他虽然也天天不是躺在宿舍就是在和我们打游戏。但是他却极其聪明,每次考试前稍加用功便能学会一学期的知识,在考试时取得高分。而且游戏还打的非常好。真是令人羡慕啊。此外就是呆哥,呆哥确实是呆呆的,而且总是在省吃俭用。但是他却也是一个温柔善良的人,虽然是一个怪胎,但是却又更像是一个正常人。 当然,宿舍更是有几个重量级的怪胎。 一位是学霸哥,人如其名,是个学霸,宿舍里的成绩担当。是唯一一个学识不在我之下的人。但是我们还是有些区别的,我只在自己认定的事情上努力,剩下的时间全部都在玩了。而他呢,大部分时间都在努力。我其实是真心的佩服他。但是我又是如此好胜,所以我们有时会莫名其妙的针锋相对,谁也不服谁。不过幸好最后我们没有选择同一条跑道,我选择了就业,他选择了读研。不再相互较劲之后,我们的关系好了很多很多。幸好在前进的路上没有他,不知道他是不是也为前进的路上没有我而长舒了一口气呢? 一位是仙不觉,或者叫秦无衣,但是他本人其实姓杨。这就已经够奇怪了吧?他是一个相当聪明而且富有见识的人,本来呢也是能成为一个正确的人的。但他偏偏没有那么做。他貌似是觉得保持正确太费劲了。于是他每天都和我们一起做着错误的事情。我们一起打游戏一起追番,一起闲的没事出去吃点好的。居然让他跟着我们学坏了呢。他每天都会说一些正确的事情,但是从来做不到。虽然每天不努力,但是凭借自己的聪明智商与神级运气,居然每次都能卡在及格线上,从来没挂科。他本人其实是那么高情商,大方,能体谅人而富有原则,却甘愿与我们做朋友一起当个怪胎,真够奇怪的呀。但是,能和他成为朋友,真不错呢。 另一位是siri哥。这个哥们,是第一个让我觉得不爽的人。刚到宿舍就开始四处找茬,这人真是一个倔脾气,谁劝也不听,只爱找茬,在宿舍里操着一口即墨话跟朋友大声的开黑。什么人啊这是。但是呢,随着日子一天天过去,我却发现,这个人在和大家开黑的时候喜欢主动承担所有人都不想玩的辅助位,会在不管是谁向他寻求帮助的时候尽力帮忙,会以一种及其拧巴的方式听取别人的建议,以及以和正常人不同的方式,思考和批判自己的人生。随着和他进一步相处,感觉他的脾气也没那么糟糕,再往后呢,我才发觉:什么嘛,这个家伙,原来和我一样,都是一个怪胎嘛。就是这个惹人讨厌的家伙,大概就是我的舍友和同学中,和我关系最要好的一位。 上面这两位,是入选我论文致谢内容的角色。 后来呢,我们专业分流,换了校区,也换了宿舍。 我和siri哥、呆哥以及仙不觉选了相同的专业,故而分在了同一个宿舍。这个新的宿舍还有六个人,分别来自三个不同的班级。真是奇怪啊。但是由于不是同一个专业,加上我的大二大三大都泡在实验室,大三大四大都在外实习。因此我对宿舍的大家也不算太熟悉。只知道其中有一个是整天说着粗鄙之语的粗鄙之人,一位退役的武警,一个帅气的令人羡慕的但是却及其擅长模仿老八的声音的家伙,还有一个努力、上进、阳光、外向又温柔的人,虽然听起来这人也是一个正常人,但我总觉得他也和我一样,是一个思考方式与正常人不同的怪胎。 还有另外两个人,我是十分确定他们就是怪胎的。 一个是振桑,振桑喜欢游戏、喜欢动漫、喜欢特摄。他喜欢这些不是因为这些东西受人欢迎也不是因为这是一种社交货币,他喜欢这些是因为他真的喜欢。和我一样。他不是一个温柔的人,而且对感情很笨拙,一副应付不了爱情和亲情的样子。但是啊,他是一个真诚的人,是一个赤诚的人。他有自己纯洁的理想,虽然他笨拙,虽然他不怎么努力,虽然他自己说自己又懒又犟。但是我知道,他有一颗那么纯洁那么真诚的心。上苍总有一天会对这样的人降下馈赠。而且,就算上苍不馈赠他,总有一天,他也能自己创造出给自己的馈赠吧。 另一个是康子,康子是一条狗,他自以为自己是一条狗,而其他人也认为他是一条狗。但是他其实是一个人。可能他知道狗山上灵活,是狗之乐也。我非康,故不知康知不知狗之乐,但是当一只狗,大概是挺快乐的。因此他选择了快乐的生活方式。所谓不知康子之梦狗与,狗之梦庄子与?康子与狗,此必有分也。此之谓物化。而这样的狗人,自然是一个不折不扣的怪胎,每天从狗嘴里面吐出莫名其妙的狗叫,但是这些狗叫传到我的耳朵里时,我却觉得蛮有道理的。我们能相互理解,大概是因为我们也是同一类人吧! siri和康,大概就是我的舍友中最奇怪的二人,也是我最好的朋友。现在总是盼望着能够回到青岛,如果是冬天,就一块煮一顿火锅;如果是夏天,就一起吃一顿烧烤。真是的,让人又多了一个怀念青岛的理由啊。 ## Ⅳ 追梦还是一起开心? 对于一个有理想的CS人,参加算法比赛几乎是一个绕不过去的话题。我还记得我第一次知道数据结构与算法还是从学霸哥哪里。有一次学霸哥出去自习完完回到宿舍,打了一天游戏的我阴阳怪气的问他这一天是去卷了高数还是C语言,还是……(巴拉巴拉列了一堆编程语言) 但是学霸哥却一脸不屑的告诉我:说了那么多其实都是一种东西,自己是去学了数据结构和算法,这是所有语言通用的。 极具虚荣心的我不甘落后,随后也去刷完了[B站王卓老师的数据结构网课](https://www.bilibili.com/video/BV1nJ411V7bd/),随后又去买了一本北大出版社的数据结构看完了,最后又去买了一本邮电出版社的《ALgorithm》,仔细的看了一遍。 当然,光说不练假把式,在看网课看书的同时也需要去刷题练手。恰好在大一的时候学校组织了一次PTA钻石级考试。虽然叫钻石级,但是钻石实际上是非常低的等级。那个时候还没有开始学习数据结构,靠自己仅有的努力考了七十多分,但是还是比学霸哥低一点。但最令我感到惊讶的是,我两个小时没有做完的题目,旁边的那个同学居然花了十几分钟就交卷了,而且考了满分。 后来我才知道,这是大名鼎鼎的wz哥,是转专业留级的同学。虽然wz哥有着很强的实力,但是他的大名鼎鼎可有很大一部分不是好名声,而这不是好名声的部分却又不是来自与wz哥。个中缘由,我也是后来才从要好的学长那里听说。 后来,就开始自己在PAT上刷题,刷完乙级刷甲级,连做带抄,总算刷完了甲级。但是甲级题对我来说,其实已经有点捉襟见肘了。毕竟自己没有受到来者的辅导,全靠自己摸索,既没有科学的方法,又没有领路的明灯(哪怕我早点发现[oi-wiki](https://github.com/OI-wiki/OI-wiki)这个仓库)。更不要提做算法题这件事和我最不擅长的算法题有多么相似了。 我做题,每次不是因为过度优化、想的解决方案太复杂而写不出来,就是因为考虑不全面而<font color=green>WA</font>,很少有能够一发<font color=red>AC</font>的时候。对我而言,做算法题常常是一种折磨。但是如同之前的很多次那样,虽然折磨,虽然不擅长,但是我一直在坚持。 后来,也跟着学校的zzm老师,加入了一个叫做软件梦工厂的群。这是一个学校里专注于算法比赛的群,里面有好几百号人,其中有很多都是往届和我目前的学长,看起来充满底蕴。里面不乏算法与工程领域的大佬。 但是这个群却死气沉沉,每次群里发点什么东西,都有一群人冷嘲热讽,阴阳怪气,语气中充满着火药味,言语中满是虚伪、谎言、算计、拉踩。在这里我仿佛看到了比我更恶毒更低俗的存在,我承认,当时我确实为这种氛围感到恶心。 本来就不算擅长算法的我,由是拒绝了软件梦工厂的一切集训,断绝了除算法比赛外一切与这个群的联系。不过在刚开始懵懂的时候,我也曾在群里问过学长们两个问题。其中有一个问题得到了一个学长热情的解答。这让曾经一无所知的我深受感动。 后来,当我得知zzm老师是如何如何的以权谋私、不劳而获、恫吓同学、鱼肉队员、排挤同僚,我深深的为自己的决定感到正确。如果当初真的一门心思搞竞赛,我一定会过一个不快乐的大学。 后来我还听说,我们学校原本除了软件梦工厂,还有一个叫一起开心的算法团队,是我们学院的周院长领导。那个学长就曾经是一起开心的成员。因为周院长采取放养的态度,大家都是老人带新人,真正的贯彻了"一起开心"这个主题。但是很可惜,当我了解算法比赛,准备进入算法团队的时候,一起开心就已经被软件梦工厂吞并了。鉴于zzm老师如此让人恶心,所以才有那么多人在群里与她暗戳戳的针锋相对。 我没去过一起开心,但是我的朋友中有一起开心的队员,却没有梦工厂的队员。因此,我主观的相信,一起开心一定是一个能够让大家一起开心的地方。 但是啊,已经没有一起开心了。 如果不能一起开心的话,那么也就没有必要一个人不开心的造梦了。于是,我的算法生涯就这样还没有开始就结束了。 ## Ⅴ 未来,309实验室 放弃了加入算法集训集训队,那么就得找点别的事情做。我那时是这么想的,因为我太想进步了。后来听说有一个叫未来研究院的地方正在招人,而且这个地方离我的宿舍很近,于是我就联系了赵叔(当时还得叫赵学长),想要加入研究院。 ![](../../../../assets/default.png) 我还记得刚见到赵叔的时候,是在21年11月月末,他穿了一件黄色的外套,手上端着一杯瑞幸,让人一看就感觉是个大厂程序员的感觉。后来他领着我在研究院逛了逛,领着我去了308实验室(刚开始的时候我们在308,后来到了309实验室,并且在那里一呆好久),这是我们办公的地方。随后他问了我几个问题,在发现我有前端基础之后决定让我跟着另一位学长搞前端。 这位学长就是ALGarth了。我记得那天下午第一次与ALGarth见面的时候,他正吃着一份看起来很不错的外卖。在跟我介绍目前正在做的项目之前,他先跟我介绍了实验室点外卖的地址,介绍了自己放在实验室的充气床,以及刚买的香烟平替——一大袋子阿尔卑斯棒棒糖。并且随手递给我一根,表示自己找个打火机点上(?)。 就这样,我的实验室生活开始了。在这里,我认识了赵叔、ALGarth、互为、银龙、大大大墨水、厨酱、金酱、光源等等很多和我一样不太正常的人,他们都成为让我受益一生的朋友。 在我刚来研究院的时候,在做的是一个面瘫线上诊疗的横向项目。这个项目说实话,写的很烂,但是让我很受益。这是一个使用Uni-APP开发的项目,通过包装一个webkit浏览器,让我们得以用web技术栈开发手机APP。在algarth(区分大小写好麻烦,后面都是小写了)的帮助下,我学会了使用git,对于前端的工程化初窥门径,以及最终于的,让我了解了前端的技术栈以及坚定了自己成为一个前端开发者的愿望。 后来赵叔和algarth给我们几个新人报名了一个学校JAVA社组织的比赛。这时是我和互为、银龙第一次合作开发。我们都没有经验,开发的及其艰难,天天在熬夜攻关。最终,因为我们的经验不足,只做出了一个半成品,与名次失之交臂。 但是赵叔和algarth的小组却拿了二等奖,几个学弟组成的小组拿了三等奖(赵叔给他们找了个开源项目,让他们包装包装,拿奖全靠口才)。对于这样的结果,我这种阴暗又好胜的人自然是及其不爽。本来已经阴沉着脸回宿舍了,但是ALgarth却给我发消息,说周末一起出去拿奖金吃个饭,剩下的钱就大家一起分了。我极力推辞,说自己没有拿到名次,哪里好意思分奖金呢?赵叔却回复到:“说什么呢,你们三个人这几天的努力,大家都看在眼里。这次就是为了试试你们的实力,后面还有大钱要赚呢!” 后来啊,做了面瘫诊疗与好停车这两个还算成功的项目,以及一个目前来看确实失败的项目,自己确确实实赚到了一笔钱。正是靠着这一笔钱,让我顺利开启了自己的实习之旅。 虽然当时将做项目和赚钱看的很重。但是如今回忆起来,才发现这在我的记忆中占比是多么的轻,寥寥数笔便写完了。原来这段记忆中,最重要的,还是和大家留下的美好回忆。 我记得我们一起在309实验室里面当作会议室的小屋里吃过好几次饭,有几次是外卖,有几次是火锅,还有一次,是本来准备去实验室的天台烧烤,结果因为买到了劣质的碳,点不着,最终烧烤变成了用平底锅炒肉。这一次让人印象很深刻,因为那时疫情正严重,学校封闭。我们不能出去吃饭,只好去美团买菜,让骑手买完之后,把食材绑到我们从楼上扔出去的绳子上,我们再将东西拽上来,因为过程艰难,所以极其难忘。虽然每次在那里吃饭都有些不同,但是每次我们都会放上一集让子弹飞,喊着已经滚瓜烂熟的台词,一起“吃着火锅唱着歌”。电影放完,饭也吃完了。只是里面这个屋子不通风,饭菜的香味经常许多天无法散去。 以及在这个实验室,我产出了两个还算有趣的开源项目。一个是输入信息一键生成电子请假条的小程序,在能够用电子假条的时候我们用这个小程序出入自如。另一个是输入信息一键生成核算预约码的项目。靠这个网站我们不用去抢核算预约时段的名额,在三天两头做核酸的时代做到了想什么时候去做核酸就什么时候去做核酸。 但是要说最有意思的还得是那个项目!那一次因为疫情封校太无聊,我们决定整个活。于是我们用一些嘉然、溜溜梅、美羊羊啥的抽象图,下面配一个二维码,打印了六七张,贴到学校的各个地方。其中有一张贴到了餐厅门口的显眼处,作为第一张。扫描这一张图片上的二维码,将会得到一个数字和一个提示,暗示下一张图片贴在哪里。就这样一张一张找下去,最后将会找到贴在未来研究院院子门前的一张图片。扫描之后会得到最后一个数字,以及一个输入框,将之前得到的数字输入经过计算后,会得到一串特别臭的数字:“114514”。这就是最终的答案,届时会告诉得到答案的人未来研究院309实验室的地址,凭最终答案可以得到我们提供的奖品:两颗溜溜梅以及一颗印有IFF(institute for future)的钥匙扣。 除此之外,我们还一起做了很多很多有趣的事情,一起去餐厅吃饭,深渊二楼的大阪烧是我们的最爱;一起打原神的深渊,比比谁先满星;被带入坑明日方舟,天天缠着Algarth给我挂助战;一起用实验室72寸的大屏幕玩“开火车”;一起看ASOUL直播和原神前瞻……原来,我有一段时间觉得在学校的时光那么开心是因为有你们啊。 当然,让我们能够拥有空间和时间去做这些事情的人,刘老师,当然值得特别的感谢。作为研究院的院长,刘老师是一个温柔又负责,知识渊博又高瞻远瞩的老师。非常感谢刘老师对我们的信任,才让我留下一段如此充实又难忘的时光。希望刘老师能够继续坚持这样,给后面的学生带来美好的回忆。 但是,最终还是要离开啊。在论文答辩的时候,我问互为:这里还是能称为家的地方吗?“互为回答我说:”不能了吧“。我其实早就知道答案如是,只是不愿承认罢了。物是,人非。曾经在实验室的那些小伙伴们,我们的last Dance已经结束了。接下来,是什么时候再见呢? ## Ⅵ ALgarth,我的挚友 algarth,就是上文提到的那个,是我的学长、良师也是我的挚友。为什么说他是我的良师呢?因为他给我解释过好多有关js的问题,其中他给我解释事件循环(event loop)时的情形,我现在还记得。此外,他也给了我许多的学习资料,以及给我推荐了许多好用的工具,这些也让我相当受益。同时,他也教给我很多人生的道理,虽然他这个人活得也就没什么道理的样子……以及前文提到的,在梦工厂的群里回答我问题的学长,也是他。 我还记得当赵叔把algarth的微信推给我的时候,我看到他的头像时候的惊讶。因为这个头像和之前帮助过我的xxx学长的QQ头像是一样的。随后他发消息进行自我介绍:“你好,我是xxx。“ 就这样,无巧不成书,缘分使我们两个本该再无联系的人相遇了。 我还记得第一次见到algarth的时候,他外面穿着那身经典灰绿色冲锋衣,里面是经典米色法兰绒衬衫。那个时候他对我说:”哎呀,学弟,没想到这样还能续上啊!“ 刚见到他的时候,我还以为他是一个靠谱的学长。因为他用很严肃的神情,配合着不协调的滑稽语气给我讲项目背景,讲git使用,讲技术栈选型,讲实验室的一些大大小小的门道。虽然他说的话有些乐子人特有的夸张、扭曲与抽象,但是大体上是正确而精准的。这些知识让我受益匪浅,我当时对他还是心生佩服的。 但是随着相处日久,我渐渐发现,这个人就是一个纯纯的啥呗二次元、互联网乐子人、抽象怪、ACG爱好者、死宅……会因为压力大又不想抽烟而买棒棒糖抽;会追一些只有到了一定浓度才会看的新番;会去知乎刷各种奇奇怪怪的问题;会去搞一些神奇小软件让自己打明日方舟更加顺利;会闲的没事的时候托着一个手柄搓尼尔·机械纪元……总之就是一个和世界格格不入的怪胎。简直是和我一摸一样,不,是比我更是一个怪胎。至少我都不会天天看ASOUL的直播。 正因如此,我和他的关系更好了。于是,在写代码之余,我们开始交流哪些番剧值得补,哪些游戏值得玩,哪些漫画值得看,哪些外设值得买……他拉着我看嘉然的直播,虽然直到现在我也还是觉得V圈没啥意思。他慷慨的把手柄借给我让我去玩NFS,帮我改进我的键盘,让它听起来声音更好听,帮我找入手G502的最低价格,一起挑战Ghost runner的结局,一块研究原神的配队和圣遗物的词条,一块打水月肉鸽,一块研究哪家外卖、哪个窗口便宜又好吃……后来他还送了我一块泥岩的音律通行证。我也曾买给他一块通行证盲盒,结果他开出来一块斥罪。最后我们一人送了对方一个不屈者的通行证呢。 algarth是那么热心,以至于每次考试我都会找他问问往年的题型,每次要买电子产品我都找他帮忙看看参数,每次遇到学校的事件都找他问问往年的情况……如同形成了路径依赖。 后来,我在外实习,他也毕业了。但是我依然在依赖着他,感谢他陪我打apex,跟我在结束游戏后一起聊一会天,陪我度过独自在外的无聊时光。 再后来,我们又一次相见,是他到北京来找我。那一次我们一块逛了漫展,逛了谷子店,看了一场演出,吃了几家餐馆。我们聊了很多话。他说:”互联网上是无法发展感情的,大家都在互相攻击。人与人的羁绊只能在线下建立。在实验室那两年,就像在玩P5,把大伙的高感度都刷满了啊!“ 等他离开的时候,我竟然有些伤感,为我这次手头拮据未能尽地主之谊,也为此次一别不知何时再见。但是我相信,只要怀着想要相见的心情,我们再见面应该也不难吧? ## Ⅶ 一段正常的恋爱 在大三的时候,我也开始了一场恋爱。持续了大概一年多。如果从外人的角度来看,这场恋爱应该是失败的。大概就是一个普通的男大和一个普通的女大谈了一场普普通通的恋爱最后因为各种磕磕绊绊而最终分手的故事。没错,只有在谈到恋爱的时候,我才会承认自己是一个普通人,是一个正常人。 但是就我自己的感觉来说,这只是一场结束了的恋爱,而不是一场失败了的恋爱。至于为何我这么讲呢?且听我下面分析。 这场恋爱是普通的开始,普通的发展,普通的高潮,普通的结束,可能与他人的恋爱别无二致,因此就没有必要详细的讲述我的恋爱故事了。 我只想谈谈我的感觉。这次恋爱,自然是有喜有悲。当得到了出乎意料的小惊喜,当携手走在海边,当遇到挫折时受到对方好心的安慰,那自然是其喜洋洋者也。当受到蛮横无理的指责、当受到莫名其妙的冷落,当看到自己仅有的几件因为没钱添置穿了好几年的衣服时,那自然是感极而悲者也。有喜有悲,人生才会有趣,这是我觉得这场恋爱成功的第一个点。 此外,恋爱也是一次很好的体验,如果不是一起逛过那么多次小吃街,我不会知道台东的松河路一号、金丝牛肉饼,李村的花溪牛肉粉、茶油臭豆腐、车轮饼、鸡蛋汉堡、熏肉大饼、拌鸡架以及鲍师傅的肉松小贝的美味,如果不是一起喝过那么多次奶茶,我就不会知道喜茶的多肉葡萄,茶百道的生椰西瓜,一点点的冰激凌红茶、茶话弄的桂花引是我喜欢的滋味,如果不是逛过这么多次街,我不会喜欢逛KKV、名创这样的精品店,如果不是出去吃过那么多顿饭,我不会知道祥子家和厨大匠这两家好吃的小店……或许这些都是一些消费的诱惑,但是能够知道如何取悦自己也是重要的一课。能够了解更多符合自己喜好的东西,这是我认为这场恋爱成功的第二点。 以及啊,通过一次恋爱,让自己知道如何正确处理和亲密之人的关系,如何让亲密之人开心,又如何让自己开心。自己喜欢什么样的人,又适合什么样的人……如何和喜欢的人相处,又如何和自己相处。这一次之后,大概对自己也有了更加深入的了解吧。 但是一场恋爱,何必总结出个一二三四的功过得失? 我们分手的时候,是一个工作日的早上。我知道这一消息之后,如常去公司工作。过了一个小时之后,我觉得自己必须做些什么,才能章明自己已经是孤身寡人了这回事。于是我从公司后门出去,面对浩浩汤汤的汤逊湖,我摘下手上的戒指,说着希望湖女祝福她,随后将戒指扔入水中,可惜只激起了一点点的浪花。有诗道: ***誓言银戒指,一点落水声*** 随后我就回去继续写代码了,一如既往。 大学谈一场回忆起来没有无尽的懊悔,没有滔天的怨恨,分手时平平淡淡的恋爱,不就已经算成功的了吗?难道还要奢求什么吗? ## Ⅷ 大学的结束从实习开始 因为没有要考研的计划,所以我在大三下学期一开学就开始准备找实习,为后面正式工作打基础了。 现在回想起来,自己的大学生活,似乎不是一个瞬间猝死,而是缓慢的凋亡。这个凋亡的过程,从我找到去实习的时候开始。 在23年的二三月份,新年伊始,疫情结束。大家带着刚刚从阳性恢复过来的躯体投入正常的工作和学习。我也终于可以,也必须找实习了。因为这是最好,也是最后的机会。我本以为凭借自己这些年在课上积累的知识,在算法网站刷的题目,在实验室积累的经验,能够让轻松找到一份合适的实习。但是疫情之后春天却是一片凛冬。 *winter is coming.* 在那个春天,我每天都在优化简历,看机会,投简历,准备项目,背八股文,复盘面试,刷leetcode,看面试题……每天都是匆匆忙忙,慌慌张张,心里七上八下不知所措,对自己对未来充满失望。而同时,和前女友之间的裂隙越来越大,我承受的经济压力也越来也大。那一段日子真是难熬啊。 但是啊,在4.18号,我在OneNote上写下了这么一句话: *今天闻到了许久不曾闻到过得花香。* 那是在从研究院到食堂的路上,一株西府海棠开得正盛。花朵如此洁白,闪着粉色的红晕,如此的健硕,如此的娇嫩,如此的完整,如此的鲜艳。我仔细观赏了这一片美丽的脸庞,心情仿佛好了一些。我这才意识到自己是多么迟钝,是怎么样的做了一件蠢事,原来”春色如此“! ![](../../../../assets/default.png) 那个时候,还有另外一件让我开心的事情,有一个我还算满意的公司让我进入了第二轮面试。不久之后,我就收到了他们的口头offer。好事居然真的发生了!我凭借自己这些年在课上积累的知识,在算法网站刷的题目,在实验室积累的经验,在这春日的凛冬中也如同这株西府海棠一般盛开了! 五月十四号,我兴冲冲从学校请假,准备出发前往实习。随后的一年里,我辗转各地,实习、旅行、在家休息,仿佛自己已经不属于学校了。我与学校的联系,我与学生这个身份的联系,正在逐渐的衰弱、枯萎、凋零。那时候我还不知道,此刻到我毕业,我还能在学校里正儿八经的呆着的日子,还有50天。 ## Ⅸ 在校倒计时 去年五月,我从没想过,自己这一年能呆在学校的日子还有50天,更没有想过,当有一天从这里离开的时候,会觉得有一丝丝遗憾,有一丝丝不舍,有一丝丝怀念。那个时候我一心只想赶紧毕业,摆脱学校的束缚,开始工作生活。 所以当我实习结束的时候,我先回家呆了一个多月。这一个多月里,我把找实习的时候走过的心路又走了一遍。从踌躇满志,到心灰意冷,再到豁然开朗,柳暗花明。这个时候真的很感谢我的妈妈一直陪在我的身边,每天照顾我的饮食起居,跟重要的是,每天都顺着我的心情,不断给我打气加油。也幸好自己一直坚持,不曾放弃。 拿到offer之后,我给自己买了一台相机,随后,在家里和亲人们过了一个快乐的中秋节和国庆节。随后,回到了学校。随后,我给自己安排了一场旅行,一个人去了已经想去了四年的南京。这一次旅行是我玩的最开心的一次。随后,为了赚够来年三月到北京实习的房租和生活费,我又开始去刘老师的公司实习。在那里和互为与银龙一起度过了一小段快乐但又辛苦的时光。从我五月出去实习到从刘老师的公司辞职,我呆在学校的时间只有六月回学校考试、过端午节,以及十一结束之后。这一共只有9天的时间。 随后,12月来了。这一个月,是我四年来在学校里最开心的一个月。12月的学校,是很舒服的,因为夏天没有空调,而冬天的暖气却很足。这个月,因为没有课程安排,没有考试,所以我是如此的自由。每天睡到八九点自然醒,然后去食堂美美吃过早饭,然后开始写[奉纸填词](https://github.com/charlesix59/fill_poem)这个开源项目。这个项目完全按照我自己的想法去进行,我自己进行技术选型,自己去做产品设计,自己做UI设计,自己做方案设计,自己开发前端,自己写了一个简单的后端,自己去做数据爬虫和数据处理……没有预期目标,没有DDL,但是我做的那么开心。每天看着自己写出的一行行代码忍俊不禁。 代码经常就是一写就到中午,随后草草吃个午饭,然后下午写累了就出去散散步。我从学校为中心,沿着青岛扭曲嶙峋的路网向四面八方探索,我向北上山,向南看海,向西进入繁华的闹市,向东进入安静的富人区。我总是娴静的漫步,一路上或者想想项目的设计,或者构思自己想写的小说的剧情,神游八方,意趣盎然。 这其中我最常走的路线,还是香港东路、麦岛路、东海东路、海游路这一圈。因为可以走到小麦岛和海之恋公园,海岸线的风景非常美。虽然小麦岛这一年都在封路修地铁,但是我还是忍不住一次又一次的去到小麦岛,感受那悬崖边上的海风。 到了下午,自然是要吃点好的。这一餐,一般都是经过我的精心构思的,要知道自己想吃什么,需要经过深思熟虑!但是晚上这一顿,我偏向去吃炸串卷饼或者麻辣烫,也很爱披萨、炸鸡、方便面炒鸡与塔斯汀与KFC这种昂贵却又便宜的快餐。但是最开心的还是要说那几次! 有一次和siri哥去台东,金丝牛肉饼溅出的热油洒了他一身,另一次和siri哥去李村,我推荐的低价美味收到了他的大力好评,还有冬至的时候和仙不觉去吃好吃的水饺,还有那天为了让振桑和不觉见识到撒了siri哥一身的牛肉饼,我特地跑了一趟台东给他们买了回来。以及我想一次又一次重复的,我们四个人的自助小火锅。提到美食,总是有满满的回忆啊。 至于晚上,那自然是宿舍一起开黑,用变形的操作和妙语连珠的狡辩引起一阵阵笑声的美好时刻。呆哥每每被我们逗笑,都会评价道:”今天太开心了!“ 是啊,今天太开心了。那一个月,每一天,都太开心了。 到了1月,我回到家里,处理了一些事情,做了个小手术,生了一场病。又是麻烦妈妈在一旁悉心的照顾,只要有她在,我要考虑的事情总是很少很少。随后,和家人们过了一个开心的大年。到了正月十八回学校的时候,我不知道我呆在学校的日子还剩下最后十天。 其中有三天是在去3月1号去实习之前,那段日子在忙于处理各种学校的事情,准备外出实习,时间被肆意的浪费了。当我再回到这座城,这个学校的时候,已经是毕业答辩的时候了。 答辩前一天我才回到学校,处理各种各样的事务,修改论文,打印论文,准备稿子,完善ppt,收拾宿舍的铺盖,和宿舍和实验室的朋友打了个招呼……第二天是论文答辩,我被分到了下午场,于是中午的时候,我背着电脑,来到了海之恋park,坐到面朝大海的长椅上。那天是个晴天,但是海雾弥漫,遮天蔽日。坐在海边,如漫步雨中,我就这样打开电脑,修改和记诵稿子。时不时看看面前的碧海,自己的紧张和疑虑就这么被消除了。 当晚上得知自己顺利通过了答辩时,又免不了和舍友一起去吃饭庆祝。第二天又与实验室的大家吃饭唱K,那一晚我罕见的亢奋,久违的喝了酒,而且又是那种怎么也喝不醉的感觉,因为我知道,在他们面前,不需要隐藏自己的内心,不需要故作矜持。我跟银龙碰杯,跟光源碰杯,跟algarth碰杯,跟厨酱碰杯,跟大大大墨水碰杯,跟赵叔碰杯……晚上我们一起唱着伍佰的last dance,为我们这一群即将各奔东西的朋友们奏响骊歌。 而这几天过去,回到北京之后,我的大学余额就仅剩四天了。再回来,就已经是毕业前夕了。这几天,我急匆匆的从一个地方赶到另一个地方,以为用眼睛扫描过,用脚步丈量过,就可以将记忆中一切打包带走。但是后来我知道,我错了。记忆中的一切,我带不走,却又逃不开。那天晚上,我一个人走上小麦岛,望着漆黑的天空中挂着一轮明月,无尽的黑暗的深渊上闪烁着明亮的波光,海风和涛声将我包围,我喝光了那一罐可乐,忽然想明白了。 那一天,我写下了这样的一篇散文: ![](../../../../assets/default.png) 随后,在毕业的那天,我和siri哥、康桑、振桑以及学霸哥一起吃了一顿晚饭,看着他们,留在那座充满海雾的城市,在浮山后租了一个宽敞的房子住在了一起,我既羡慕,又欣慰。在那之后,我要去北京,学霸哥要去杭州。我和我的舍友们,也离分了。我与这座学校,也离分了。我与这座城市,也离分了。 但是啊,你记住,你们记住,有些联系不会那么容易切断。我会顺着这份羁绊找到你们,一次又一次。 ## Ⅹ 我的大学 这篇文章,从突发奇想开始构思到写完,历时五天,凡一万四千余字。虽然文中有些地方我记不清楚,只取大意。但总体上,我是诚实的。我诚实的回顾了我的大学生活,遍历着这四年的的人生轨迹。 虽然我的大学没教给我多少知识,也没让我有多少收获。但是我还是要感谢它,如果不是选择了这个学校,我也不会遇到那么多有趣的人,度过这四年有趣的时间。 在大学时,我曾无数次憧憬着毕业之后的日子。但是当他真正到来的时候,有那么一瞬间,我居然想要逃避,我居然感到不舍,我居然想要想要抓住时间的衣角,让他慢下来,等我一下。 但是就像我说的,“明日亦有明日未竟之美。”正是这份对未来的憧憬,一直支持着我一步步走到现在,也将继续支持我一步步走向未来。所以,虽然不舍,我也不会停下脚步,我会继续往前,不断往前。 *進め,進め!* 只有这样,我才能拥有与过去重逢的勇气,只有这样,我才能再回望过去的时候感慨:“看吧,虽然受了那么多苦,但是我的选择果然是值得的。”

社会观察——性别对立

# 社会观察 第一篇 男女对立 现在男女对立的现象已经不仅存在于网络之中,更顺着网线蔓延到了现实世界…… ## 经济,此间万物的根本 我们回顾世界各地的女权运动,可以发现,**女性获取政治地位通常发生在女性获得工作的权利之后**。女性广泛的获得工作条件从家庭主妇的身份中脱离出来是在第一次工业革命之后。当社会生产的中心从农村、农业转换到城镇、工业之后,越来越多的人从农村搬迁到城市,进入工厂工作。 当人们的工作性质从繁重的农业劳动变为枯燥的流水线之后,男性与女性在体力上的差距逐渐消弭。越来越多的工作,女性与男性的工作效率相差无几,甚至高于男性。随着城镇化的深入,更多的第三产业的发展,越来越多的女性赚到了更多的钱,在经济上,他们不必依靠男性,可以维持自己的生活。 至此,女性开始尝试从传统的地位中脱离出来,但是她们却四处碰壁。由于一些传统的价值观,女性在政治和经济上受到诸多的限制。于是,**女性开始追求自己正当的权利,包括:选举与被选举权,工作权,同工同酬,生育与性的权利。** 我们必须说,这些权利是十分正当的,它们需要,而且必须,得到或逐渐得到实现。我们观察世界各地世俗国家,不难发现上述的权利大都逐渐的都实现。 以选举权为例,它们实现的时间集中在20世纪20年代左右。一次世界大战结束后,大量的男性死亡,而经济则继续保持发展。于是更多的工作机会对女性敞开,得到更多话语权的女性,追求自己的政治权利是理所应当的。 而发展中国家的男女平等呢,则在20世纪50年代左右兴起。因为大量的国家脱离殖民统治,获取独立。大量的民主或共产政府为他们国家的女性提供了相应政治权利。随着经济的发展,工业化的推进,这些国家的女性权利也逐渐得到完善。 > 在1920年批准《[第19条修正案](https://zh.wikipedia.org/wiki/%E7%BE%8E%E5%9C%8B%E6%86%B2%E6%B3%95%E7%AC%AC%E5%8D%81%E4%B9%9D%E4%BF%AE%E6%AD%A3%E6%A1%88 "美国宪法第十九修正案")》之前,各个州已经通过了立法,允许女性在不同类型的选举中投票;有的只允许女性在学校或市政选举中投票,有的则要求女性拥有自己的财产才能投票,有些领土则向女性提供充分的选举权,只是在成为州之后就将其剥夺。[[53]](https://zh.wikipedia.org/wiki/%E5%A5%B3%E6%80%A7%E5%8F%83%E6%94%BF%E6%AC%8A#cite_note-53)尽管具有合法选举权,但1965年之前,黑人女性的这项权利在南部许多州实际上都被剥夺了。 > > —— wiki 美国女性的政治权利 我们说回到中国,为什么中国如今的男女对立如此严重呢?难道是中国的女性权利得不到保障吗?我认为中国政府在这一点做的相对较好。男女对立如此严重,并非主要是社会制度问题。 男女对立的出现,是近些年的事情。女权的出现,最早也是改革开放之后出现的。为什么1945年之前不会有男女对立呢?因为那时女性是男性的附庸,不存在所谓的对立。而改革开始之前呢?大家一穷二白,忙于社会主义建设与阶级斗争,恐怕无暇与性别的对立。 在改革开放的初期,男女对立仍然极不明显。甚至在进入新世纪之前,女性主义在中国都完全不流行。那时大量男性吃到了改革开放的红利,而大量女性呢,吃到了男性吃到了改革开放的红利的红利。 **到13年之前,虽然女性主义在中国已经有了自己的受众与市场,但是由于经济的快速发展,人们仍致力于勤劳与务实的致富。如果经济增长的势头能够持续下去,那么中国应该也能够形成类似欧美的两性关系**。可惜的是,自13年以来,经济增长速度的放缓、贫富差距的增大与社会阶级的固化都冲击着每一个普通人。 面对令人窒息的生活压力,普通人与富人的子嗣面对的挑战完全不同。富有的人,可以完全不必担心男女对立的问题,而普通人则无法回避,无从选择。 对于普通的女性,**她们无法得到适宜的工作**。那些印象中更适合女性的工作竞争激烈,更因这些岗位男女比例的失衡,使得雇主更愿意吸纳男性加入。我必须在这进行说明,这种行为是正常的也是自然的。首先,对于普通的岗位,有一定量的男性是必要的,因为一些体力活人们更倾向于让男性去做。并且,如果男女比例过大,会使数量少的群体被特殊化或边缘化,是不利于行业的生态的。这样,会**使一部分女生产生女性被社会鄙视的感觉,或者会认为男性抢走了她们的工作,从而对男性产生愤怒**。 对于普通男性,他们无法得到适宜的工作,得不到足够多的薪资,那用于追求女性的部分自然就会减少。而追求异性的代价却并未减少,甚至是逐渐增加的。于是**男性会认为女性比以往更加拜金,更加贪婪,于是对女性产生愤怒**。 不仅是工作的压力增加,**女性通过婚假来达到阶级跃升的可能性更小了**。在经济快速增长的时期,社会上充满了各种各样的机会。如果希望通过特殊的途径来增加自己的财富是相对较为简单的,也较为体面。当经济放缓时,希望通过婚假的方式实现阶级跃升,不管对于男性或女性,都会遇到更激烈的竞争,即使达到自己的目的,也会承受更多的非议。 同时,两人搭伙过日子的难度也有增无减,昂贵的房价,愈演愈烈的消费与难以维系的生育投入,都使得**男女双方对待婚姻更加的谨慎**。这两个条件叠加起来,使得两性在面对结婚这个话题时都格外的挑挑拣拣,疑神疑鬼。这样的行为无疑在消磨双方的精神耐心与爱。**对爱人与婚姻的失望可能会被放大到整个异性群体。** 不仅如此,消费主义的盛行无疑也在将男性与女性的距离拉开。**对于消费来说,女性的价值大于儿童大于男性**。我们从商超的布局就能看出来,第一层多是珠宝化妆品奢侈品这种高溢价的商品,再往上可能是女装或快时尚的衣物更多,再往上是儿童乐园与童装、儿童兴趣班与育婴商品之类。男装要么在更上层,要么散布在角落里。最上层是吃喝玩乐这种功能性与目的性更强的消费作为一次逛街的终点。**消费主义的盛行,对于男性与女性都意味着更大的经济压力**。女性需要买更多的化妆品与潮流的服饰,以实现自身对于美丽的追求,表达自身个性价值以及追去与世界和潮流的同步,同时男性也将一定程度上为这种花费买单。 **消费主义并非不好,而当经济下行与消费主义的盛行相遇时,无数的矛盾就会迸发,男女对立只是其中的一种。** 对于商人,他们需要拆解的传统的家庭,因为单身的男女或者恋爱中的男女能够有更多的消费能力。当男性与女性结婚成家时,他们花费在餐馆、娱乐场所与商场上的价值不出意外的将会减少。但当他们决心结婚来应对消费主义时,高额的房价又将榨干他们最后的一滴血。我们不得不承认,**年轻人已经进退维谷了**。 总结一下上述内容,**当经济经过一段时间的快速发展之后陷入低迷,而又有着巨大的贫富差距和较低的社会福利时,青壮年人就会感到理想与现实的落差。当这个落差不能被抹平时,通常就会爆发社会冲突**。目前不仅是男女对立,意识形态的对立、阶级的对立与地域的对立甚至民族的对立都同样的明显。 ***在迷茫的绝境中,人们本能的将责任甩给另一群人,只需要一点小小的引导,就能引发两派的冲突***。 ## 舆论,推波助澜的帮凶 微博、贴吧等社交平台不仅仅是男女对立的主战场,也是这一现象产生的引导者。在经济如是的背景下,他们引导了男女的双方的对立。 他们成为主战场的原因容易理解,而引导对立的原因却有点曲折。**以这两个平台为例,他们引导男女对立恐怕并非平台刻意为之,但是却是必然现象。** 这一切的根本原因便是受众的不同。 几年前的贴吧还不是男厕所,而是一个供大量同好交流经验与技术的平台。大量游戏、电影、动漫、小说的爱好者聚集在一起相互讨论与分享。几年前的微博也不是女厕所,而是相当多的明星与机构与普通人交流与分享的平台。人们聚集在一起通常是为了追星。他们二者的功能决定了他们的受众构成——贴吧的使用者大都是男性,而微博的使用者更多的是女性。 当然,只有受众并不构成充分条件,**另一重要的部分是平台对负面的、对立的、劣质的内容缺少正面的管理措施**。微博在这个方面已经是积重难返了,在成为女厕所之前它已经引起了数次追星族对其他人的攻击与诽谤。它是一个管理极度缺失的平台,除了某些敏感的言论,你几乎可以在上面说任何话,肆意侵犯他人的权利,攻击侮辱诽谤他人而几乎不需要付出任何代价。而平台的管理者在很多时候是起反作用的,他们不仅允许使用金钱燃起或消降热度,更默认购买使用机器人水军的行为。 贴吧在几年前还存在一个十分强力的民间自治组织,叫做吧务组。如今,这个制度恐怕濒临分崩离析了,为了热度与讨论度而牺牲专业性与自治性,导致如今的贴吧在言论的令人不适程度与思想的扭曲程度恐怕无人能敌。 在微博上的某些妖魔鬼怪在胡作非为的同时,其他人群在各种平台进行了不同程度的回击,其中以贴吧的回击在声量上最大,态度也十分激烈。微博上某明星的支持者与贴吧上的非议者相互攻击能够被看做是当下男女对立的雏形。 到此为止,**两个平台都完成朝向男女对立的首要战场的演化:某个性别占主导的受众构成、不受限制的劣质与负面内容,以及对冲突与话题的追求。** 虽然这两个平台依然承担着自己曾今的任务,但是最主要的街面上,已经充满着硝烟与恶臭。 不过还有一个问题,对立内容的生产者从何而来呢?我们绝对不能否认,一些生产者只是对自己的遭遇感到愤怒与不满,而在网络上宣泄自己的负面情绪,人自有恻隐之心,对这些人应该援以善意的问候,而非精心的引诱与利用。除此之外,绝对是有大部分人是出于某种利益而进行如是行为。一个原因是境外势力的煽动,我认为这种猜想可以被忽略,即使真的有境外势力,他们也没有如此大的能量与体量。**最主要的原因还是对流量的追求或者输出的观点能够为自己带来好处。** 前者容易理解,比如咪蒙,依靠热点话题带来的流量吸金。后者其实也常见,从男女对立上受益的势力有很多,比如部分情趣用品、某些单身经济的实体行业,以及很多受众性别单一的商品都可能从中谋取消费者的关注。 恰好,此时有两个广阔的舞台仿佛特意为他们搭建,能够让他们尽情演出。**在他们巧妙设计的剧情下,女性与男性之间的关系形同水火,势不相容。他们扭曲事实、断章取义,试图用一面之词、只言片语去评价一个人**——多么可鄙的一种行为。 当社会中男女对立被挑起时,其势如燎原烈火,恐怕很难再被阻挡了。 而如何男女双方如同上好的柴火如此容易引燃如同积怨已久? ***看过当下,或许我们还应该回顾从前。*** ## 历史,物是人非的困境 上文已经说过,在47年之前,女性一定程度上是男性的附庸。不只是中国,目前世界上所有的主流国家,在进行工业化之前,男性的地位都要高于女性。工业化之前,劳动主要以体力劳动为主,男性创造的价值要高于女性。况且女性要承担相当繁重的生育与养育的任务,所以主导了生产力的男性,也就主导了社会的结构。 古代中国的男女关系又有自己的特点,现在主流的学者喜欢叫民国之前的中国“封建中国”,但是很奇怪,中国从秦朝以来就已经废除了封建制度,改为郡县制。那为什么我们叫古代中国封建呢?我认为封建的是国家的cell——家庭。 我们中国人十分重视宗族,孰为宗,孰为祖,素以为重。在一个家庭中,最年长的家长一般占到绝对的话语权——即使她不掌握最多的话语权。什么意思呢?就像红楼梦中的贾府,贾府中表面上的话事人应该是贾赦,而实际上贾政却拥有更多的权威,而贾府的大小事物却由王熙凤统管,而贾府中言语最有重量最不容反驳的却是贾母。古代中国的家庭地位就是这样:**嫡长子优先,能者为大,长者为重**。嫡长子虽然是理论上的话事人,但是有能力的人通常说话会更有分量,而一家中最年长的人通常说的话是最重的。 这样就形成了一种层层剥削的封建制度。所以封建制度对人的压迫不限于男女,只是**同阶层的男女,女性的地位通常比男性低一层**。这一层的差距是血淋淋真是存在的,但却并非不可消弭。 我们印象中古代有权势的男性都是三妻四妾,花天酒地。而现实是,按照传统,古人是坚定的一夫一妻制的支持者。林语堂认为,读书人、传统世家大族的后代通常都是坚持一夫一妻的。嫁到这种家庭的女性,通常拥有较高的家庭地位。可能在明面上只负责相夫教子,而关起门来就能当起一家之主。不过古人的一夫一妻制有些特殊,它应该被描述成,一夫n妾(n>=0)。古人纳妾,通常需要征得妻子的同意,甚至有些女性为了减轻自己打理家庭、生养孩子的压力会主动让丈夫纳妾。妻与妾的地位有如云泥,发妻决不能被轻易抛弃,更不允许被其他人欺侮,不然男子会受到长年的羞辱与非议;而妾则是可以随意赠送的东西。**这对女性不公吗?当然不公,极其不公,但是要知道,在那个时代,妾并不是唯一可以被随意赠送与抛弃的东西**。 说了这么多,只是想说明一点:在古代,<u>同阶级</u>的男女组成家庭,家庭内部是有消弭男尊女卑的可能的。 而说明这一点,只是想让大家认识到,古代的男女不平等,并非如大众所想,更多的存在于家庭之间。**真正体现重男轻女的是二者的社会地位,而非家庭地位**。 古代的社会对女性的发展的可能性的限制是十分严重的。比如女子无法参加科举考试,鲜能从政,不可从军,更枉谈成为军官,不可参与宗族的祭奠、没有孩子的冠姓权,甚至被认为是没有传宗接代的能力……**除了对女性地位的限制,对女性人格的限制也同样甚至更为严重。女性被认为是弱势群体,甚至被强制变为弱势群体**。最著名的是在宋末之后,裹脚的习俗在女性之间流行,这种行为是及其血腥与野蛮的,是优势群体对劣势群体的强制臆想与人格压迫。 不过我们同时需要说明一点,**当女性被打压成为弱势群体,失去一定的权利的同时,她们有被赋予了一些新的权利**,比如不需要被强制征兵、不需要强制服徭役、可以较少的从事劳动生产等……同时,男女对立中很重要的一种东西——彩礼,这在这种背景下出现了。关于彩礼,不管是给到女方还是给到女方的父母,或是为了从女方手中“买下”闺女,或是给女方作为生活的保障。不管系何种情况,女方的父母都有义务给予自己的闺女一些嫁妆来保证自己的闺女在过门之后的生活。 我个人认为,**这种在权利被剥夺时被赋予的权利,应当具有原子性**。与其在彩礼的问题上挣个高下,不如保障女性在性与生育方面的决定权,当这个社会把被剥夺的权利返还给女性时,那么女性被额外赋予的权利也应该并且应当被女性自然的放弃。 不过历史中相当有趣的一点是,我们观察各个朝代的社会状况时,有时会觉得社会在倒退。比如木兰诗,是北朝民歌,那时巾帼英雄还被津津乐道的传颂;到了唐朝,女性依然有骑马打猎种地劳作的权利;到了宋朝,还有许多女性的文学方面展露头角,引领风骚。**这一情况的改变是在宋末,至于明朝,对女性的压迫就已到达极致……而清承明制,现代中国又一定情况承清制。** 在改革开放的初期,我们一度看到许多乱象。我们必须承认,重男轻女、男尊女卑的观念一直存在流传到如今。随着中国经济与产业的发展,女性的社会地位逐渐提高,那套从明清继承而来的封建糟粕已经不适于当今社会了。这便是这节标题说的物是人非——**因为历史的惯性,男女不平等的现象一定程度存在,而女性已经不是之前的女性了**,她们有着自己独立的思想,有着自己独立的经济能力。社会经济的结构变革,社会制度也必须要变革,**当女性不再在经济上附庸男性时,男女的平等便势在必行,而这一变革已经取得相当大的成效了,并且应当持续推进**。 不过坏消息是,这种变革的推进似乎遇到了阻碍:**当社会经济发展停滞时,这种变革就一定程度上会受到阻碍**。要完全把男尊女卑、男强女弱的思维从中国——尤其是中国的广大农村——消除看来还有一段路要走。 **而单单依靠的经济的力量是不稳定,我们同样需要法律与制度的力量**。 ## 法律,主观善意的错误 我们现在使用的刑法是在1980的颁布,在那个时代,女性的社会地位还不如如今这样平等,大量胚胎鉴定、拐卖妇女、嫖娼强暴的事情发生,所以那一版刑法有专门注意这一点并且制定了许多保护妇女儿童权益的法条。 但是随着思想的开放,有人意识到**对弱者的特别保护本身也会引发对弱者的歧视**。比如刑法的如下条款: > 第二百三十六条 以暴力、胁迫或者其他手段强奸**妇女**的,处三年以上十年以下有期徒刑。 > > 第二百三十七条  以暴力、胁迫或者其他方法强制猥亵他人或者侮辱**妇女**的,处五年以下有期徒刑或者拘役。 > > 第二百四十条  拐卖**妇女、儿童**的,处五年以上十年以下有期徒刑,并处罚金;有下列情形之一的,处十年以上有期徒刑或者无期徒刑,并处罚金或者没收财产;情节特别严重的,处死刑,并处没收财产 > > ——《中华人民共和国刑法》 这些法条的出发点是好的,也是正确的,但却并不完美。除了上文所说的原因之外,它在一定程度上忽视了男性也有可能遭遇强奸、强制侮辱与拐卖。 这些法令存在着一种性别上的刻板印象,并且将少数人的情况置于不顾。所以**当我们将其中的弱势群体改为全体大众时,不仅能够部分消除人们对于弱势群体的歧视、进一步促进男女平等,保护更多人的权利,而且也没有违背这些法律的本性,没有减少对弱者的保护**,何乐而不为呢? 如果排除立法者因为修改法条太麻烦,因为他们太忙而没时间去落实的原因,那就可能是当今的立法者并不认为男女的对立是一种严重的社会现象,并没有改变对两性的刻板印象。当然,我们无须对此苛责。法条的修改本身就是一件严肃的事情,不可一蹴而就。 不过我们的法律在一定程度上更能体现肉食者的意图,他们又是如何在高高在上的位置看待男女对立的现象呢?**也许,他们并不在乎**。 ## 政治,错失机会的手段 上一节我说他们并不在乎,并非空穴来风妄加揣测。中国是一个很大的国家,内政外交事物繁多,像男女对立这样的问题,一非政治危机,二非社会危机,三非外交危机,很难让人重视。况且以他们的年龄,这样的事情在他们看来不过是年轻男女的气盛之缘。毕竟在他们这个年代,男女小的时候也是对立仇视的,而到了青春期之后,却都互生情愫、求之若渴。他们无视了这个问题,以他们的角度,实在是正常的。 而在我的角度看,在几年前,我们完全能把这种现象扼杀在萌芽中,而且方法还有很多。这里有上中下三策: **上策,加快产业转型,降低房价,提高城镇化水平,完善医疗体系,保证社会福利,进一步开放国门,加强国际合作、加强科技投入、改善教育制度,赚更多的钱。** **中策,以法律的形式保证男女同工同酬、强迫男性与女性休同样时间的产假、承认LGBT群体的利益、强制取缔彩礼、保证女性的生育权、大力增加生育的补贴,保证男女的平等。** **下策,督促各个社交平台删除性别对立的言论,对部分以此为话题的博主进行封杀。** 这三个对策应该都是有效的,只不过**上策伤筋动骨,中策伤及皮肉,下策易于饮水**。不过,目前并没有专门针对性别对立这个现象做什么实质性的动作。不但如此,还有一些政策进一步加剧了男女之间的对立。 试问,中国目前最被重视的问题是什么? 中美关系?中台关系?中日关系?新冠疫情?芯片短期?经济复苏?生育率低?其实这些都算不上最主要的问题,**最主要的问题是,房地产**。房地产是中国的立国之本,是政府收入的主要来源,是大多数家庭的主要财富。所以一定要保证房地产的价格不跌。那是什么决定商品的价格呢?剖除政府的控制不谈,那供需关系一定是最主要的因素。当一件商品因为价格过高在市场上的流通率降低时,那它一定会逐渐降价。但是房地产并不是普通的商品,他还是理财产品——当然,对于大多数人来说,房子可能只是寓所。 **但是对于一些人来说房价就是财富,所以房价是万万不能跌的**。如果房价不跌,那么购房的人就会减少,如此房产的积压就会使很多房地产公司亏损、资金链断裂、无法偿还债务,以至破产。这对整个政府来说都是难以接受的损失。 所以政府要做的第一件事,就是保证房地产市场能够正常运转,保持房市的正常运转就是保证政府的财政,就是保护房企,就是保护已经购房者的利益,**而房地产正常运转的方案只能是保证更多的人购房**。所以这种博弈呢,至少要一个人受损失,要么是政府,要么是房企,要么是已经是购房者,要么是未购房者。**前面三种势力的能量都是极大的,所以必须要让还没有买房的人承受损失**。那么如何让这种剥削能够持续呢?回忆我们古代的家庭是如何为续的呢?莫不是长辈剥削晚辈,这样往下,只要子孙不绝,剥削便不停止。 **只要大家能够源源不断的结婚生子,那么新生儿就需要买房——毕竟现在婚房已经快要成为结婚的决定条件了——这样,这个游戏就能维持下去了**。所以为了房产,政府必须不断的促进人民结婚生育,只有这样,房地产的游戏才能继续。而青年男女结婚的压力却又如此巨大,房、车、生育,一座座大山压在他们的头上。 结婚,有山大的压力,而不结婚呢?父母的催婚,政府的催婚,单位的催婚……纷至沓来的又是各种压力。又是这样的进退维谷,毫无办法。真是,结婚,青年苦,不婚,青年苦。**如此的矛盾与高压,在不能冲击触及其他阶层的情况下,青年男女除了相互攻击,还有什么缓解压力的做法**?难道靠做爱吗? ## 女权,莫衷一是的组织 为何在此只说女权不说男权呢?应为上文已经提到过,现在主流的国家曾今都是男性占主导的社会,所以只会有女权组织,不会有男权组织。不过我在此提到这个组织并没有任何的不敬,我只是想要说明一个事实:**在中国,从来没出现过一个团结的、目标一致的、大规模的、具有里程碑意义的女权组织**。 而这其中最重要的是,没有一个一致的目标。而任何一次运动,没有统一的纲领很难成功的。而在中国,**想要为女权运动定一个统一的纲领是相当困难的。** 女权运动想要什么?男女平等?这个纲领太笼统了,他们会说,新中国成立以来,男女就已经平等了。选举权?在中国,没有选举权的可不只是女性。同工同酬?这个似乎在中国并不是一个很大的问题。取消彩礼,取消对弱势群体的特殊保护?这可能是大多数女性不想看到的。 实际上,**在中国,男女平等在社会层面实际上已经做的相当好了,问题出在个人身上,出现在思想上**。有许多人在思想上觉得男女不平等,并且以自己个人的身份作出重男轻女或者重女轻男的事情。对于这种人,我们必然不能将他们统统判为死刑,这似乎有些太不人道了。由于历史有一定的惯性,随着社会发展,这种情况一定会逐渐消失,我们需要做的只是等待。 对于现在,社会运动可能对个人的思想不起作用,我们能做的只有坚持自己男女平等的思想,然后原理那些用重男轻女或重女轻男的思想压迫我们的人。 这就要求我们自己有能够独立生活的能力,**能够做到经济独立,才能最低限度的让自己实现男女的平等**。所以在中国,女权运动这种方式并不符合我们的国情,而在网络上站队,互相诋毁也不能改变自己的处境。**与其诋毁他人,不如提升自己**。 ## 题外话:田园女权与直男癌 几年前,有两个词语比较流行,一个叫做“田园女权”,一个叫做“直男癌”。 > 田园女权,[网络流行词](https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E6%B5%81%E8%A1%8C%E8%AF%8D/4604547?fromModule=lemma_inlink),“中华田园女权”的简称,指要求[男女平等](https://baike.baidu.com/item/%E7%94%B7%E5%A5%B3%E5%B9%B3%E7%AD%89/2939686?fromModule=lemma_inlink)却要男性承担主要[责任](https://baike.baidu.com/item/%E8%B4%A3%E4%BB%BB/32951?fromModule=lemma_inlink),以女权为借口追求女性收益最大化的群体。 > 直男癌一词源于网友对活在自己的世界观、价值观、[审美观](https://baike.baidu.com/item/%E5%AE%A1%E7%BE%8E%E8%A7%82?fromModule=lemma_inlink)里,时时向别人流露出对对方的不顺眼及不满,并略带[大男子主义](https://baike.baidu.com/item/%E5%A4%A7%E7%94%B7%E5%AD%90%E4%B8%BB%E4%B9%89?fromModule=lemma_inlink)的人的一种蔑称或调侃。 当时性别对立还不强烈,大部分人对于田园女权和直男癌都是持批评态度的。 但是如今这两个词几乎已经消失在网络历史的黄沙当中了。究其原因,无非是现在的网友已经完成了女权向田园女权、直男向直男癌的退化了。当他们成为主流,也就无须背负他们的前缀与后缀了。这样的结果是令人遗憾的。 笔者所在的地方依然有着一定程度的男女不平等现象,包括但不限于女性不能在男性喝酒时上桌、不能参与祭祀等,我也曾汲汲与男女的平权,不曾想时过境迁,风向的变化居然能如此之快。 支持男女平等的,从来不只是女性,青年的男性也深受男女不平等之苦。青年的男女应该是最团结的盟友,而他们的敌人永远是冰冷的现实。当青年男女开始相互攻击的时候,他们只能多了一个敌人,而少了一个朋友。 几乎所有正常的人,都能看到当下这种处处对立的环境的低劣之处,但人们并没有选择去结束或遏制这种糟糕的舆论环境。 曾认为这种处处的对立,不过是在网络中才会出现的现象。但当它开始侵蚀我们的现实时,我们还有能力去改变现状吗? 而这种侵蚀,却早已开始了。

SSM & SpringBoot 138问138答

# SSM 73问 ## mybaits 21问 ### Mybaits与Hibernate的异同? Hibernate与MyBatis都是ORM框架,都有相应的代码生成工具,可以生成简单基本的DAO层方法。 Mybaits是半ORM框架,Hibernate是全ORM框架 Mybaits需要手动写SQL语句,Hibernate不需要 ### 什么是ORM? **对象关系映射**(英语:**Object Relational Mapping**,简称**ORM**,或**O/RM**,或**O/R mapping**),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。 目的是**使用面向对象的方法操纵数据库** ### mybatis怎么配置环境信息? ```xml <environments default="development"> <!-- default:默认的环境 ID--> <environment id="development"> <!-- 事务管理器的配置 如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器 - JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。 - MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期 --> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <!-- 数据源的配置--> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> ``` ### mybaits的setting有什么作用? 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。下表描述了设置中各项的意图、默认值等。 | 设置参数 | 描述 | 有效值 | 默认值 | |------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------| | cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true,false | true | | lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置`fetchType`属性来覆盖该项的开关状态。 | true,false | false | | aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 | true,false | false (在 3.4.1 及之前的版本中默认为 true) | | multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动)。 | true,false | true | | useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true,false | true | | useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | true,false | False | | autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL | | defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE | | defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) | | safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。 | true,false | False | | mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true, false | False | | localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION,STATEMENT | SESSION | | jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER | | lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载。 | A method name list separated by commas | equals,clone,hashCode,toString | | defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | A type alias or fully qualified class name. | org.<br/>apache<br/>.ibatis.<br/>scripting.<br/>xmltags.<br/>XMLDynamicLanguageDriver | | callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | true,false | false | | logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | Any String | Not set | | logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J<br/>,LOG4J<br/>,LOG4J2,<br/>JDK_LOGGING,<br/>COMMONS_LOGGING,<br/>STDOUT_LOGGING,<br/>NO_LOGGING | Not set | | proxyFactory<br><br> | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB JAVASSIST | CGLIB<br><br> | | vfslmpl<br><br> | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | no set<br><br> | | useActualParamName<br><br> | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 `-parameters` 选项。(新增于 3.4.1) | true \\ | false | | configurationFactory<br><br> | 指定一个提供 `Configuration` 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为`static Configuration getConfiguration()` 的方法。(新增于 3.2.3) | 类型别名或者全类名. | no set | 一个配置完整的 settings 元素的示例如下: ```xml <settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings> ``` ### 如何配置类型别名? 类型别名是为 Java 类型设置一个短的名字,存在的意义仅在于用来减少类完全限定名的冗余。例如: ```xml <typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases> ``` 当这样配置时,`Blog`可以用在任何使用`domain.blog.Blog`的地方。 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如: ```xml <typeAliases> <package name="domain.blog"/> </typeAliases> ``` 每一个在包 `domain.blog` 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 `domain.blog.Author` 的别名为 `author`;若有注解,则别名为其注解值。看下面的例子: ```java @Alias("author") public class Author { ... } ``` ### 三种数据源类型的区别? **UNPOOLED**– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然一点慢,它对在及时可用连接方面没有性能要求的简单应用程序是一个很好的选择。 **POOLED**– 这种数据源的实现利用"池"的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。 **JNDI**– 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。 ### 如何指定映射文件? 最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 `file:///` 的 URL),或类名和包名等。例如: ```xml <!-- 使用相对类路径定义资源 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> </mappers> <!-- 使用完全限定资源定位符 --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> </mappers> <!-- 使用包名+类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> </mappers> <!-- 注册包内所有mapper --> <mappers> <package name="org.mybatis.builder"/> </mappers> ``` ### select与属性的用法? 简单查询的 select 元素是非常简单的。比如: ```xml <select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select> <!-- id:查询的名称 parameterType:参数类型 resultType:返回值类型 --> ``` select 元素有很多属性允许你配置,来决定每条语句的作用细节。 ```xml <select id="selectPerson" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10000" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY"> ``` 属性的含义: | 属性 | 描述 | |---------------|------------------------------------------------------------------------------------------------------------------------------| | id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 | | parameterType | 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 | | resultType | 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。 | | resultMap | 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用。 | | flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。 | | useCache | 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。 | | timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 | | fetchSize | 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。 | | statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 | | resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)。 | | databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 | | resultOrdered | 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 | | resultSets | 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。 | ### insert、update、delete的用法? 数据变更语句 insert,update 和 delete 的实现非常接近: ```xml <insert id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20"> <update id="updateAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20"> <delete id="deleteAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20"> ``` Insert, Update 和 Delete 的属性 | 属性 | 描述 | |------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | id | 命名空间中的唯一标识符,可被用来代表这条语句。 | | parameterType | 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 | | flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。 | | timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 | | statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 | | useGeneratedKeys | (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 | | keyProperty | (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 | | keyColumn | (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 | | databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 | 下面就是 insert,update 和 delete 语句的示例: ```xml <insert id="insertAuthor"> insert into Author (id,username,password,email,bio) values (#{id},#{username},#{password},#{email},#{bio}) </insert> <update id="updateAuthor"> update Author set username = #{username}, password = #{password}, email = #{email}, bio = #{bio} where id = #{id} </update> <delete id="deleteAuthor"> delete from Author where id = #{id} </delete> ``` ### 两种字符串替换的区别? | #{} | ${} | |--------------------------------|--------------------------------| | 参数占位符,即预编译 | 字符串替换符,即SQL拼接 | | 很大程度上能防止sql 注入 | 不能防止sql 注入 | | 将传入的数据都当成一个字符串,会对传入的变量自动加一个单引号 | 将传入的参数直接显示生成在sql中,且不加任何引号 | | | 排序时使用order by 动态参数时需要注意,用$而不是# | ### 三种自动映射等级? ```xml <setting name="autoMappingBehavior" value="PARTIAL"/> ``` NONE 表示取消自动映射 PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集 FULL 会自动映射任意复杂的结果集(无论是否嵌套) ### if标签的作用? ```xml <select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null"> <!-- 如果test为true则语句包含if内的内容--> AND title like #{title} </if> </select> ``` ### foreach标签的用法? ```xml <select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach> </select> <!-- foreach 允许你指定一个集合,声明可以用在元素体内的集合项和索引变量--> <!-- 也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。--> ``` ### 怎么配置日志级别? ```properties # 全局日志等级 log4j.rootLogger=ERROR, stdout # mabatis mapper日志等级 log4j.logger.org.mybatis.example.BlogMapper=TRACE ``` ### 一级缓存和二级缓存的定义与区别? - 一级缓存 - 定义 - 一级缓存作用域是sqlsession级别的,同一个sqlsession中执行相同的sql查询(相同的sql和参数),第一次会去查询数据库并写到缓存中,第二次从一级缓存中取。 - 一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,默认打开一级缓存。 - 清空一级缓存 - 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 - 一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。 - 一级缓存无过期时间,只有生命周期 - 二级缓存 - 简介 - 它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。 - 二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。 、 - 何时存入 - 在关闭sqlsession后(close),才会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。 - 开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中。 - 二级缓存有过期时间,但没有后台线程进行检测 - 需要注意的是,并不是key-value的过期时间,而是这个cache的过期时间,是flushInterval,意味着整个清空缓存cache,所以不需要后台线程去定时检测。 - 每当存取数据的时候,都要检测一下cache的生命时间,默认是1小时,如果这个cache存活了一个小时,那么将整个清空一下。 - 当 Mybatis 调用 Dao 层查询数据库时,先查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找。 ### useCache与flushCache的作用? ```xml <select flushCache="false" useCache="true" > <!--useCache:将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。--> <!--flushCache:将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。--> ``` ### Redis的概念? Redis 是一个高性能的key-value数据库。经常用作缓存 ### 那个注解是对dao组件的修饰? ```java @Mapper //Mybatis 需要找到对应的 mapper,在编译的时候动态生成代理类 @Repository //用于标注数据访问组件,即DAO组件 ``` ### JavaModelGenerator配置、sqlMapperGenerator配置与JavaClientGenerator配置的作用? - JavaModelGenerator配置生成的实体类的存放的位置(entity层的java文件) - sqlMapperGenerator配置生成的映射文件的位置(resources/dao中的xml文件) - JavaClientGenerator配置生成的mapper接口文件的位置(dao层中的java文件) ### 如何实现分页? 导入依赖包 ```xml <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.3</version> </dependency> ``` 配置properties ```properties #pagehelper配置 pagehelper.helper-dialect=mysql pagehelper.reasonable=true pagehelper.support-methods-arguments=true pagehelper.params=count=countSql ``` 使用 ```java @Transactional(readOnly = true) @Override public PageInfo<Student> getStudentListByBatchName(String batchName, Integer pageNo) { //spring boot程序中,添加了PageHelper的启动依赖后,直接调用对应方法分页查询即可 PageHelper.startPage(pageNo, 5); List<Student> list = studentMapper.getListByBatchName(batchName); //传入查询的列表,创建一个PageInfo对象,用于包含分页的所有信息 //还可以传入第二个参数,表示导航页码的数量 PageInfo<Student> pageInfo=new PageInfo<>(list,5); return pageInfo; } ``` ### 分页最后封装成什么数据? `PageInfo<T>` ## spring Framework 40问 ### 依赖注入的概念? Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系. 控制反转核心思想就是由 Spring 负责对象的创建。DI是IOC的一种。在对象创建过程中,Spring 会自动根据依赖关系,将它依赖的对象注入到当前对象中,这就是所谓的“依赖注入”。 ### springCoreContainer是什么? ![img.png](./ssm/core_container.png) - Core:核心工具包,包括字节码操作cglib、asm,资源的抽象Resource,对象实例化化工具等等。 - Beans:Bean 的定义、Bean 的创建以及对 Bean 的解析。 - Context:Context模块建立在Core和Beans模块之上,是Bean运行环境(即保存维护Bean的状态、数据,Bean之间的关系),又称之为Ioc容器。 - SpEL:提供了一个强大的表达式语言,可以在运行时查询和操作对象。 ### springIoC容器的类型? #### spring BeanFactory 容器 - 是Spring bean容器的根接口,提供获取bean,是否包含bean,是否单例与原型,获取bean类型,bean 别名的方法 。 - BeanFactory不支持国际化功能 - 不支持事件机制 - 没有扩展ResourceLoader,只能加载一个Resource - BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化 - BeanFactory需要手动注册 #### Spring ApplicationContext 容器 - ApplicationContext继承了BeanFactory - 扩展了MessageResource接口,因而具有消息处理的能力(i18N) - 通过ApplicationEvent和ApplicationListener这两个接口实现事件机制 - 扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource - 在容器启动时,一次性创建了所有的Bean - 而ApplicationContext则是自动注册 ### ApplicationContext容器的实现有哪些? ApplicationContext 有两个直接子接口:WebApplicationContext 和 ConfigurableApplicationContext。 ConfigurableApplicationContext:扩展于ApplicationContext, 新增加两个主要方法。refresh()和close(),让ApplicationContext具有启动、刷新和关闭上下文的能力。ApplicationContext在初始化上下文时就实例化所有的单例Bean. WebApplicationContext:WebApplicationContext是专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作。 最常用的两个实现类: ClassPathXmlApplicationContext : 从类路径下加载配置文件。 FileSystemXmlApplicationContext : 从文件系统中加载配置文件。 两个都是继承ConfigurableApplicationContext ### bean标记的使用? ```xml <bean id="Bean 唯一标志符" class="包名+类名" p:普通属性="普通属性值" p:对象属性-ref="对象的引用"> ``` Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。 | 短命名空间 | 简化的 XML 配置 | 说明 | |--------|--------------------------------|--------------------------| | p 命名空间 | <bean> 元素中嵌套的 <property> 元素 | 是 setter 方式属性注入的一种快捷实现方式 | | c 命名空间 | <bean> 元素中嵌套的 <constructor> 元素 | 是构造函数属性注入的一种快捷实现方式 | ### bean的范围有哪些? Spring 5 共提供了 6 种 scope 作用域,如下表。 | 作用范围 | 描述 | |-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | singleton | 默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例 | | prototype | 原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。 | | request | 每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。 | | session | 同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。 | | application | 同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。 | | websocket | websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。 | ### 如何获取bean? 1. 在初始化时保存ApplicationContext对象 2. 通过Spring提供的utils类获取ApplicationContext对象 3. 继承自抽象类ApplicationObjectSupport 4. 继承自抽象类WebApplicationObjectSupport 5. 实现接口ApplicationContextAware 6. 通过Spring提供的ContextLoader ### bean的生命周期和生命周期方法? 在传统的 Java 应用中,Bean 的生命周期很简单,使用 Java 关键字 new 进行 Bean 的实例化后,这个 Bean 就可以使用了。一旦这个 Bean 长期不被使用,Java 自动进行垃圾回收。 相比之下,Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段: 1. Bean 的实例化 2. Bean 属性赋值 3. Bean 的初始化 4. Bean 的使用 5. Bean 的销毁 Spring 根据 Bean 的作用域来选择 Bean 的管理方式, - 对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁; - 对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。 ![Spring 生命周期流程](./ssm/1F32KG1-0.png) Bean 的生命周期回调方法主要有两种: - 初始化回调方法:在 Spring Bean 被初始化后调用,执行一些自定义的回调操作。 - 销毁回调方法:在 Spring Bean 被销毁前调用,执行一些自定义的回调操作。 **通过接口实现** ```java //in bean class @Override public void afterPropertiesSet() throws Exception { System.out .println("【InitializingBean接口】调用InitializingBean.afterPropertiesSet()"); } // 这是DiposibleBean接口方法 @Override public void destroy() throws Exception { System.out.println("【DiposibleBean接口】调用DiposibleBean.destory()"); } ``` **通过XML配置实现** ```java //in bean class public void myInit() { System.out.println("【init-method】调用<bean>的init-method属性指定的初始化方法"); } public void myDestory() { System.out.println("【destroy-method】调用<bean>的destroy-method属性指定的初始化方法"); } ``` ```xml <bean id="person" class="springBeanTest.Person" init-method="myInit" destroy-method="myDestory" scope="singleton" p:name="张三" p:address="广州" p:phone="15900000000" /> ``` **通过注解实现** | 注解 | 描述 | | -------------- | --------------------------------------------------- | | @PostConstruct | 指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。 | | @PreDestroy | 指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。 | **通过后置处理器实现** ```java public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; } ``` ### 两种显式装配? **构造函数注入**</b> 使用构造函数实现属性注入大致步骤如下: 1. 在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性; 2. 在 Spring 的 XML 配置文件中,通过 `<beans>` 及其子元素 `<bean>` 对 Bean 进行定义; 3. 在 `<bean>` 元素内使用 `<constructor-arg>`元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 `<constructor-arg>` 元素。 <b>**setter注入**</b> 使用 setter 注入的方式进行属性注入,大致步骤如下: 1. 在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx() 方法; 2. 在 Spring 的 XML 配置文件中,使用 `<beans>` 及其子元素 `<bean>`对 Bean 进行定义; 3. 在 `<bean>`元素内使用 `<property>` 元素对各个属性进行赋值。 ### value和ref的区别? value用于注入字面量属性,ref用于注入引用属性 ### 如何注入集合? 标签说明<list>用于注入 list 类型的值,允许重复<set>用于注入 set 类型的值,不允许重复<map>用于注入 key-value 的集合,其中 key 和 value 都可以是任意类型<props>用于注入 key-value 的集合,其中 key 和 value 都是字符串类型 | 标签 | 说明 | |-----------|--------------------------------------------| | `<list>` | 用于注入 list 类型的值,允许重复 | | `<set>` | 用于注入 set 类型的值,不允许重复 | | `<map>` | 用于注入 key-value 的集合,其中 key 和 value 都可以是任意类型 | | `<props>` | 用于注入 key-value 的集合,其中 key 和 value 都是字符串类型 | ### 构造函数注入? ```xml <bean id="bean名" class="类"> <constructor-arg name="属性名" value="值"/> <constructor-arg name="属性名" ref="引用"/> </bean> ``` 或使用c:命名空间 ### parent属性的作用? 在 Spring XML 配置中,我们通过子 Bean 的 parent 属性来指定需要继承的父 Bean,配置格式如下。 ```XML <!--父Bean--> <bean id="parentBean" class="xxx.xxxx.xxx.ParentBean" > <property name="xxx" value="xxx"/> <property name="xxx" value="xxx"/> </bean> <!--子Bean--> <bean id="childBean" class="xxx.xxx.xxx.ChildBean" parent="parentBean"/> ``` ### 如何使用工厂方法实现DI? ```java public class AnimalFactory { public static Animal getAnimal1() { System.out.println("调用AnimalFactory类的静态工厂方法getAnimal1()................."); return new Dog(); } public Animal getAnimal2() { System.out.println("调用AnimalFactory类的实例工厂方法getAnimal2()................."); return new Pig(); } private static Map<Integer,Animal> map; static { map=new HashMap<>(); map.put(1,new Dog()); map.put(2,new Pig()); map.put(3,new Sheep()); } public static Animal getAnimal3(int type) { System.out.println("调用AnimalFactory类的静态工厂方法getAnimal3()................."); return map.get(type); } } ``` 使用静态工厂方法返回自身实例 ```xml <!-- 如果想通过一个工厂类的静态工厂方法创建bean 可通过factory-method属性指定创建实例的方法的名称 class指定工厂类的类型 --> <bean class="com.qdu.bean.A" factory-method="getA" /> ``` 使用静态工厂方法返回其他类实例 ```xml <!-- 如果希望调用A类的静态方法来获取B类的实例 --> <!-- 这种情况下,class指定的应该是工厂方法所在的类的名称 --> <!-- factory-method属性指定创建实例的方法的名称 --> <!-- 实际创建的对象不是AnimalFactory类型,而是Dog类型 --> <bean id="animal" class="com.qdu.bean.AnimalFactory" factory-method="getAnimal1" /> <!-- 希望通过AnimalFactory这个工厂类的静态方法getAnimal3(int type) --> <!-- 来创建要托给spring管理的Dog或Pig或Sheep的实例 --> <bean id="animal" class="com.qdu.bean.AnimalFactory" factory-method="getAnimal3"> <!-- constructor-arg在构造函数注入的时候是构造函数参数的名称 --> <!-- 但是对于工厂方法,就是指定一个普通方法参数的信息 --> <constructor-arg name="type" value="2" /> </bean> ``` 使用非静态工厂方法返回其他类实例(不考) ```xml <!-- 如果希望通过工厂类A的非静态方法来实例化B --> <!-- 必须先有一个A的对象才可以 --> <bean id="animalFactory" class="com.qdu.bean.AnimalFactory" /> <!-- 如果希望通过工厂类A的非静态方法来实例化B 这时候不再使用class属性来指定生成的bean的类型 factory-bean属性指定创建bean实例(如这里的Pig)的工厂对象是哪个,指定其id或name factory-method属性指定创建bean实例的工厂方法的名称 id为animal的bean实际是一个Pig实例,也即是getAnimal2()方法返回的实例 --> <bean id="animal" factory-bean="animalFactory" factory-method="getAnimal2" /> ``` 在java中获取bean ```java public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("config/beans1.xml"); A a = ctx.getBean(A.class); a.methodOfA(); } ``` ### 自动装配的模式有哪些? Spring 共提供了 5 中自动装配规则,它们分别与 autowire 属性的 5 个取值对应,具体说明如下表。 | 属性值 | 说明 | |-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| | byName | 按名称自动装配。 Spring 会根据的 Java 类中对象属性的名称,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。 | | byType | 按类型自动装配。 Spring 会根据 Java 类中的对象属性的类型,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。 | | constructor | 与 byType 模式相似,不同之处在与它应用于构造器参数(依赖项),如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。 其实就是根据构造器参数的数据类型,进行 byType 模式的自动装配。 | | default | 表示默认采用上一级元素 <beans> 设置的自动装配规则(default-autowire)进行装配。 | | no | 默认值,表示不使用自动装配,Bean 的依赖关系必须通过 <constructor-arg>和 <property> 元素的 ref 属性来定义。 | **基于注解的自动装配** ```xml <!--开启组件扫描--> <context:component-scan base-package=""/> ``` ```java @Autowired ``` ### 显式装配与自动装配的关系? 显式装配是在 XML 配置中通过 <constructor-arg>和 <property> 中的 ref 属性,手动维护 Bean 与 Bean 之间的依赖关系的。 Spring 的自动装配功能可以让 Spring 容器依据某种规则(自动装配的规则,有五种),为指定的 Bean 从应用的上下文(AppplicationContext 容器)中查找它所依赖的 Bean,并自动建立 Bean 之间的依赖关系。而这一过程是在完全不使用任何 <constructor-arg>和 <property> 元素 ref 属性的情况下进行的。 ### 依赖注入使用哪些注解? Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。 | 注解 | 说明 | |-------------|-------------------------------------------------------------------------------------------------------------| | @Component | 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。 | | @Repository | 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 | | @Service | 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 | | @Controller | 该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 | 我们可以通过以下注解将定义好 Bean 装配到其它的 Bean 中。 | 注解 | 说明 | |------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | @Autowired | 可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。 @Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用 | | @Resource | 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。 @Resource 中有两个重要属性:name 和 type。 Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配;如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。 | | @Qualifier | 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。 | ### @Configuration和@Bean注解的作用 ```java /* @Configration 注解作用在类、接口(包含注解)上 @Configuration 用于定义配置类,可替换 xml 配置文件 @Configration 注解类中可以声明一个或多个 @Bean 方法 */ @Configuration public class MyConfig { /* @Bean 注解作用在方法上 @Bean 指示一个方法返回一个 Spring 容器管理的 Bean,也就是说方法返回值就是给Springr容器装配的bean @Bean 一般和 @Component 或者 @Configuration 一起使用,也可以在 @Service 里使用,没有特定要求,主要看项目的需求。 @Bean 注解默认作用域为单例 singleton 作用域,可通过 @Scope(“prototype”) 设置为原型作用域 */ @Bean public MyBean myBean() { return new MyBean(); } @Bean public MyBean myBean1() { return new MyBean(); } } ``` ### 什么是AOP? AOP 的全称是“Aspect Oriented Programming”,译为“面向切面编程”,和 OOP(面向对象编程)类似,它也是一种编程思想。 ### 通知的概念? AOP的一套术语 | 名称 | 说明 | |----------------|------------------------------------------------------------------------------------| | Joinpoint(连接点) | AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。 在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。 | | Pointcut(切入点) | 又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 | | Advice(通知) | 指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。 | | Target(目标) | 指代理的目标对象,通常也被称为被通知(advised)对象。 | | Weaving(织入) | 指把增强代码应用到目标对象上,生成代理对象的过程。 | | Proxy(代理) | 指生成的代理对象。 | | Aspect(切面) | 切面是切入点(Pointcut)和通知(Advice)的结合。 | ### 连接点的概念? 见上条 ### 通知的类型(位置)? 共有5种: | 通知 | 说明 | |------------------------|-------------------| | before(前置通知) | 通知方法在目标方法调用之前执行 | | after(后置通知) | 通知方法在目标方法返回或异常后调用 | | after-returning(返回后通知) | 通知方法会在目标方法返回后调用 | | after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 | | around(环绕通知) | 通知方法会将目标方法封装起来 | ### 用来创建通知的注解有哪些? | 名称 | 说明 | |-----------------|----------------------------------------------| | @Aspect | 用于定义一个切面。 | | @Pointcut | 用于定义一个切入点。 | | @Before | 用于定义前置通知,相当于 BeforeAdvice。 | | @AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 | | @Around | 用于定义环绕通知,相当于 MethodInterceptor。 | | @AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 | | @After | 用于定义最终通知,不管是否异常,该通知都会执行。 | | @DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 | ### 如何声明切入点? ```java //Pointcut表示式 //我除了可以通过切入点表达式(execution)直接对切点进行定义外 //还可以通过切入点方法的名称来引用其他的切入点 //在使用方法名引用其他切入点时,还可以使用“&&”、“||”和“!”等表示“与”、“或”、“非”的含义 @Pointcut("execution(* com.savage.aop.MessageSender.*(..))") //定义为切点的方法,它的返回值类型必须为 void private void log(){} ``` ### 在返回后通知中如何获取目标方法的返回值? ```java @AfterReturning(value = "pt()",returning = "ret") public void afterReturning(Object ret) { System.out.println("afterReturning advice ..."+ret); } ``` ### XML如何配置AOP? 在 Spring 的 XML 配置文件中,添加以下内容启用 @AspectJ 注解支持。 ```xml <!-- 开启注解扫描 --> <context:component-scan base-package=""/> <!--开启AspectJ 自动代理--> <aop:aspectj-autoproxy/> ``` ```xml <!-- 默认是不启用@AspectJ的支持,也就是默认情况下,@Aspect等注解是不起作用的,启用AspectJ支持后才会起作用 --> <!-- 该注解的作用是启用@AspectJ风格的Spring AOP --> <!-- 如果是基于xml schema的aop是否还需要使用该标记: 不用 --> <!-- <aop:aspectj-autoproxy /> --> <!-- 方面对应的类需要注册为spring管理的bean,才能将方面切入到需要的地方 --> <bean id="logAspect1" class="com.qdu.aop.LogAspect1" /> <bean id="logAspect2" class="com.qdu.aop.LogAspect2" /> <bean class="com.qdu.service.impl.StudentServiceImpl" /> <bean class="com.qdu.service.impl.TeacherServiceImpl" /> <bean class="com.qdu.service.impl.MathServiceImpl" /> <!-- execution(* com.qdu.service.StudentService.*(..)) --> <!-- execution(* com.qdu.service.MathService.add(..)) --> <!-- execution(* com.qdu.service.MathService.divide(..)) --> <!-- execution(* com.qdu.service.MathService.multiply(..)) --> <!-- aop:config用于以xml格式配置aop --> <aop:config> <!-- aop:config标记中可以定义切入点,这样的切入点可以在多个aop:aspect标记中使用 --> <aop:pointcut expression="execution(* com.qdu.service.StudentService.*(..))" id="pt1" /> <!-- aop:aspect用于配置一个方面对应的类 --> <!-- ref指定方面类bean的id或name --> <!-- order用于控制通知的执行顺序,值越小,该方面类中对应的通知就会先执行 --> <aop:aspect ref="logAspect1" order="2"> <!-- 在aop:aspect标记内定义的切入点只能在该aop:aspect标记中使用 --> <aop:pointcut expression="execution(* com.qdu.service.MathService.add(..))" id="pt2" /> <aop:pointcut expression="execution(* com.qdu.service.MathService.divide(..))" id="pt3" /> <aop:pointcut expression="execution(* com.qdu.service.MathService.multiply(..))" id="pt4" /> <!-- aop:before用于配置前置通知,method指定作为前置通知的方法的名称 --> <!-- pointcut属性用于指定切入点表达式,pointcut-ref用于指定引用的切入点的id --> <aop:before method="before1" pointcut-ref="pt1" /> <aop:before method="before2" pointcut-ref="pt1" /> <!-- aop:after-returning用于配置返回后通知,returning用于指定一个参数名(可随便起,尽量有意义) --> <!-- 这样可以在返回后通知对应的方法上添加一个该名称的参数,用于接收目标方法的返回值 --> <aop:after-returning method="afterReturning" pointcut-ref="pt2" returning="returnValue" /> <!-- aop:after-throwing用于配置抛出后通知,throwing属性指定一个参数名(可随便起,尽量有意义) --> <!-- 这样可以抛出后通知对应的方法上添加一个该名称的参数,用于接收抛出的异常对象 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pt3" throwing="ex" /> <!-- aop:after用于配置最终通知/后置通知 --> <aop:after method="after" pointcut-ref="pt3"/> <!-- aop:around用于配置环绕通知 --> <aop:around method="around" pointcut-ref="pt4"/> </aop:aspect> <aop:aspect ref="logAspect2" order="1"> <aop:before method="before3" pointcut-ref="pt1" /> <aop:before method="before4" pointcut-ref="pt1" /> </aop:aspect> </aop:config> ``` ### JDBC Template的CRUD操作有哪些? | 方法 | 说明 | |-------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| | public int update(String sql) | 用于执行新增、更新、删除等语句;sql:需要执行的 SQL 语句;args 表示需要传入到 SQL 语句中的参数。 | | public int update(String sql,Object... args) | | | public void execute(String sql) | 可以执行任意 SQL,一般用于执行 DDL 语句; sql:需要执行的 SQL 语句;action 表示执行完 SQL 语句后,要调用的函数。 | | public T execute(String sql, PreparedStatementCallback action) | | | public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args) | 用于执行查询语句;sql:需要执行的 SQL 语句;rowMapper:用于确定返回的集合(List)的类型;args:表示需要传入到 SQL 语句的参数。 | | public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) | | | public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) | 用于批量执行新增、更新、删除等语句; sql:需要执行的 SQL 语句;argTypes:需要注入的 SQL 参数的 JDBC 类型;batchArgs:表示需要传入到 SQL 语句的参数。 | ### 如何创建处理全局异常? 局部异常 ```java //可以在控制器类中添加方法,用于处理异常,这个方法就叫做局部异常处理程序 //这样该方法可以处理这个控制器类里发生的异常 //异常处理程序使用@ExceptionHandler注解修饰,说明这个方法用于处理异常 //value属性用于指定处理的异常类型,多个类型使用一个数组给出 //在异常处理程序中,可以跳转到某个错误页面,返回一个字符串指定视图名称即可 //在异常处理程序中,可以使用Model等对象返回一些数据给页面 //如果希望获取异常信息,可以在方法上添加一个对应异常类型(如ArithmeticException)的参数 //也可使用Throwable来接收所有类型的异常对象 @ExceptionHandler({ArithmeticException.class,NumberFormatException.class}) public String handleException(Model model, Throwable ex) { //可以考虑使用Log4J2等日志框架将异常记录到日志 logger.error("发生异常,异常消息: "+ex.getMessage()); //如果需要返回一些数据显示在页面,可以添加到Model对象中 model.addAttribute("msg", "局部异常处理程序,异常消息: "+ex.getMessage()); return "error"; //指定要跳转的视图的名称 } ``` 全局异常 ```java //全局异常处理程序所在的类也要成为spring mvc容器管理的bean //@Controller、@Service、@Repository、@Component //@RestController、@Configuration、@ControllerAdvice //@ControllerAdvice也会将一个类注册为spring管理的bean //@ControllerAdvice作用很多,其中一个作用是修饰包含全局异常处理程序类 //这样,需要开启对这个类所在的包的扫描,但是这里我们直接将类放到了com.qdu.controller的子包 //com.qdu.controller.advice包下,所以不需要额外开启包扫描 @ControllerAdvice public class GlobalExceptionHandler { private static Logger logger=LoggerFactory.getLogger(GlobalExceptionHandler.class); //添加一个方法,作为异常处理程序 //方法需要使用@ExceptionHandler注解进行修饰 //可以考虑处理异常后,跳转到一个友好的错误页面,如果需要,也可在错误页面显示一些信息 //如果同时定义了全局异常处理和局部异常处理程序,那么会使用局部异常处理程序 @ExceptionHandler({ArithmeticException.class}) public String handleException(Model model, ArithmeticException e) { logger.error("全局异常处理程序:程序发生异常,异常消息-"+e.getMessage()); model.addAttribute("msg", "全局异常处理程序,异常消息: "+e.getMessage()); return "error"; } //异常处理程序前也可使用@ResponseBody注解,让返回的内容成为响应正文内容显示 //而不是要跳转的页面的名称 @ExceptionHandler({IOException.class,ArrayIndexOutOfBoundsException.class}) @ResponseBody public String exceptionHandler2(Throwable ex) { return "Exception occurred, exception message: "+ex.getMessage(); } } ``` ### JDBC Template 和 NamedParameterJDBCTemplate的区别 JDBC Template:最基本的JDBC模板,支持基于索引参数(`?`)的查询 NamedParameterJdbcTemplate:使用该模板类执行查询的时候使用命名参数的方式绑定到SQL而不是索引参数 ### 事务的特征? 事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。 - 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。 - 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。 - 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。 - 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。 ### 事务的传播行为? 事务传播行为(propagation behavior)指的是,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。 Spring 提供了以下 7 种不同的事务传播行为。 | 名称 | 说明 | |---------------------------|--------------------------------------------------------------| | PROPAGATION_MANDATORY | 支持当前事务,如果不存在当前事务,则引发异常。 | | PROPAGATION_NESTED | 如果当前事务存在,则在嵌套事务中执行。 | | PROPAGATION_NEVER | 不支持当前事务,如果当前事务存在,则引发异常。 | | PROPAGATION_NOT_SUPPORTED | 不支持当前事务,始终以非事务方式执行。 | | PROPAGATION_REQUIRED | 默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。 | | PROPAGATION_REQUIRES_NEW | 创建新事务,如果已经存在事务则暂停当前事务。 | | PROPAGATION_SUPPORTS | 支持当前事务,如果不存在事务,则以非事务方式执行。 | ### 事务并发可能导致的问题? 事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。 在实际应用中,经常会出现多个事务同时对同一数据执行不同操作,来实现各自的任务的情况。此时就有可能导致脏读、幻读以及不可重复读等问题的出现。 在理想情况下,事务之间是完全隔离的,这自然不会出现上述问题。但完全的事务隔离会导致性能问题,而且并不是所有的应用都需要事务的完全隔离,因此有时应用程序在事务隔离上也有一定的灵活性。 Spring 中提供了以下隔离级别,我们可以根据自身的需求自行选择合适的隔离级别。 | 方法 | 说明 | |----------------------------|----------------------------------------------| | ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 | | ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读 | | ISOLATION_READ_COMMITTED | Oracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读 | | ISOLATION_REPEATABLE_READ | MySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读 | | ISOLATION_SERIALIZABLE | 完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读 | ### PlatformTransactionManager接口的作用? Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。 在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下。 ```java public interface PlatformTransactionManager extends TransactionManager { TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; } ``` 该接口中各方法说明如下: | 名称 | 说明 | |--------------------------------------------------------------------|-------------| | TransactionStatus getTransaction(TransactionDefinition definition) | 用于获取事务的状态信息 | | void commit(TransactionStatus status) | 用于提交事务 | | void rollback(TransactionStatus status) | 用于回滚事务 | Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。 | 实现类 | 说明 | |----------------------------------------------------------------------------|---------------------------------------------| | org.springframework.<br/>jdbc.datasource.<br/>DataSourceTransactionManager | 使用 Spring JDBC 或 iBatis 进行持久化数据时使用。 | | org.springframework.<br/>orm.hibernate3.<br/>HibernateTransactionManager | 使用 Hibernate 3.0 及以上版本进行持久化数据时使用。 | | org.springframework<br/>.orm.jpa.<br/>JpaTransactionManager | 使用 JPA 进行持久化时使用。 | | org.springframework<br/>.jdo.<br/>JdoTransactionManager | 当持久化机制是 Jdo 时使用。 | | org.springframework.<br/>transaction.<br/>jta.JtaTransactionManager | 使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。 | 这些事务管理器的使用方式十分简单,我们只要根据持久化框架(或平台)选用相应的事务管理器实现,即可实现对事物的管理,而不必关心实际事务实现到底是什么。 ### 如何使用XML配置事务? #### 1. 引入 tx 命名空间 Spring 提供了一个 tx 命名空间,借助它可以极大地简化 Spring 中的声明式事务的配置。 想要使用 tx 命名空间,第一步就是要在 XML 配置文件中添加 tx 命名空间的约束。 ```xml <?xml version="1.0" encoding="UTF-8"?> <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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> ``` > **注意:**由于 Spring 提供的声明式事务管理是依赖于 Spring AOP 实现的,因此我们在 XML 配置文件中还应该添加与 aop 命名空间相关的配置。 #### 2. 配置事务管理器 接下来,我们就需要借助数据源配置,定义相应的事务管理器实现(PlatformTransactionManager 接口的实现类)的 Bean,配置内容如下。 ```xml <!--配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!--数据库连接地址--> <property name="url" value="xxx"/> <!--数据库的用户名--> <property name="username" value="xxx"/> <!--数据库的密码--> <property name="password" value="xxx"/> <!--数据库驱动--> <property name="driverClassName" value="xxx"/> </bean> <!--配置事务管理器,以 JDBC 为例--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> ``` 在以上配置中,配置的事务管理器实现为 DataSourceTransactionManager,即为 JDBC 和 iBatis 提供的 PlatformTransactionManager 接口实现。 #### 3. 配置事务通知 在 Spring 的 XML 配置文件中配置事务通知,指定事务作用的方法以及所需的事务属性。 ```xml <!--配置通知--> <tx:advice id="tx-advice" transaction-manager="transactionManager"> <!--配置事务参数--> <tx:attributes> <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/> </tx:attributes> </tx:advice> ``` ##### 事务管理器配置 当我们使用 <tx:advice> 来声明事务时,需要通过 transaction-manager 参数来定义一个事务管理器,这个参数的取值默认为 transactionManager。 如果我们自己设置的事务管理器(第 2 步中设置的事务管理器 id)恰好与默认值相同,则可以省略对改参数的配置。 ```xml <tx:advice id="tx-advice" > <!--配置事务参数--> <tx:attributes> <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/> </tx:attributes> </tx:advice> ``` 但如果我们自己设置的事务管理器 id 与默认值不同,则必须手动在 <tx:advice> 元素中通过 transaction-manager 参数指定。 ##### 事务属性配置 对于<tx:advice> 来说,事务属性是被定义在<tx:attributes> 中的,该元素可以包含一个或多个 <tx:method> 元素。 <tx:method> 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。 | 事务属性 | 说明 | |-----------------|-------------------------------------------------------------| | propagation | 指定事务的传播行为。 | | isolation | 指定事务的隔离级别。 | | read-only | 指定是否为只读事务。 | | timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 | | rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 | | no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 | ### 4. 配置切点切面 <tx:advice> 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 <tx:advice> 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。 在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。 ```xml <!--配置切点和切面--> <aop:config> <!--配置切点--> <aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor> </aop:config> ``` ### 如何启用注解驱动的事务编程模型? #### 1. 开启注解事务 tx 命名空间提供了一个 <tx:annotation-driven> 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。 <tx:annotation-driven> 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。 ```xml <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> ``` 与 <tx:advice> 元素一样,<tx:annotation-driven> 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。 ```xml <tx:annotation-driven/> ``` 通过 <tx:annotation-driven> 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。 #### 2. 使用 @Transactional 注解 @Transactional 注解是 Spring 声明式事务编程的核心注解,该注解既可以在类上使用,也可以在方法上使用。 ```java @Transactional public class XXX { @Transactional public void A(Order order) { …… } public void B(Order order) { …… } } ``` 若 @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。 Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。 @Transactional 注解包含多个属性,其中常用属性如下表。 | 事务属性 | 说明 | |-----------------|-------------------------------------------------------------| | propagation | 指定事务的传播行为。 | | isolation | 指定事务的隔离级别。 | | read-only | 指定是否为只读事务。 | | timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 | | rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 | | no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 | ### SQLSessionFactory的创建与使用? ```java public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { InputStream is = Resources.getResourceAsStream("config/mybatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); sqlSessionFactory = builder.build(is); } catch (IOException ex) { ex.printStackTrace(); } } public static SqlSessionFactory getSqlSessionFactory(){ return sqlSessionFactory; } public static SqlSession openSession(){ return sqlSessionFactory.openSession(); } } ``` ### 如何使用Java类替换web.xml? web.xml对应的配置 ```java public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // spring的配置 @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { SpringConfig.class }; } // springMVC的配置 @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { SpringMvcConfig.class }; } // servlet配置 @Override protected String[] getServletMappings() { return new String[] { "/" }; } // servlet过滤器 @Override protected Filter[] getServletFilters() { return new Filter[] { new CharacterEncodingFilter("utf-8",true,true) }; } } ``` springApplication.xml相应的配置 ```java @Configuration //开启对service组件所在包的扫描 @ComponentScan(basePackages= {"com.qdu.service"}) //指定要使用的属性文件的位置,src目录下的文件要添加classpath:作为前缀 @PropertySource({"classpath:config/jdbc.properties"}) //开启对Mapper接口所在包的扫描,这样mybatis-spring可以帮你发现映射器 //帮你创建Mapper接口的对象,也就是映射器实例 @MapperScan(basePackages= {"com.qdu.mapper"}) //启用注解驱动的编程模型,这样可以通过使用@Transactional注解来实现事务 @EnableTransactionManagement public class SpringConfig { //注入一个Environment对象,用于读取属性文件中的属性 @Autowired Environment env; //1. 配置数据源 @Bean public DruidDataSource dataSource() { //属性文件中属性的值都是字符串,所以如果需要转换类型,需要手动转换 DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(env.getProperty("jdbc.driver")); dataSource.setUrl(env.getProperty("jdbc.url")); dataSource.setUsername(env.getProperty("jdbc.username")); dataSource.setPassword(env.getProperty("jdbc.password")); dataSource.setInitialSize(Integer.parseInt(env.getProperty("initialSize"))); dataSource.setMaxActive(Integer.parseInt(env.getProperty("maxActive"))); dataSource.setMaxWait(Integer.parseInt(env.getProperty("maxWait"))); dataSource.setMinIdle(Integer.parseInt(env.getProperty("minIdle"))); dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(env.getProperty("timeBetweenEvictionRunsMillis"))); dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(env.getProperty("minEvictableIdleTimeMillis"))); dataSource.setValidationQuery(env.getProperty("validationQuery")); dataSource.setTestWhileIdle(Boolean.parseBoolean(env.getProperty("minEvictableIdleTimeMillis"))); dataSource.setTestOnBorrow(Boolean.parseBoolean(env.getProperty("testOnBorrow"))); dataSource.setTestOnReturn(Boolean.parseBoolean(env.getProperty("testOnReturn"))); return dataSource; } //2. 配置SqlSessionFactory @Bean public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception { //通过SqlSessionFactoryBean来创建SqlSessionFactory对象 SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean(); //用传入的DataSource对象设置为依赖的数据源 factoryBean.setDataSource(ds); //指定要使用别名的类型所在的包,这样在mybatis映射文件等文件中可以使用类型的别名 factoryBean.setTypeAliasesPackage("com.qdu.entity"); //返回创建的SqlSessionFactory对象,成为spring容器管理的bean return factoryBean.getObject(); } //3. 配置事务管理器 @Bean public DataSourceTransactionManager txManager(DataSource ds) { return new DataSourceTransactionManager(ds); } } ``` springMVC.xml对应的配置 ```java @Configuration @ComponentScan(basePackages = {"com.qdu.controller"}) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/index").setViewName("index"); } @Bean public SpringResourceTemplateResolver templateResolver() { SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); templateResolver.setTemplateMode(TemplateMode.HTML); templateResolver.setCacheable(false); templateResolver.setCharacterEncoding("UTF-8"); return templateResolver; } @Bean public SpringTemplateEngine templateEngine() { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); templateEngine.setEnableSpringELCompiler(true); return templateEngine; } @Bean public ThymeleafViewResolver viewResolver() { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(templateEngine()); viewResolver.setCharacterEncoding("UTF-8"); return viewResolver; } } ``` ### 如何集成SSM? #### java配置类配置ssm 见上条 #### xml配置ssm web.xml ```xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:config/spring-config.xml</param-value> </context-param> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:config/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <session-config> <session-timeout> 30 </session-timeout> </session-config> </web-app> ``` spring-config ```xml <beans 略> <!-- 1. 开启包扫描 --> <!-- 因为后面使用MapperScannerConfigurer这个类的配置开启了对Mapper接口所在包的扫描 --> <!-- 而且我们已经没有Mapper的实现类了,所以这里不需要扫描mapper包了 --> <context:component-scan base-package="com.qdu.service" /> <!-- 2. 指定要加载的属性文件的位置,可以指定多个,逗号隔开 --> <context:property-placeholder location="classpath:config/jdbc.properties" /> <!-- 3. 配置数据源,这里使用alibaba的DruidDataSource --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClass}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="initialSize" value="${initialSize}" /> <property name="maxActive" value="${maxActive}" /> <property name="maxWait" value="${maxWait}" /> <property name="minIdle" value="${minIdle}" /> <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${validationQuery}" /> <property name="testWhileIdle" value="${testWhileIdle}" /> <property name="testOnBorrow" value="${testOnBorrow}" /> <property name="testOnReturn" value="${testOnReturn}" /> </bean> <!-- 4. 配置SqlSessionFactoryBean,告知spring如何创建SqlSessionFactory对象 --> <!-- 并将创建的SqlSessionFactory交给spring容器管理,然后可以在需要的地方依赖注入 --> <!-- 这里class指定的SqlSessionFactoryBean,这是一个工厂bean,负责生产某种类型的对象 --> <!-- 它负责创建SqlSessionFactory对象并交给spring管理该对象 --> <!-- 这里的id是它创建的SqlSessionFactory这个bean的id,而不是SqlSessionFactoryBean的id --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 如果还需要使用mybatis配置文件,可以使用configLocation属性指定其位置 --> <!-- <property name="configLocation" value="classpath:mybatis-config.xml" /> --> <!-- typeAliasesPackage属性用于指定实体类所在的包,这样在mybatis映射文件中可以使用别名来使用实体类 --> <property name="typeAliasesPackage" value="com.qdu.entity" /> <!-- mapperLocations属性用于指定mybatis映射文件的位置 --> <!-- <property name="mapperLocations" value="classpath:com/qdu/mapper/**/*.xml" /> --> </bean> <!-- 5. 配置MapperScannerConfigurer,扫描Mapper接口所在的包,这样可以发现映射器 --> <!-- 可以不用一个个注册映射器,而是直接指定Mapper接口所在的包 --> <!-- 让spring扫描Mapper接口所在的包,根据每个Mapper接口创建对应的Mapper对象,也就是映射器实例 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 需要使用basePackage属性指定Mapper接口所在的包 这样映射器会自动被创建,在需要的地方直接注入映射器(Mapper实例)即可--> <property name="basePackage" value="com.qdu.mapper" /> </bean> <!-- 6. spring集成mybatis应该将事务交给spring管理 --> <!-- 6.1 配置事务管理器,事务管理器提供方法操作事务 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 事务管理器和SqlSessionFactoryBean使用的数据源必须是同一个 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 6.2 配置事务通知 --> <!-- 配置事务通知其实就是配置通知的bean,但是不使用bean标记 --> <!-- 而是使用tx:advice标记简化事务配置,需要指定通知bean的id和依赖的事务管理器--> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- tx:method标记用于指定每个要加事务的方法如何应用事务 --> <!-- 查询操作可加事务,可不加事务,但是如果查询操作可能受并发事务影响 --> <!-- 那就要考虑加事务 --> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="update*" /> <tx:method name="delete*" /> <tx:method name="transfer*" /> <!-- 如果查询加了事务,最好使用只读事务,方便优化查询 --> <tx:method name="get*" read-only="true" /> </tx:attributes> </tx:advice> <!-- 6.3 配置事务通知器,指定事务通知应用到什么切入点 --> <aop:config> <aop:pointcut expression="within(com.qdu.service..*)" id="pt"/> <!-- 配置通知器,指定哪个通知应用到哪个切入点 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" /> </aop:config> </beans> ``` spring-mvc ```xml <?xml version="1.0" encoding="UTF-8"?> <beans 略> <!--1. 开启包扫描,扫描控制器所在包 --> <context:component-scan base-package="com.qdu.controller" /> <!--2. 启用注解驱动的控制器编程模型,也就是启用Web MVC配置 --> <mvc:annotation-driven /> <!--3. 配置Thymeleaf相关的bean --> <!-- 配置模板解析器 --> <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <property name="prefix" value="/WEB-INF/templates/" /> <property name="suffix" value=".html" /> <!-- templateMode指定Thymeleaf处理的模板类型 --> <property name="templateMode" value="HTML" /> <!-- cacheable指定是否可以缓存模板页面内容 开发的时候设置为false,实际发布程序可以设置为true 设置true的话,页面被缓存,如果更改了页面内容,刷新页面不会改变 设置false的话,如果更改了页面内容,刷新页面内容会更改 --> <property name="cacheable" value="false" /> <property name="characterEncoding" value="UTF-8" /> </bean> <!-- 配置模板引擎 --> <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver" ref="templateResolver" /> <!-- enableSpringELCompiler属性设置为true是为了提高性能 --> <!-- 只有考虑向后兼容的时候才会设置为false --> <property name="enableSpringELCompiler" value="true" /> </bean> <!-- 配置Thymeleaf视图解析器 --> <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <property name="characterEncoding" value="UTF-8" /> </bean> <!-- 4. 处理静态资源 --> <mvc:resources mapping="/static/**" location="/static/" /> <!-- 5. 配置跳转到首页 --> <mvc:view-controller path="/" view-name="index" /> <mvc:view-controller path="/index" view-name="index" /> </beans> ``` ### 如何配置Spring Security? 1.添加启动依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2.配置spring security ```java @Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } //注入要使用的UserDetailsService对象 @Autowired private CustomUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //指定要使用UserDetailsService对象,从而实现从数据库加载用户信息(用户名、密码和角色/授权) //并指定要使用的密码加密器 auth .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/img/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/index", "/toLogin", "/login_failed").permitAll() .antMatchers("/user/**").hasRole("user") .antMatchers("/admin/**").hasAnyRole("admin", "sadmin") .antMatchers("/sadmin/**").hasRole("sadmin") .anyRequest().authenticated() .and() .formLogin() .loginPage("/toLogin") .failureUrl("/login_failed") .and() .rememberMe() .rememberMeCookieName("remember") .rememberMeParameter("rememberMe") .tokenValiditySeconds(7 * 24 * 60 * 60) .and() .httpBasic() .and() .csrf().disable(); // http.httpBasic(); // http.csrf().disable(); } } ``` 3.自定义security功能 ```java //如果使用spring security验证用户,用户的信息来自数据库 //可以创建一个UserDetailsService类来指定如何加载用户信息 //需要将其注册为spring管理的bean,在需要的地方依赖注入即可 @Component public class CustomUserDetailsService implements UserDetailsService{ @Autowired private UserInfoMapper mapper; //loadUserByUsername()方法中查询用户信息,封装成验证要用的用户信息 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //如果需要spring security进行用户的验证和授权 //spring security需要知道用户的用户名、密码和角色 //这里根据传入的用户名(直接使用即可,会帮你传入输入的用户名) //根据用户名查询用户的信息,主要是为了获取密码 UserInfo userinfo=mapper.selectByPrimaryKey(username); //如果查不到该用户,抛出一个用户名找不到异常 if(null==userinfo) throw new UsernameNotFoundException("无此用户"); //根据用户名查询用户所属的角色列表 List<String> roles=mapper.selectRoleNamesById(username); //用户角色列表需要封装成一个List<GrantedAuthority>列表 List<GrantedAuthority> authorities=new ArrayList<>(); //遍历每个角色名称,将每个角色名称封装成一个GrantedAuthority对象 //添加到列表中,表示对用户的一项授权,如果多个角色,则有多项授权 for(String role:roles) { //角色名必须要添加ROLE_前缀,这是spring security的工作机制决定的 authorities.add(new SimpleGrantedAuthority("ROLE_"+role)); } //最终要返回一个UserDetails对象,封装了验证和授权用的用户名、密码和授权信息 //UserDetails是一个接口,可以使用其实现类User return new User(userinfo.getUid(),userinfo.getUpassword(),authorities); } } ``` ## spring MVC 12问 ### MVC模式的概念? MVC 设计模式一般指 MVC 框架,M(Model)指数据模型层,V(View)指视图层,C(Controller)指控制层。使用 MVC 的目的是将 M 和 V 的实现代码分离,使同一个程序可以有不同的表现形式。其中,View 的定义比较清晰,就是用户界面。 ### Spring MVC包含的组件及其作用? #### 1)DispatcherServlet DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。 #### 2)HandlerMapping HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。 #### 3)HandlerAdapter HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。 #### 4)Handler Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。 #### 5)View Resolver View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。 #### 6)View View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。 以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面。 ### Spring MVC处理请求的过程? ![Spring MVC执行流程](./ssm/1139441444-0.png) SpringMVC 的执行流程如下。 1. 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器); 2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。 3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器); 4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller); 5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息); 6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ; 7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析; 8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet; 9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图); 10. 视图负责将结果显示到浏览器(客户端)。 ### servlet合法的URL模式? servlet的url模式合法的写法有: - 精准匹配 - ./index.jsp - 扩展名匹配 - `*.xxx` 匹配以xxx结尾的请求url - 路径匹配 - `/xxx/*` 用于匹配路径包含xxx的所有请求,xxx可以是一级或者多级 - `/*` 匹配以/开头,任意结尾的请求url。 - 缺省匹配 - `/` 匹配除JSP之外的所有请求 ### 如何配置DispatcherServlet? xml配置 ```xml <!--web.xml--> <web-app> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 如果不指定<context-param>, 将会默认加载/WEB-INF/applicationContext.xml文件.--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/app-context.xml</param-value> </context-param> <servlet> <servlet-name>app</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>app</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app> ``` Java配置:实现WebApplicationInitializer接口 ```java public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletCxt) { // 加载spring 容器 AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(AppConfig.class); ac.refresh(); // 创建并注册DispatcherServlet DispatcherServlet servlet = new DispatcherServlet(ac); ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/app/*"); } } ``` ### 如何配置Spring MVC配置文件的位置? Spring MVC 配置:在 web.xml 中配置 Servlet,创建 Spring MVC 的配置文件 ```xml <!-- 配置 SpringMVC 的前端控制器,对浏览器发送的请求统一进行处理 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置 DispatcherServlet 的一个初始化参数:spring mvc 配置文件按的位置和名称--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMVC.xml</param-value> </init-param> <!--作为框架的核心组件,在启动过程中有大量的初始化操作要做 而这些操作放在第一次请求时才执行会严重影响访问速度 因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时--> <load-on-startup>1</load-on-startup> </servlet> ``` ### Spring和Spring MVC配置文件的默认名称? springMVC配置文件默认名称:`<servlet-name>-servlet.xml` servlet-name是在web.xml`<servlet>`标签中定义的 spring配置文件名称:`applicationContext.xml` ### 如何配置视图解析器? in *-.servlet ```xml <?xml version="1.0" encoding="UTF-8"?> <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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--开启组件扫描--> <context:component-scan base-package=""/> <!-- 配置 Thymeleaf 视图解析器 --> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="order" value="1"/> <property name="characterEncoding" value="UTF-8"/> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀 --> <property name="prefix" value="/WEB-INF/templates/"/> <!-- 视图后缀 --> <property name="suffix" value=".html"/> <property name="templateMode" value="HTML5"/> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> </beans> ``` 视图解析器(ViewResolver)是 Spring MVC 的重要组成部分,负责将逻辑视图名解析为具体的视图对象。 Spring MVC 提供了很多视图解析类,其中每一项都对应 Java Web 应用中特定的某些视图技术。下面介绍一些常用的视图解析类。 #### URLBasedViewResolver UrlBasedViewResolver 是对 ViewResolver 的一种简单实现,主要提供了一种拼接 URL 的方式来解析视图。 UrlBasedViewResolver 通过 prefix 属性指定前缀,suffix 属性指定后缀。当 ModelAndView 对象返回具体的 View 名称时,它会将前缀 prefix 和后缀 suffix 与具体的视图名称拼接,得到一个视图资源文件的具体加载路径,从而加载真正的视图文件并反馈给用户。 使用 UrlBasedViewResolver 除了要配置前缀和后缀属性之外,还需要配置“viewClass”,表示解析成哪种视图。示例代码如下 ```xml <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--不能省略--> <!--前缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean> ``` #### InternalResourceViewResolver InternalResourceViewResolver 为“内部资源视图解析器”,是日常开发中最常用的视图解析器类型。它是 URLBasedViewResolver 的子类,拥有 URLBasedViewResolver 的一切特性。 InternalResourceViewResolver 能自动将返回的视图名称解析为 InternalResourceView 类型的对象。InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。也就是说,使用 InternalResourceViewResolver 视图解析时,无需再单独指定 viewClass 属性。示例代码如下。 ```xml <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--可以省略--> <!--前缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean> ``` ### 创建和使用控制器? @Controller 注解用于声明某类的实例是一个控制器。例如,创建控制器类 IndexController,示例代码如下 ```java @Controller public class IndexController { // 处理请求的方法 } ``` Spring MVC 使用扫描机制找到应用中所有基于注解的控制器类,所以,为了让控制器类被 Spring MVC 框架扫描到,需要在配置文件中声明 spring-context,并使用 `<context:component-scan/>` 元素指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下)。 例如,在 springmvcDemo 应用的配置文件 springmvc-servlet.xml 中添加以下代码: ```xml <!-- 使用扫描机制扫描控制器类,控制器类都在net.biancheng.controller包及其子包下 --> <context:component-scan base-package="com.example.controller" /> ``` ### 如何映射不同的请求? 在基于注解的控制器类中可以为每个请求编写对应的处理方法。使用 @RequestMapping 注解将请求与处理方法一 一对应即可。 @RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。 @RequestMapping 注解常用属性如下。 #### 1. value 属性 value 属性是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称。如下。 ```java @RequestMapping(value="toUser") //or @RequestMapping("toUser") ``` value 属性支持通配符匹配,如 `@RequestMapping(value="toUser/*")` 表示 http://localhost:8080/toUser/1 或 http://localhost:8080/toUser/hahaha 都能够正常访问。 #### 2. path属性 path 属性和 value 属性都用来作为映射使用。即 `@RequestMapping(value="toUser")` 和 `@RequestMapping(path="toUser")` 都能访问 toUser() 方法。 path 属性支持通配符匹配,如 `@RequestMapping(path="toUser/*")` 表示 http://localhost:8080/toUser/1 或 http://localhost:8080/toUser/hahaha 都能够正常访问。 #### 3. name属性 name属性相当于方法的注释,使方法更易理解。如 `@RequestMapping(value = "toUser",name = "获取用户信息")`。 #### 4. method属性 method 属性用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。 `@RequestMapping(value = "toUser",method = RequestMethod.GET)`表示该方法只支持 GET 请求。 也可指定多个 HTTP 请求,如 `@RequestMapping(value = "toUser",method = {RequestMethod.GET,RequestMethod.POST})`,说明该方法同时支持 GET 和 POST 请求。 #### 5. params属性 params 属性用于指定请求中规定的参数,代码如下。 ```java @RequestMapping(value = "toUser",params = "type") public String toUser() { return "showUser"; } ``` 以上代码表示请求中必须包含 type 参数时才能执行该请求。即 http://localhost:8080/toUser?type=xxx 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser 则不能正常访问 toUser() 方法。 ```java @RequestMapping(value = "toUser",params = "type=1") public String toUser() { return "showUser"; } ``` 以上代码表示请求中必须包含 type 参数,且 type 参数为 1 时才能够执行该请求。即 http://localhost:8080/toUser?type=1 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser?type=2 则不能正常访问 toUser() 方法。 #### 6. header属性 header 属性表示请求中必须包含某些指定的 header 值。 `@RequestMapping(value = "toUser",headers = "Referer=http://www.xxx.com")` 表示请求的 header 中必须包含了指定的“Referer”请求头,以及值为“http://www.xxx.com”时,才能执行该请求。 #### 7. consumers属性 consumers 属性用于指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html。如 `@RequestMapping(value = "toUser",consumes = "application/json")`。 #### 8. produces属性 produces 属性用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型。如 @RequestMapping(value = "toUser",produces = "application/json")。 除此之外,produces 属性还可以指定返回值的编码。如 `@RequestMapping(value = "toUser",produces = "application/json,charset=utf-8")`,表示返回 utf-8 编码。 使用 @RequestMapping 来完成映射,具体包括 4 个方面的信息项:请求 URL、请求参数、请求方法和请求头。 ### 如何配置静态资源? ```xml <mvc:resources mapping="/js/**" location="/js/"></mvc:resources> <mvc:resources mapping="/css/**" location="/css/"></mvc:resources> <mvc:resources mapping="/img/**" location="/img/"></mvc:resources> <!-- 在配置了mvc:resources标签之后必须配置mvc:annotation-driven标签静态资源 才可以访问,否则不仅静态资源不能访问,其他的所有请求也都无法正常处理了 --> <mvc:annotation-driven/> ``` ### 如何构建Restful API? REST(Representational State Transfer)即表述性转移,是目前最流行的一种软件架构风格。它结构清晰、易于理解、有较好的扩展性。 Spring REST 风格可以简单理解为:使用 URL 表示资源时,每个资源都用一个独一无二的 URL 来表示,并使用 HTTP 方法表示操作,即准确描述服务器对资源的处理动作(GET、POST、PUT、DELETE),实现资源的增删改查。 - GET:表示获取资源 - POST:表示新建资源 - PUT:表示更新资源 - DELETE:表示删除资源 # Spring Boot 65问 ## 框架22问 ### 启动依赖是什么? Spring Boot就可以指定基于功能依赖。Spring Boot通过起步依赖为项目的依赖管理提供帮助。如果应用程序是Web应用程序(功能),不需要向项目pom.xml文件中添加一堆单独的依赖,可以直接向项目中添加Web起步依赖。如果应用程序需要用到JPA持久化(功能),加入jpa起步依赖;如果需要安全功能(功能),就加入security起步依赖。添加依赖时不需要指定依赖的版本号,依赖的版本号由当前是使用的Spring Boot版本号来决定。 起步依赖就是特殊的Maven依赖,利用了传递依赖解析,把常用库聚合在一起,组成几个为特定功能而定制的依赖。Spring Boot通过起步依赖:直接引入相关起步依赖就行,我们不需要考虑支持某种功能需要什么库, 减少了依赖数量,而且不需要考虑这些库的那些版本。如果我们需要什么功能,就往项目中加入该功能的起步依赖就好了。 ### Actuator的作用? Actuator用于监视和管理应用程序 监控的内容: - Spring应用程序上下文中配置的Bean - Spring Boot的自动配置做的决策 - 应用程序可用的环境变量、系统属性、配置属性和命令行参数等 - 应用程序里线程的当前状态 - 应用程序最近处理的HTTP请求的追踪情况 - 各种和内存使用、垃圾回收、Web请求等相关的指标 ### Spring Boot 配置依赖? ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> ``` ### 如何修饰启动类? ```xml <!--排除依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> </exclusion> </exclusions> </dependency> ``` ```xml <!--用指定依赖版本 覆盖 传递依赖--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.4.3</version> </dependency> ``` ### @SpringBootApplication是哪三合一? @SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。 @Configuration:建一个简单的spring配置类,可以用来替代相应的xml配置文件,见词条 @Configuration和@Bean注解的作用 @EnableAutoConfiguration:能够自动配置spring的上下文,试图猜测和配置你想要的bean类,通常会自动根据你的类路径和你的bean定义自动配置 @ComponentScan:会自动扫描指定包下的全部标有@Component的类,并注册成bean,当然包括@Component下的子注解@Service,@Repository,@Controller ### 如何排除指定的自动配置? ```java @SpringBootApplication(exclude={RedisAutoConfiguration.class}) ``` ### 一个启动类能否同时作为控制器类? 能 ### 如何自定义banner? 将`banner.txt`(或`banner,jpg`)放入`src/main/resources` ### SpringBoot如何实现自动配置? 加载spring.factories ### SpringBoot如何配置视图解析器? **配置文件** ```properties spring.mvc.view.prefix=/pages/ spring.mvc.view.suffix=.html ``` 或 ```yaml spring: mvc: view: prefix: /pages/ suffix: .html ``` **配置类** ```java @Configuration public class MvcConfig implements WebMvcConfigurer { @Bean public InternalResourceViewResolver configureInternalResourceViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/pages/"); resolver.setSuffix(".html"); return resolver; } } ``` ### 如何配置端口号? ```properties server.port=8081 ``` 或 ```yaml server: port: 8081 ``` ### properties与yml的异同? <mark>properties的优先级会高于yml</mark> yml采用树形结构,更有层次感,可读性很强;相反,properties 则更为直接 properties 的基本语法格式是“key=value”的形式;yml 的基本语法格式是“key: value”的形式,冒号后面需要加空格 ### 静态资源默认位置在哪? classpath:/META-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/ ### 如何自定义静态资源的位置? **在配置文件中配置** ```properties #静态资源访问路径 spring.mvc.static-path-pattern=/upload/** #静态资源映射路径 spring.resources.static-locations=classpath:/upload/ ``` **配置类** ```java @Configuration public class MvcConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) {     // 这里之所以多了一"/",是为了解决打war时访问不到问题 registry.addResourceHandler("/**").addResourceLocations("/","classpath:/"); } } ``` ### 有哪些常用的JSON解析器? 1)json-lib json-lib 最早也是应用广泛的 JSON 解析工具,缺点是依赖很多的第三方包 对于复杂类型的转换,json-lib 在将 JSON 转换成 Bean 时还有缺陷,比如一个类里包含另一个类的 List 或者 Map 集合,json-lib 从 JSON 到 Bean 的转换就会出现问题。 #### 2)开源的Jackson 开源的 Jackson 是 Spring MVC 内置的 JSON 转换工具。 但是 Jackson 对于复杂类型的 JSON 转换 Bean 会出现问题 #### 3)Google的Gson Gson 是目前功能最全的 JSON 解析神器 Gson 完全可以将复杂类型的 JSON 到 Bean 或 Bean 到 JSON 的转换,是 JSON 解析的神器。Gson 在功能上面无可挑剔,但性能比 FastJson 有所差距。 #### 4)阿里巴巴的FastJson FastJson 是用 Java 语言编写的高性能 JSON 处理器,由阿里巴巴公司开发。 FastJson 在复杂类型的 Bean 转换 JSON 上会出现一些问题,可能会出现引用的类型,导致 JSON 转换出错,需要制定引用。 ### 为哪些JSON解析器提供了自动配置? Gson 、Jackson 、JSON-B ### 如何使用fastjson? 导入依赖 ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.15</version> </dependency> ``` 在springMVC的配置类中配置JSON解析器为fastJSON ```java //通过配置HttpMessageConverter来配置使用的JSON处理器 //每个JSON处理器都有对应的消息转换器实现类,用于实现java对象和json对象的转换 @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter converter=new FastJsonHttpMessageConverter(); //设置转换器的字符集编码,解决中文乱码 converter.setDefaultCharset(Charset.forName("UTF-8")); //因为spring boot没有提供fastjson的集成和自动配置,所以不能在application.properties //中配置fastjson的属性 //创建一个FastJsonConfig对象,定制fastjson的配置 FastJsonConfig config=new FastJsonConfig(); config.setDateFormat("yyyy-MM-dd"); //setSerializerFeatures()用于指定序列化后json数据的特征 //WriteMapNullValue: 是否输出值为null的字段,默认为false //WriteNullStringAsEmpty: null字符串是否显示为空字符串,而不是null //WriteNullNumberAsZero: 是否将null的数值显示为0,而不是null //WriteNullListAsEmpty: 是否将null的List显示为[],而不是null //DisableCircularReferenceDetect: 禁用对同一个对象的循环引用,如果使用Hibernate等框架映射了关系,可能会出现循环引用 config.setSerializerFeatures( SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullNumberAsZero, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.DisableCircularReferenceDetect ); //应用fastjson配置 converter.setFastJsonConfig(config); converters.add(converter); for(HttpMessageConverter c:converters) System.out.println(c.getClass()); //打印一下转换器的类型 } ``` 直接用。。。 ### 如何自定义错误界面的位置? **静态** 在 resource/static 目录下创建 error 目录,然后在 error 目录中创建错误展示页面。错误展示页面命名规则有如下两种: 一种是直接使用具体的响应码命名文件,比如:404.html、405.html、500.html 另一种可以使用 4xx.html、5xx.html 这样的命名 **动态** 在 resource/templates 目录下创建 error 目录,然后在 error 目录中创建错误展示页面。 ### 错误界面使用的顺序如何? 优先级: 动态错误码>静态错误码>动态模糊错误码>静态模糊错误码 找到一个后便不往下寻找 ### 如何定义局部异常handler? ```java @ExceptionHandler(value = {java.lang.ArithmeticException.class}) public ModelAndView arithmeticExceptionHandler(Exception e){ ModelAndView mv = new ModelAndView(); mv.addObject("errorMsg",e); mv.setViewName("error"); return mv; } ``` ### 如何定义全局异常handler? ``` //创建一个全局异常处理程序 //全局异常处理程序所在的类使用@ControllerAdvice或@RestControllerAdvice注解修饰 //@ControllerAdvice注解作用很多,其中一个作用是修饰全局异常处理程序所在的类 @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler({ ArithmeticException.class }) public String handleException(Exception ex, Model model) { // 在此收集异常信息,比如将异常信息写入日志等 model.addAttribute("msg", "全局异常处理程序,异常消息:"+ex.getMessage()); return "err"; // 处理完异常,可以跳转到指定的页面/视图 } //异常处理程序对应的方法前可以使用@ResponseBody注解,将返回值作为响应内容返回 @ExceptionHandler({ArrayIndexOutOfBoundsException.class}) @ResponseBody public Map<String,Object> handleException2(Exception e) { Map<String,Object> map=new HashMap<>(); map.put("status", 500); map.put("msg", "数组索引出界,索引: "+e.getMessage()); map.put("reason", "索引值超出数组长度"); return map; } } ``` ### 如何使用RestController? 如果在类上加上@RestController,该类中的所有SpringMVCUrl接口映射都是返回json格式 ```java @RestController public class hello{ //something } ``` ## Thymeleaf 11问 ### Thymeleaf如何使用变量表达式页面获取不同范围的属性? ```html 变量表达式${.....} <p> <!-- 能够直接获取model的属性值--> user1属性值: <span th:text="${user1.id}"></span> | <span th:text="${user1.password}" ></span> | <span th:text="${user1.gender}" ></span> </p> <p> user2属性值: <!-- 会话范围的属性需要添加session的使用 --> <span th:text="${session.user2.id}"></span> | <span th:text="${session.user2.password}" ></span> | <span th:text="${session.user2.gender}" ></span> </p> ``` ### Thymeleaf的五种表达式? - 变量表达式${.....} - 选择变量表达式*{.....} - 选择变量表达式主要是为了简化书写 - th:object属性用于绑定一个对象,这样在当前元素(p元素)内可以直接使用该对象的属性 - 选择变量表达式用于使用外围绑定的对象的属性,以简化书写 - 消息表达式#{.....} - 消息表达式用于显示消息,主要是来自属性文件的属性,可用于实现国际化等功能 - {属性名}来显示来自属性文件的一个消息 - URL链接变量表达式@{.....} - 用于指定一个链接,指定一个资源的url - 片段(fragment)表达式~{.....} - 页面名称::片段名称 ### 如何使用选择变量表达式? ```html <p th:object="${user1}"> user1属性值: <span th:text="*{id}"></span> | <span th:text="*{password}" ></span> | <span th:text="*{gender}" ></span> </p> ``` ### 链接表达式的四种写法? ```html <div class="content"> <a th:href="@{https://www.baidu.com}" class="btn btn-sm btn-danger">测试1-绝对路径</a> &nbsp; <!-- 请求/页面相对路径:直接写资源的路径 --> <!-- 使用相对路径,要注意404问题 --> <a th:href="@{static/img/lxh01.gif}" class="btn btn-sm btn-warning">测试2-页面/请求相对路径</a> &nbsp; <!-- 上下文相对路径,以/开头,表示相对程序的上下文路径 --> <a th:href="@{/static/img/lxh02.gif}" class="btn btn-sm btn-success">测试3-上下文相对路径</a> &nbsp; <!-- 服务器相对路径,以~/开头,表示相对服务器的路径 --> <a th:href="@{~/static/img/lxh03.gif}" class="btn btn-sm btn-primary">测试4-服务器相对路径</a> &nbsp; <!-- 协议相对路径: 以//开头,表示相对协议的路径 --> <a th:href="@{//static/img/lxh04.gif}" class="btn btn-sm btn-info">测试5-协议相对路径</a> &nbsp; </div> ``` ### 怎么定义和引用片段? **定义** ```html <div th:fragment="f1" id="frag1" class="bg-primary padding10 margin10"> 这是模板片段1内容~~~~~~~~~~~~~~~~~~~<br> 这是模板片段1内容~~~~~~~~~~~~~~~~~~~<br> 这是模板片段1内容~~~~~~~~~~~~~~~~~~~<br> </div> ``` **引用** ```html <div class="content"> <!-- th:insert用于插入一个模板片段,比如下面的代码表示将f1片段内容插入div1 --> <!-- 页面名称::片段名称 --> <div id="div1" th:insert="~{footer::f1}"></div> <!-- th:replace用于使用指定的模板片段替换原有内容,比如下面的代码表示使用f1的片段内容替换div2 --> <div id="div2" th:replace="~{footer::f1}"></div> <!-- 也可通过页面名称::#id通过元素的id来使用一个片段 --> <div id="div3" th:insert="~{footer::#frag2}"></div> <div id="div4" th:insert="~{footer}"></div> <!-- 使用当前页面定义的片段,可不加页面名称或使用this --> <div id="div5" th:insert="~{::frag3}"></div> <div id="div5" th:insert="~{this::#fragment3}"></div> </div> ``` ### 哪些字面标记不合法? 字面标记值类似文本字面值: 区别是不使用单引号括起来值, 只能允许字母、数字、下划线、中划线、点号、中括号, 不能包含逗号、空格以及其他特殊字符 ```html 字面标记:<span th:text="Anna"></span><br> 字面标记:<span th:text="Annabelle_Lee"></span><br> 字面标记:<span th:text="www.baidu.com"></span><br> 字面标记:<span th:text="a-b_c.d"></span><br> <!-- 空格不允许 --> <!-- 字面标记:<span th:text="[kite runner]"></span><br> --> <!-- 逗号不允许 --> <!-- 字面标记:<span th:text="a,b"></span><br> ``` ### th:*属性的作用? th:* 修改单个属性的值 ```html <!-- 绝大部分html属性都有对应的th:*属性,在th:*属性中才能使用Thymeleaf的语法 --> <div class="content"> <p> <!--显示user5的头像,鼠标悬浮显示其id--> <img th:src="'static/img/'+${user5.img}" th:title="${user5.id}" src="static/img/lxh01.gif" title="小胖" width="60" height="60"> <img th:src="@{'/static/img/'+${user5.img}}" th:title="${user5.id}" src="static/img/lxh01.gif" title="小胖" width="60" height="60"> </p> </div> ``` th:attr 修改一个标签多个属性的值 ```html <div class="content"> <p> <!--显示user5的头像,鼠标悬浮显示其id--> <img th:attr="src=@{'/static/img/'+${user5.img}},title=${user5.id}" src="static/img/lxh01.gif" title="小胖" width="60" height="60"> </p> </div> ``` th:object 绑定一个对象,方便在子标记中使用选择变量表达式 ```html <div class="content"> <p th:object="${user5}"> user5属性值: <span th:text="*{id}"></span> | <span th:text="*{password}"></span> | <span th:text="*{gender}"></span> | <span th:text="*{img}"></span> </p> </div> ``` th:if 判断一个条件,条件成立,显示标记内容 ```html <div class="content"> <p th:if="${age}>=18">恭喜你,成为大人了!!!</p> </div> ``` th:switch+th:case 类似switch-case结构作用的属性 ```html <div class="content"> <!-- th:switch通常用于判断一个表达式的值 --> <div th:switch="${type}"> <p th:case="1">冰箱</p> <p th:case="2">空调</p> <p th:case="3">电视</p> <!-- *表示其他情况 --> <p th:case="*">其他</p> </div> </div> ``` th:each 类似foreach循环 ```html <div class="content"> <table class="table table-striped table-hover text-center"> <tr> <th>学号</th> <th>姓名</th> <th>性别</th> </tr> <!--1)遍历列表--> <tr th:each="s:${studentList}"> <!-- th:属性名写法 --> <td th:text="${s.sid}"></td> <!-- data-th-属性名写法 --> <td data-th-text="${s.sname}"></td> <!-- 内联表达式写法 --> <td>[[${s.sgender}]]</td> </tr> </table> <!--2)获取状态信息--> <!-- s是一个变量名,可以随意命名,表示当前遍历的元素 --> <!-- stat是一个变量名,可以随意命名,用于获取当前遍历的元素的状态信息 --> <div th:each="s,stat:${studentList}" th:class="${stat.odd}?'bg-danger':'bg-success'"> [[${s.sid}]] | [[${s.sname}]] | <!-- index:获取当前元素的索引,从0开始 --> [[${stat.index}]] | <!-- count: 获取当前元素的序号,也就是第几个元素 --> [[${stat.count}]] | <!-- size: 获取集合大小 --> [[${stat.size}]] | <!-- first: 返回布尔值,表示是否是第一项元素 --> [[${stat.first}]] | <!-- last: 返回布尔值,表示是否是最后一项元素 --> [[${stat.last}]] | <!-- odd: 返回布尔值,表示是否是奇数项元素 --> [[${stat.odd}]] | <!-- even: 返回布尔值,表示是否是偶数项元素 --> [[${stat.even}]] | </div> <!--3)遍历Map集合--> <hr> <!-- 遍历Map集合,通过key获取键,通过value获取值 --> <div th:each="entry,status:${map1}"> [[${entry.key}]] | [[${entry.value}]] | [[${status.index}]] </div> <!-- 遍历map2集合,打印键和值的各项信息 --> <!-- value如果不是简单类型,可继续获取具体属性 --> </div> ``` th:text和th:utext ```html <div class="content"> <!-- th:text如果内容是html内容,则不会解析html内容,作为文本显示 --> <p th:text="${content1}"></p> <!--th:text等效写法:内联表达式写法--> [[${content1}]] <!-- th:utext如果内容是html内容,会解析html内容,显示解析后的内容 --> <p th:utext="${content2}"></p> <!--th:utext等效写法:内联表达式写法--> [(${content2})] </div> ``` th:with 用于定义局部变量 ```html <!-- th:with用于定义局部变量,可以定义一个或多个局部变量 --> <!-- 定义的变量只能在标记内使用 --> <div th:with="firstStudent=${studentList[0]}"> [[${firstStudent.sid}]] | [[${firstStudent.sname}]] | [[${firstStudent.sgender}]] </div> <!--定义多个变量--> <div th:with="a=10,b=20,c=30"> [[${a+b+c}]] </div> ``` th:inline 启用和禁用内联表达式 ```html <div th:inline="none"> [[1+2+3]] <br> [(1+2+3)] </div> <div> <!-- 如果需要在js代码中使用内联表达式,可将inline属性设置为javascript --> <script th:inline="javascript"> document.write([[${user5.id}]]); document.write("<br>"); document.write([[${age}]]); </script> </div> <div> <!-- 默认,可以在css样式中使用内联表达式 --> <!-- 如果设置值是none,css中的内联表达式不会被解析 --> <style th:text="css"> .bg-red{ color: [[${color}]]; } </style> <p class="bg-red">测试样式</p> </div> ``` ### Thymeleaf的名称空间? Thymeleaf提供一些名称空间,用于获取对应的信息。每个名称空间都对应一个Map集合,存储了对应的属性、参数或其他信息 ```html <div class="content"> <!-- 不使用任何名称空间就是请求范围的属性 --> <!--获取请求范围属性--> <p th:text="${num1}"></p> <p th:text="${num2}"></p> <!--获取会话范围属性--> <p th:text="${session.num3}"></p> <!--获取应用程序范围属性--> <p th:text="${application.num4}"></p> <!--获取一个请求参数的单个值--> <p th:text="${param.name}"></p> <!--获取一个请求参数的多个值--> <p th:text="${param.hobbies}"></p> <!--获取一个请求参数的多个值中的某个值,加上数组索引即可--> <p th:text="${param.hobbies[0]}"></p> </div> ``` ### 有哪些基本对象? Thymeleaf 中常用的内置基本对象如下: - ctx :上下文对象 - vars :上下文变量 - locale:上下文的语言环境 - request:HttpServletRequest 对象(仅在 Web 应用中可用) - response:HttpServletResponse 对象(仅在 Web 应用中可用) - session:HttpSession 对象(仅在 Web 应用中可用) - servletContext:ServletContext 对象(仅在 Web 应用中可用) ### 表达式工具对象如何使用? #### numbers:数字工具对象 ```html formatDecimal()方法: 用于格式化小数数值 <div class="content"> <!-- 这里的三个参数分别表示:要格式化的值,至少保留几位整数,保留几位小数 --> <p th:text="${#numbers.formatDecimal(num1,1,3)}"></p> </div> ``` formatCurrency()方法:用于格式化金额,会加货币符号 ```html <p th:text="${#numbers.formatCurrency(num1)}"></p> ``` sequence()方法: 用于产生一个整数序列/数组, 可以通过产生一个整数序列实现普通循环,如打印分页的页码 ```html <!-- 可以指定两个参数,表示from和to,最小值和最大值 --> <!-- 可以指定三个参数,表示from、to和step,最小值、最大值和步长 --> <span th:each="i:${#numbers.sequence(1,10)}"> | <a th:href="'findByPageNo?pageNo='+${i}">[[${i}]]</a> | </span> ``` #### strings:字符串工具对象 ```html <div class="content"> <!-- 检查一个字符串内容是否为空 --> <p th:text="${#strings.isEmpty('')}"></p> <p th:text="${#strings.isEmpty('anna')}"></p> <!-- 获取字符串长度 --> <p th:text="${#strings.length(bookName)}"></p> <!-- 截取子字符串 从索引为0的字符开始,截取5-0个字符--> <p th:text="${#strings.substring(bookName,0,5)}"></p> <!-- 检查字符串是否包含指定内容 --> <p th:text="${#strings.contains(bookName,'kite')}"></p> </div> ``` #### dates:日期工具对象 ```html <!-- 格式化日期时间 --> <p th:text="${#dates.format(now)}"></p> <!-- 格式化的时候可指定显示格式 --> <p th:text="${#dates.format(now,'yyyy-MM-dd HH:mm:ss D E')}"></p> <!-- 获取日期时间的指定部分, 如年份、月份、日、月份名称、星期几等 --> <p th:text="${#dates.year(now)}"></p> <p th:text="${#dates.month(now)}"></p> <p th:text="${#dates.day(now)}"></p> <p th:text="${#dates.monthName(now)}"></p> <p th:text="${#dates.dayOfWeek(now)}"></p> <p th:text="${#dates.hour(now)}"></p> ``` #### lists/sets:List/Set 集合工具对象 ```html <div class="content"> <!-- #lists对象用于操作List类型的数据,比如sort()方法可用于对集合排序--> <span th:each="name:${#lists.sort(nameList)}"> [[${name}]] | </span> <p th:text="${#lists.sort(scoreList)}"></p> </div> ``` #### aggregates ```html <!-- #aggregates用于对数组或集合进行汇总运算,如求和或求平均 --> <div class="content"> <p th:text="${#aggregates.avg(scoreList)}"></p> <p th:text="${#aggregates.sum(scoreList)}"></p> </div> ``` #### maps:Map 集合工具对象 常用的方法有:size、isEmpty、containsKey 和 containsValue 等 ## Spring Boot JPA 13问 ### SpringBoot支持的三种数据源是什么? HikariCP:默认内置数据源对象 Tomcat提供DataSource:HikariCP不可用的情况下,在web环境中,将tomcat服务器配置的数据源对象。 Commons DBCP:HikariCP不可用,tomcat数据源也不可用,将使用dbcp数据源。 ### 如何配置数据源? 导入依赖项 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> ``` ```yaml spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8 username: root password: root jpa: hibernate: ddl-auto: update show-sql: true ``` ### SpringDataJPA是什么? spring data jpa是spring提供的一套简化JPA开发的框架,按照约定好的【方法命名规则】写dao层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作。同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等。 ### SpringData JPA 需不需要JPA提供程序? 需要 Spring Data JPA 可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现。 ### JPA个各个属性都有什么作用? ```properties # 指定是否在控制台打印生成的sql语句,以方便跟踪程序执行和调试程序,默认值为false spring.jpa.show-sql=true # 指定是否格式化在控制台打印的sq|语句 spring.jpa.properties.hibernate.format_sql=true # 指定使用的方言,是一个类的名称,根据操作的数据库选择 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57InnoDBDialect # 指定程序运行时是否自动生成、更新或删除数据库表 spring.jpa.hibernate.ddl-auto=update ``` ddl-auto的有效值有: - create: 每次加载hibernate时,先删除已存在的数据库表结构再重新生成 - create-drop:每次加载hibernate时,先删除已存在的数据库表结构再重新生成,并且当sessionFactory关闭时自动删除生成的数据库表结构: - update: 只在第一次加载hibernate时自动生成数据库表结构,以后再次加载hibernate时根据 model类自动更新表结构 - validate:每次加载hibermate时,验证数据库表结构,仅仅和数据库中的表进行比较,不会 创建新表,但是会插入新值 - none:禁用DDL操作 ### 有哪些常用的注解? - @Entity定义对象将会成为被JPA管理的实体,将映射到指定的数据库表。 - @Table指定数据库的表名。 - @Id定义属性为数据库的主键,一个实体里面必须有一个。 - @IdClass利用外部类的联合主键。 - @GeneratedValue为主键生成策略 - @Basic表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为@Basic。 - @Transient表示该属性并非一个到数据库表的字段的映射,表示非持久化属性,与@Basic作用相反。JPA映射数据库的时候忽略它。 - @Column定义该属性对应数据库中的列名。 - @Temporal用来设置Date类型的属性映射到对应精度的字段。 - @Lob 将属性映射成数据库支持的大对象类型,支持以下两种数据库类型的字段。 关联关系注解 - @JoinColumn定义外键关联的字段名称 - @OneToOne关联关系 - @OneToMany与@ManyToOne可以相对存在,也可只存在一方。 - @ManyToMany表示多对多,和@OneToOne、@ManyToOne一样也有单向、双向之分。单向双向和注解没有关系,只看实体类之间是否相互引用。 ### Repository接口和他的派生类型? **Repository**: 是 spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法 仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别 **CrudRepository**: 继承 Repository,实现了一组 CRUD 相关的方法 **PagingAndSortingRepository**: 继承 CrudRepository,实现了一组分页排序相关的方法 **JpaRepository**: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法 ### 如何构建简单的条件查询? ```java //resposity List<Student> findByUserDepUuid(String uuid); //框架在解析该方法时,流程如下: /* 首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc 先判断 userDepUuid(根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性, 如果是,则表示根据该属性进行查询;如果没有该属性,继续往下走 从右往左截取第一个大写字母开头的字符串(此处为Uuid), 然后检查剩下的字符串是否为查询实体的一个属性,如果是, 则表示根据该属性进行查询;如果没有该属性,则重复这一步,继续从右往左截取; 最后假设 user 为查询实体的一个属性 接着处理剩下部分(DepUuid), 先判断 user 所对应的类型是否有depUuid属性, 如果有,则表示该方法最终是根据 "Doc.user.depUuid" 的取值进行查询;否则继续按照步骤3的规则从右往左截取, 最终表示根据 "Doc.user.dep.uuid" 的值进行查询。 */ //以下是条件查询中可用的关键字 //5. 构建条件查询-根据性别和班级查询学生列表 //如果多个参数,则参数顺序和方法名称要对应起来,比如这里第一个参数必须是班级 List<Student> findBySbatchAndSgender(String batchName, String gender); //6. 构建条件查询-根据学号或姓名查询学生 List<Student> findBySidOrSname(String id, String name); //7. 构建条件查询-根据两个学号查询学号介于该区间的学生的列表 List<Student> findBySidBetween(String start, String end); //8. 构建条件查询-查询学号小于指定学号的学生列表 List<Student> findBySidLessThan(String id); //9. 构建条件查询-查询学号小于等于指定学号的学生列表 List<Student> findBySidLessThanEqual(String id); //10. 构建条件查询-查询学号大于指定学号的学生列表 List<Student> findBySidGreaterThan(String id); //11. 构建条件查询-查询学号大于等于指定学号的学生列表 List<Student> findBySidGreaterThanEqual(String id); //12. 构建条件查询-查询班级为null的学生的列表 List<Student> findBySbatchNull(); //13. 构建条件查询-查询学号包含指定关键字的学生列表 List<Student> findBySidLike(String id); //14. 构建条件查询-查询名字包含指定关键字的学生列表 List<Student> findBySnameLike(String name); //15. 构建条件查询-查询名字以指定字符串结尾的学生列表 List<Student> findBySnameEndingWith(String name); ``` ```java //server //根据主键查找查找时 public Product get(Long id) { //findById()返回的是一个Optional对象,需要进一步调用get()返回实际数据对象 return repo.findById(id).get(); } @Override public List<Student> getStudentListByUuid(String Uuid) { return studentRepository.findByUuid(Uuid); } ``` ### 如何使用@Query查询? ```java // 1. 查询id最大的员工的信息 // 使用@Query注解定义JPQL查询 // JPQL(Java Persistence Querying Language-Java持久化语言)或者叫做JPA Querying // Language-操作的是对象和对象的属性 // Native Query-本地/原生SQL语句-操作的是表和表中的列 // select emp相当于查询员工对象的所有属性,emp.属性名 表示查询对应的属性 @Query("select emp from Employee emp where id=(select max(id) from Employee)") Employee query1(); // 2. 根据性别和部门编号查询员工列表 // 使用占位符参数 ?X 表示查询参数 // Employee类中通过dept属性映射了关系,但是Employee类中没有deptId属性 // 所以如果需要使用deptId属性,需要写dept.deptId @Query("select emp from Employee emp where gender=?1 and dept.deptId=?2") List<Employee> query2(String gender, Integer deptId); // 3. 根据性别和部门编号查询员工列表 // 使用命名参数 :参数名 // 可通过@Param注解指定查询参数的名称 @Query("select emp from Employee emp where gender=:gen and dept.deptId=:did") List<Employee> query3(@Param("gen") String gender, @Param("did") Integer deptId); // 4. 查询姓名包含指定关键字和指定性别的员工 // Spring Data支持在占位符参数两端添加%的使用 @Query("select emp from Employee emp where name like %?1% and gender=?2") List<Employee> query4(String name, String gender); // 5. 查询姓名包含指定关键字和指定性别的员工 // Spring Data支持在命名参数两端添加%的使用 @Query("select emp from Employee emp where name like %:name% and gender=:gender") List<Employee> query5(@Param("name") String name, @Param("gender") String gender); // 6. 查询所有员工 // JPQL: 操作的是对象和对象的属性 // 原生SQL: 操作的是表和表中的列 // 使用本地查询/原生SQL语句,需要设置nativeQuery属性为true // Spring Data JPA不仅仅支持JPQL语句,也支持原生SQL语句 @Query(value = "select * from emp", nativeQuery = true) List<Employee> query6(); // 7. 联接表查询指定列 // 查询指定列,可以考虑将每行数据封装到一个Object[]数组 @Query("select e.id,e.name,e.gender,d.deptId,d.deptName from Employee e, Department d where e.dept.deptId=d.deptId") List<Object[]> query7(); ``` ### 如何进行增删改? 直接调用JPA给定的 CRUD 方法 ```java @Override public void addStudent(Student student) { studentRepository.save(student); } @Override public void updateStudent(Student student) { studentRepository.save(student); } @Override public void deleteStudent(String id) { studentRepository.deleteById(id); } ``` 或者在repository层中定义Query ```java // 8. 修改指定员工的姓名 // 需要使用@Modifying注解和@Transactional注解 // @Query("select emp from Employee emp") // 如果执行的是一个增删改操作,则必须添加@Modifying和@Transactional注解的使用 // @Transactional注解建议加到Service层的方法上,有时候一个Service功能可能涉及多个Dao的调用,需要作为一个事务执行 @Modifying @Query("update Employee set name=:name where id=:id") void update1(@Param("id") Integer id, @Param("name") String name); // 9. 添加一个新员工 // 需要使用@Modifying注解和@Transactional注解 // JPQL不支持insert语句 @Modifying @Query(value = "insert into emp(name,gender,dept_id) values(:name,:gender,:deptId)", nativeQuery = true) void insert(@Param("name") String name, @Param("gender") String gender, @Param("deptId") Integer deptId); // 10. 刪除一个员工 // 需要使用@Modifying注解和@Transactional注解 @Modifying @Query("delete Employee where id=?1") void delete(Integer id); ``` ### 如何声明事务? ```java //例 @Transactional(propagation=Propagation.SUPPORTS, readOnly=true) public List<PayInfo> findAll() { return payInfoDao.findAll(); } ``` | 属性 | 类型 | 描述 | |-------------------------|------------------------------|----------------------| | value | String | 可选的限定描述符,指定使用的事务管理器 | | propagation | enum: Propagation | 可选的事务传播行为设置 | | isolation | enum: Isolation | 可选的事务隔离级别设置 | | readOnly | boolean | 读写或只读事务,默认读写 | | timeout | int (in seconds granularity) | 事务超时时间设置 | | rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 | | rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 | | noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 | | noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 | ### 如何配置mapper映射文件的位置? 在配置文件中 ```properties mybatis.mapper-locations=classpath:com/qdu/mapper/**/*.xml ``` ### 如何开启对mapper包的扫描? 在启动类中 ```java @MapperScan("com.qdu.mapper") ``` ## 消息传递 10问 ### 消息传递的概念? 消息传递是一个或多个实体之间进行通信的一-种方式,它无处不在。自计算机发明以来,以一种或 另一种形式进行的计算机消息传递就已经存在。它被定义为硬件和/或软件组件或应用程序之间的通 信方法。总会有一个发送者和一个或多个接收者。消息传递可以是同步和异步,发布/订阅, RPC 基于企业的消息传递,ESB (企业服务总线),MOM (面向消息的中间件)等等。 ### JMS的概念? JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。 ### 点对点模型有什么特点? 1. 一个消息由一个<b>消息生产者(producer)</b>传送给一个<b>消息使用者(consumer)</b>。</a></li> 2. 消息由生产者发送到<b>队列(Queue)</b>目的地,然后传送给在该队列上注册了的<b>消息使用者之一 </b>。</a></li> 3. 任意多数量的消息生产者都可以发送消息同一个队列,每条消息都可以确保被传送成功,且<b>每条消息仅由一个消息使用者收到和使用</b>。</a></li> 4. 如果没有消息使用者注册接受队列中的消息,则<b>队列保留该消息</b>,直到有使用者读取该消息,一旦读取,该消息便不在队列中,不可再有其他使用者读取。</a></li> ### 发布订阅模型的特点? 1. 一个消息由一个<b>消息生产者(Producer/Publisher)</b>传送给<b>任意数量的消息使用者(Consumer/Subscriber)</b>。</a></li> 2. 消息由生产者发送到<b>主题(Topic)</b>目的地, 然后由<b>订阅了该主题的活跃消息使用者读取和使用</b>。</a></li> 3. 任意数量的消息生产者可以发送消息到<b>主题</b>目的地,<b>每条消息</b>传送给<b>任意数量订阅了该主题的消息使用者</b>。</a></li> 4. 如果没有订阅该主题的消息使用者,则Topic目的地不会保留该消息(除非有非活跃使用者进行了持久性订阅),后续订阅该主题的消息使用者也不会再收到使用者之前发的消息。</a></li> 5. 一个持久性订阅表示注册了该主题的使用者可以在生产者发送消息的时候处于非活跃状态,这样,使用者可以在变成活跃状态时收到之前发送的消息。</a></li> ### ActiveMQ是什么? ActiveMQ是一种开源的基于JMS(Java Message Servie)规范的一种消息中间件的实现,ActiveMQ的设计目标是提供标准的、面向消息的、能够跨越多语言和多系统的应用集成消息通信中间件。 它为企业应用中消息传递提供高可用、出色性能、可扩展、稳定和安全保障。 ActiveMQ实现JMS规范并在此之上提供大量额外的特性。ActiveMQ支持队列和订阅两种模式的消息发送。 ### 如何启动ActiveMQ? ```shell cd <activeMQ_dir>/bin ./activemq start ``` ### JMS创建什么链接实例? `javax.jms.Connection` (由于使用SpringBoot,此处不再赘述) ### 如何配置ActiveMQ? 首先配置properties文件 ```properties #activemq通讯地址 spring.activemq.broker-url=tcp://localhost:61616 #用户名 spring.activemq.user=admin #密码 spring.activemq.password=admin #是否启用内存模型(就是不安装mq,项目启动时同时启动一个mq实例) spring.activemq.in-memory=false #信任所有包 spring.activemq.packages.trust-all=true #是否替换默认的连接池,使用activemq的连接池需引入依赖 spring.activemq.pool.enabled=false ``` 然后配置activeMQ ```java @Configuration @EnableJms public class ActiveMQConfig { @Bean public Queue queue() { return new ActiveMQQueue("springboot.queue") ; } //springboot默认只配置queue类型消息,如果要使用topic类型的消息,则需要配置该bean @Bean public JmsListenerContainerFactory jmsTopicListenerContainerFactory(ConnectionFactory connectionFactory){ DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); //这里必须设置为true,false则表示是queue类型 factory.setPubSubDomain(true); return factory; } @Bean public Topic topic() { return new ActiveMQTopic("springboot.topic") ; } } ``` 或者在properties中配置 ```propeties # 使用点对点模型 spring.jms.pub-sub-domain=false ``` 配置文件的其他配置: ```properties # 是否信任所有包 spring.activemq.packages.trust-all= # 要信任的特定包的逗号分隔列表(当不信任所有包时) spring.activemq.packages.trusted= # 当连接请求和池满时是否阻塞。设置false会抛“JMSException异常”。 spring.activemq.pool.block-if-full=true # 如果池仍然满,则在抛出异常前阻塞时间。 spring.activemq.pool.block-if-full-timeout=-1ms # 是否在启动时创建连接。可以在启动时用于加热池。 spring.activemq.pool.create-connection-on-startup=true # 是否用Pooledconnectionfactory代替普通的ConnectionFactory。 spring.activemq.pool.enabled=false # 连接过期超时。 spring.activemq.pool.expiry-timeout=0ms # 连接空闲超时 spring.activemq.pool.idle-timeout=30s # 连接池最大连接数 spring.activemq.pool.max-connections=1 # 每个连接的有效会话的最大数目。 spring.activemq.pool.maximum-active-session-per-connection=500 # 当有"JMSException"时尝试重新连接 spring.activemq.pool.reconnect-on-exception=true # 在空闲连接清除线程之间运行的时间。当为负数时,没有空闲连接驱逐线程运行。 spring.activemq.pool.time-between-expiration-check=-1ms # 是否只使用一个MessageProducer spring.activemq.pool.use-anonymous-producers=true ``` ### @JMSListener有什么用? ```java //该注解用于将一个方法设置为监听端点,用于接收消息 //destination用于指定接收消息的目的地,也就是消息来自哪个目的地,给出其名称 @JmsListener(destination="queue2") public void receiveMsg1(String msg) { System.out.println("监听端点1收到消息,消息内容:"+msg); } ``` ### 如何使用发布订阅模型? ```properties # 设置为ture使用发布订阅模型, i.e spring.jms.pub-sub-domain=true ``` ## 测试 9问 ### 单元测试与集成测试? 见[软件工程第十一章单元测试与集成测试部分](https://charlesix59.github.io/2023/02/07/subject/software_project/chapter11/#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95) ### Junit5的三个组件是什么? JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage - JUnit Platform: 是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。 - JUnit Jupiter: 提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。 - JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,其提供了兼容JUnit4.x,Junit3.x的测试引擎。 ### Junit的常用注解? @Test :表示方法是测试方法 ```java @Test void contextLoads() { } ``` @DisplayName :为测试类或者测试方法设置展示名称 ```java @DisplayName("测试displayname注解") @Test void testDisplayName() { System.out.println(1); } ``` @BeforeEach:表示在每个单元测试之前执行 @AfterEach:表示在每个单元测试之后执行 @BeforeAll:表示在所有单元测试之前执行 @AfterAll:表示在所有单元测试之后执行 ```java @BeforeEach void testBeforeEach() { System.out.println("测试就要开始。。。"); } @AfterEach void testAfterEach() { System.out.println("测试就要结束。。。"); } @BeforeAll static void testBeforeAll() { System.out.println("所有测试就要开始。。。"); } @AfterAll static void testAfterAll() { System.out.println("所有测试已经结束。。。"); } ``` @Disabled :表示测试类或测试方法不执行 ```java @Disabled @Test void test2() { System.out.println(2); System.out.println(jdbcTemplate.getClass()); } ``` @Timeout :表示测试方法运行如果超过了指定时间将会返回错误 ```java @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) void testTimeOut() throws InterruptedException { Thread.sleep(520); } ``` @RepeatedTest :表示方法可重复执行 ```java @RepeatedTest(5) //重复测试5次 @DisplayName("测试3") @Test void test3() { System.out.println(3); System.out.println(jdbcTemplate.getClass()); } ``` @ParameterizedTest :表示方法是参数化测试 @Tag :表示单元测试类别 @ExtendWith :为测试类或测试方法提供扩展类引用 ### Junit的测试方法与注意事项? 就这么测。。。 注意注意就行了。。。 ### Test依赖包含的库有哪些? ![img.png](./ssm/dependencies.png) ### 如何测试Spring Boot程序? @SpringBootTest注解:添加在需要依赖spring boot框架的测试类上, 不然不能使用Spring boot的相关开发功能 @Test注解:添加在测试方法上 ```java @SpringBootTest class Boot05WebAdminApplicationTests { @Test void contextLoads() { } } ``` ### MockMvc是什么? MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。 ### @WebMvcTest有什么用? ```java //该注解的使用实现针对spring mvc组件的切片测试,这样只加载mvc相关的配置的即可 @WebMvcTest(controllers= {HelloController.class}) ``` 如果使用@SpringBootTest来修饰一个测试类, 则会加载所有配置,并注册相关的bean成为spring管理的bean, 包括service组件、mapper组件、数据源等 但是如果测试只涉及部分组件,如控制器,不需要使用service或mapper等组件, 可以使用切片测试对应的注解来实施测试,这样只加载需要的配置即可 比如使用@WebMvcTest,实现对spring mvc组件的切片测试, 这样就不能注入service等组件,因为不会加载service等的配置

操作系统复习 第一章

# 第一章 引论 **操作系统:** - 目标: - 方便性 - 有效性 - 提高系统资源利用率 - 提高系统吞吐量 - 可拓展性 - 开放性 - 作用: - 作为用户与计算机硬件系统之间的接口 - 程序接口 - 命令接口 - GUI - 作为计算机系统资源的管理者 - 处理机管理 - 存储器管理 - I/O设备管理 - 文件管理 - 实现了对计算机资源的抽象 - 将具体的计算机硬件资源抽象为软件资源,方便用户使用和拓展 - 开放了简单的访问方式,隐藏了实现细节 - 发展动力 - 不断提高计算机资源利用率 - 方便用户 - 器件的不断更新迭代 - 计算机体系结构的不断发展 - 不断提出新的应用需求 ## 计算机的发展历程 ### 人工操作方式 - 优点 - 用户独占全机 - CPU等待人工操作 - 缺点 - 计算机资源浪费 - 效率低 - CPU与I/O设备之间速度不匹配 ### 脱机输入输出方式 - 优点 - 解决了人际矛盾 - 减少了CPU的空闲时间 - 提高了I/O速度 - 缺点 - 一次只能执行一个程序 ### 单道批处理系统 批处理是指计算机系统对一批作业自动进行处理的一种技术。 为实现对作业的连续处理,需要先把一批作业以脱机方式输入到磁带上,并在系统中配上监督程序(Monitor) ,在它的控制下,使这批作业能一个接一地连续处理 - 优点 - 自动性 - 顺序性 - 单道性 - 缺点 - 内存中只有一道程序 - CPU需要等待I/O完成 ### 多道批处理系统 在多道批处理系统中,用户所提交的作业都先存放在外存上并排成一个队列,称为“后备队列”; 然后,由作业调度程序按一定的算法从后备队列中选择若干个作业调入内存,使它们共享CPU和系统中的各种资源。 - 优点 - 提高CPU的利用率 - 可提高内存I/O设备利用率 - 增加系统吞吐量 - 缺点 - 平均周转周期长 - 无人机交互 ### 分时系统 采用时间片轮转的方法,同时为许多终端用户服务,对每个用户能保证足够快的响应时间,并提供交互会话的功能。 时间片:将CPU的时间划分成若干个片段,称为时间片,操作系统以时间片为单位,轮流为每个终端用户服务关键问题 - 特征 - 多路性:时间片轮转机制 - 独立性:用户彼此独立 - 及时性:用户能在短时间内获得响应 - 交互性:用户可以请求多种服务 #### 实时任务 - 按照是否呈现周期性划分 - 周期性实时任务 - 非周期性实时任务 - 开始截止时间 - 完成截止时间 - 对截止时间的要求的划分 - 硬实时任务 - 软实时任务 ### 微机操作系统 微型计算机操作系统 微型计算机操作系统简称微机操作系统,常用的有Windows、Mac OS、Linux。 ## 基本特征 ### <mark>并发性</mark> **并行性**是指两个或多个事件在同一时间发生 **并发性**是指两个或多个事件在同一事件间隔发生 - 宏观上,处理机同时处理多道程序 - 微观上,处理机在多道程序间高速切换 **进程**( process )是指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。 **线程**( threads )通常在一个进程中可以包含若干个线程, 一般把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。 ### <mark>共享性</mark> 资源共享,即系统中的资源多个“并发执行”的应用程序共同使用 - 互斥共享方式:多个程序在同一个共享资源上独立而互不干扰的工作 - 又叫临界资源/独占资源 - 同时访问方式:同一时段允许多个程序同时访问共享资源 并发和共享互为存在条件 - 共享性要求OS中同时运行着多道程序若只有单道程序正在运行,则不存在共享的可能 - 并发性难以避免的导致多道程序同时访问同一个资源,若多道程序无法共享部分资源(比如磁盘) ,则无法并发 ### 虚拟性 使用某种技术把一个物理尸体变成多个逻辑上的对应物 - 时分复用技术( TDM , Time Division Multiplexing ) - 虚拟处理机技术:利用多道程序设计技术,为每道程序建立一个进程,让多道程序并发执行,以此来分时使用一台处理机。 - 虚拟设备技术:将一台物理I/O设备虚拟为多台逻辑上的I/O设备,并允许每个用户占用一台逻辑上的I/O设备。 - 空分复用技术( SDM , Space Division Multiplexing ) - 虚拟存储器技术 ### 异步性 - 多道程序环境下,允许多个程序并发执行;单处理机环境下,多个程序分时交替执行; - 程序执行的不可预知性 - 获得运行的时机 - 因何暂停 - 每道程序需要多少时间 - 不同程序的性能,比如计算多少, I/O多少 - 宏观上“一气呵成”,微观上“走走停停” - 每个程序在何时执行,多个程序间的执行顺序以及完成每道程序所需的时间都是不确 定和不可预知的。进程是以人们不可预知的速度向前推进,此即进程的异步性。 ## 主要功能 ### 处理机管理 - **进程控制**:当用户作业要运行时,应为之建立一个或多个进程,并为它分配除处理机以外的所有资源,将它放入进程就绪队列。当进程运行完成时,立即撤消该进程,以便及时释放其所占有的资源。进程控制的基本功能就是创建和撤消进程以及控制进程的状态转换。 - **进程同步**:所谓进程同步是指系统对并发执行的进程进行协调。 - 进程互斥方式,是使诸进程以互斥方式访问临界资源。 - 进程同步方式,对于彼此相互合作去完成共同任务的诸进程,则应由系统对它们的运行次序加以协调。 - **进程通信**:对于相互合作的进程,在它们运行时,相互之间往往要交换一定的信息,这种进程间所进行的信息交换称为进程通信。 - **调度**:当一个正在执行的进程已经完成,或因某事件而无法继续执行时,系统应进行进程调度,重新分配处理机。 - 作业调度是从后备队列中按照一定的算法,选择出若干个作业,为它们 分配运行所需的资源。 - 进程调度是指按一定算法,从进程就绪队列中选出一进程,把处理机分 配给它,为该进程设置运行现场。并使之投入运行。 ### 存储器管理 - 内存分配:主要任务是: - 为每道程序分配内存空间,使它们"各得其所"。 - 提高存储器的利用率,尽量减少不可用的内存空间(碎片)。 - 允许正在运行的程序申请附加的内存空间,以适应程序和数据动态增长的需要。 - 内存分配方式 - 静态分配 - 动态分配 - 内存保护:为保证各道程序都能在自己的内存空间运行而互不干扰,要求每道程序在执行时能随时检查对内存的所有访问是否合法。必须防止因一道程序的错误而扰乱了其它程序,尤其应防止用户程序侵犯操作系统的内存区。 - 内存保护机制:设置两个寄存器,存放正在执行程序的上下界。 - 地址映射: -个应用程序(源程序)经编译后,通常会形成若干个目标程序;这些目标程序再经过链接便形成了可装入程序。这些程序的地址都是从"0"开始的,程序中的其它地址都是相对于起始地址计算的; - 在多道程序环境下,每道程序不可能都从"0"地址开始装入(内存),这就致使地址空间内的逻辑地址和内存空间中的物理地址不相一-致。 - 使程序能正确运行,存储器管理必须提供地址映射功能,以将地址空间中的逻辑地址转换为内存空间中与之对应的物理地址。 - 内存扩充:并非是去扩大物理内存的容量,而是借助于虚拟存储技术,从逻辑上去扩充内存容量,使用户所感觉到的内存容量比实际内存容量大得多;或者是让更多的用户程序能并发运行。 - 请求调入功能。 - 置换功能。 - 缓冲管理:几乎所有的外围设备于处理机交换信息时,都要利用缓冲来缓和CPU和I/O设备间速度不匹配的矛盾,和提高CPU与设备、设备与设备间操作的并行程度,以提高CPU和I/O设备的利用率。 - 最常见的缓冲区机制有单缓冲机制、能实现双向同时传送数据的双缓冲机制,以及能供多个设备同时使用的公用缓冲池机制。 - 设备分配:系统根据用户所请求的设备类型和所采用的分配算法对设备进行分配,并将未获得所需设备的进程放进相应设备的等待队列。 - 设备处理程序又称为设备驱动程序。其基本任务是用于实现CPU和设备控制器之间的通信,即由CPU向设备控制器发出I/O命令,要求它完成指定的I/0操作;反之由CPU接收从控制器发来的中断请求,并给予迅速的响应和相应的处理。 ### 设备管理 - 设备管理的主要任务: - 为用户程序分配I/0设备;完成用户程序请求的I/O操作; - 提高CPU和I/O设备的利用率;方便用户使用。 ### 文件管理 - 文件存储空间的管理 - 目录管理 - 文件读写管理和保护 ### 操作系统与用户之间的接口 - 用户接口 - 联机用户接口 - 为联机用户提供的,它由一组键盘操作命令及命令解释程序所组成。用户可通过先后键入不同的命令方式来实现对作业的控制。 - 脱机用户接口 - 该接口是为批处理作业的用户提供的,故也称为批处理用户接口。由一组作业控制语言组成。用户用JCL把需要对作业进行的控制和干预事先写在作业说明书上,然后将作业连同作业说明书一起提供给系统。 - 图形用户接口 - 采用图形化的操作界面,用各种图表将系统的各项功能、文件等,直观的表现出来。用户直接用鼠标对应用程序和文件进行操作。 - 程序接口 - 由一组系统调用组成,每一个系统调用都是一个能完成特定功能的子程序,每当应用程序要求OS提供某种服务时,便调用具有相应功能的系统调用。 ## 运行环境 <mark>内核态(管态)和用户态(目态)将内核程序和用户程序隔离</mark> - 内核态 - 操作系统的程序执行 - 使用全部指令 - 使用全部系统资源 - 用户态 - 用户程序执行 - 禁止使用特权指令 - 只允许用户程序访问自己的存储区域 特权指令和非特权指令 - 特权指令 - 设计外部设备的输入/输出指令 - 存取用于内存保护的寄存器 - 内存清零 - 置时钟 - 允许/禁用中断 <mark>中断指令</mark>是用户程序发起的调用内核代码的唯一方式 - 中断机制 - 提高多道程序环境下CPU利用率 - 外中断:中断信号来源于外部设备 - 内中断:中断信号来源于当前指令内 - 内中断的三种情况 - 陷阱/陷入:由应用程序主动引发 - 故障:由错误条件引发 - 终止:由致命错误引发 - 系统调用的核心 - 用户程序中包含一段含有int指令的代码 - 操作系统写中断处理,获取想调用程序的编号 - int指令将使CPL改成0 ,“进入内核” - 操作系统根据编号执行相应代码 -

操作系统复习 第二章

# 第二章 进程的描述与控制 ## 程序的执行顺序 ### 程序的顺序执行 一个具有独立功能的程序独占处理机,直到得到最终结果的过程 #### 前驱图 一个有向无环图(DAG),用于描述进程间执行的前后的关系 - 节点:表示一个程序段或进程,或一条语句 - 有向边:节点之间的偏序或前驱关系 - 初始节点:没有前驱关系的点 - 终止节点:没有后继的节点 程序顺序执行的特性: - 顺序性:处理机的操作严格按照顺序进行 - 封闭性:程序一旦开始执行,结果不受外因影响 - 可再现性:程序结果与执行速度无关,只与初始条件有关 ### 程序的并发执行 内存中同时装入多道程序,他们共享系统资源,并发执行 特性: - 间断性:程序并发执行时,由于它们共享资源或程序之间相互合作完成-项共同任务,因而使程序之间相互制约。相互制约导致并发程序具有"执行一暂停-执行”这种间断性的活动规律。 - 失去封闭性: 程序在并发执行时,多个程序共享资源,因而资源的状态将由多个程序来改变,致使程序的运行失去了封闭性。 - 不可再现性:程序在并发执行时,多次运行初始条件相同的同-一程序会得出不同的运行结果。 ## 进程 - 定义: - 程序段、数据段、PCB三部分构成了进程实体,简称"进程”。 - 注意: - 进程是[程序J的「一次执行J - 进程是一个程序及其数据在处理机上顺序执行时所发生的「活动」 - 进程是程序在一个「数据集合J」运行的过程 - 进程是系统进行「资源分配和调度J的- -个「独立」单位(或者说基本单位) - 特征 - 动态性:进程的实质是程序的一次执行过程 - 进程是动态产生,动态消亡的,进程在其生命周期内,在3种基本状态之间转换 - 并发性:任何进程都可以同其他进程一起向前推进 - 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位 - 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进 - 三种状态 - 就绪状态(Ready) - 进程已获得除CPU之外的所有必需的资源,一旦得到CPU控制权,立即可以运行 - 运行状态(Running) - 进程已获得运行所必需的资源,它正在处理机上执行。 - 阻塞状态(Blocked) - 正在执行的进程由于发生某事件而暂时无法执行时,便放弃处理机而处于暂停状态,称该进程处于阻塞状态或等待状态。 - 其他状态 - 创建(New) - 为了保证进程的调度必须在创建工作完成后进行,同时为了增加管理的灵活性, OS可以根据系统性能或主存容量的限制,推迟创建状态进程的提交,产生进程的创建状态。 - 终止( Terminated ) - 进程到达自然结束点、出现无法克服的错误、被操作系统所终结 - 等待操作系统善后处理,将其PCB清零,将PCB空间返还系统 - 挂起 - 有的系统有时希望能人为地把进程挂起,使之处于静止状态,以便研究其执行情况或对它进行修改。 - 挂起:把进程从内存转向外存 - 引入挂起操作的原因 - 终端用户的请求。 - 父进程请求。 - 负荷调节的需要。 - 操作系统的需要。 - 激活 - 对应挂起 - ![](../../../../assets/default.png) ### 进程管理中的数据结构 - 操作系统中,对每个资源和每个进程都设置了-个数据结构,用于表征其实体,称之为:资源信息表和进程信息表。 - OS管理的这些数据结构分为四类:内存表、设备表、文件表、进程表。 ### 进程控制块 PCB PCB的作用是使一个在多道程序环境下不能独立运行的程序(含数据) ,成为一个能独立运行的基本单位, 一个能与其他进程并发执行的进程 <mark>PCB是进程存在的唯一标志</mark> - 作用 - 作为独立运行基本单位的标志。 - 能实现间断性运行方式。 - 提供进程管理所需要的信息。 - 提供进程调度所需要的信息。 - 实现与其它进程的同步与通信。 - 内容 - | 类型 | 内容 | 作用 | | ------------ | --------------------------------------------------------- | ---------------- | | 标识信息 | 1 )外部标识为方便用户<br/>2 )内部标识为方便系统 | 标识一个进程 | | 处理机状态(现场信息 ) | 1 ) CPU通用/指令寄存器<br/>2 ) CPU程序状态字PSW<br/>3 )用户栈指针 | 记录处理机现场信息,以备恢复之用 | | 调度信息 | 1 )进程状态<br/>2 )进程优先级<br/>3 )调度所需信息<br/>4)事件 | 用户进程的调度管理 | | 控制信息 | 1 )程序和数据地址<br/>2 )进程同步和通信机制<br/>3 )资源清单<br/>4 )指向下一个进程的指针 | 用于进程的控制管理 | - 组织方式 - 线性方式:即将系统中所有的PCB都组织在一张线性表中,将该表的首址存放在内存的一个专用区域中。该方式实现简单、开销小,但每次查找时都需要扫描整张表,因此适合进程数目不多的系统 - 链接方式:把具有同一状态的PCB用其中的链接字链接成一个队列。 - 就绪队列 - 若干个堵塞队列 - 索引方式:系统根据所有进程的状态建立几张索引表,把各表的内存首地址记录在内存的专用单元中。索引表的表目中记录了相应状态的某个PCB在PCB表中的地址。 ## 进程控制 进程控制是对系统中所有进程从产生、存在到消亡的全过程实行有效的管理和控制。 进程控制一般是由操作系统的内核来实现,内核在执行操作时,往往是通过执行各种原语操作来实现的。 ### 操作系统内核 - 功能 - 支撑功能 - 中断管理 - 时钟管理 - 计时 - 时钟中断 - 原语操作 - 由若干条指令组成 - 用来完成某个特定功能 - 执行过程不会中断 - 资源管理功能 - 进程管理 - 存储器管理 - 设备管理 - 进程控制 - 原语种类 - 进程创建原语 - 进程撤销原语 - 阻塞原语 - 唤醒原语 - 挂起原语 - 激活原语 - 控制过程 - 更新PCB中的信息(如修改进程状态标识、将运行环境保存到PCB、从 PCB恢复运行环境) ➢所有的进程控制原语一定会修改进程状态标志 ➢剥夺当前运行进程的CPU使用权必然需要保存其运行环境(玩游戏的存档) ➢某进程开始运行钱必然要恢复运行环境(玩游戏的读档) - 将PCB插入合适的队列 - 分配/回收资源 ### 进程的创建 - 层次结构 - 在OS中允许一个进程创建另-个进程,通常把创建进程的进程称为父进程,而把被创建的进程称为子进程。 - 在UNIX中子进程可继续创建更多的孙进程,由此便形成了- -个进程的层次 结构。 - 子进程可以继承父进程所拥有的资源, - 当子进程被撤消时,应将其从父进程那里获得的资源归还给父进程。 - 在撤消父进程时,也必须同时撤消其所有的子进程。 - Windows中不存在任何进程层次结构的概念 - 引起创建进程的事件 - 用户登录 - 作业调度 - 提供服务 - 应用请求 - 创建过程 - 申请进程标识,即申请空白PCB - 为新进程分配内存和其它资源 - 初始化进程控制块 - 将创建的进程置于就绪队列 ### 进程的终止 - 引起进程终止的事件 - 正常: - 任务完成=> halt/ logs off指令=>中断 - 异常: - 访问控制(存储越界、资源访问越界、指令越界) - 运行超时、等待超时 - 被禁止的运算 - I/O错误等 - 外界干预: - OS或用户干预 - 父进程请求 - 父进程终止 - 终止过程 - 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB ,从中读出该进程的状态 - 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度; - 若该进程还有子孙进程,还应将其所有子孙进程也都予以终止,以防它们成为不可控的进程; - 将被终止进程所拥有的全部资源或者归还给其父进程,或者归还给系统; - 将被终止进程( PCB )从所在队列(或链表)中移出,等待其它程序来搜集信息。 ### 进程的阻塞与唤醒 - 引起的事件 - 请求资源失败(如临界资源被占用,属于内部同步) - 等待某种操作完成(如磁盘l/O操作,属于内部同步) - 等待新数据的到来(如多进程协作时,属于外部同步) - 等待新任务(如Deamon服务进程,属于外部同步) - 进程的阻塞( block原语) - 进程停止执行 - 状态改为“阻塞” - 进程被插入阻塞队列 - 进程的唤醒( wakeup原语) - 状态改为“就绪” - 进程被插入就绪队列 ### 进程的挂起与激活 #### 挂起 当出现了引起进程挂起的事件时,比如,用户进程请求将自己挂起,或父进程请求将自己的某个子进程挂起,系统将利用挂起原语suspend( )将指定进程或处于阻塞状态的进程挂起。 挂起原语的执行过程: - 检查被挂起进程的状态,若处于活动就绪状态,便将其改为静止就绪;对于活动阻塞状态的进程,则将之改为静止阻塞。 - 把该进程的PCB插入相应的挂起队列上,将程序段、数据段移除内存。最后,若被挂起的进程正在执行,则转向调度程序重新调度。 ### 激活 - 当发生激活进程的事件时,若该进程驻留在外存而内存中已有足够的空间时,则可将在外存上处于静止就绪状态的进程换入内存。 - 系统将利用激活原语active( )将指定进程激活。 - 激活过程: - 先将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪;若为静止阻塞便将之改为活动阻塞。 - 假如采用的是抢占调度策略,则每当有新进程进入就绪队列时,应检查是否要进行重新调度,即由调度程序将被激活进程与当前进程进行优先级的比较,如果被激活进程的优先级更低,就不必重新调度;否则,立即剥夺当前进程的运行,把处理机分配给刚被激活的进程。 ## 进程同步 **引入进程的好处**:支持多道程序并发,提高资源利用率 **引入进程的缺点**:系统更复杂,破坏了程序运行的封闭性和不可再现性 **引入进程同步**:对多个进程在执行次序上进行协调,使并发执行的进程之间能按照一定的规则共享系统资源,并能相互协作,使得程序的执行具有可再现性 - 两种形式的制约关系 - 间接相互制约:多个进程只能互斥执行 - 互斥:对某个系统资源,多个进程不能同时使用 - 临界资源:一段时间内只允许一个进程访问的资源 - 问题 - 死锁 - 直接相互制约:多个进程之间相互合作共同完成一件事 - 临界区:进程中访问临界资源的那段代码 - 访问临界区的程序设计为: - 对欲访问的临界资源进行检查, - 若此刻未被访问,设正在访问的标志——进入区 - 访问临界资源——临界区 - 将正在访问的标志恢复为未被访问的标志——退出区 - 其余部分——剩余区 - 同步机制应遵循的规则 - 空闲让进:当无进程在临界区时,任何有权使用临界区的进程可进入。 - 忙则等待:不允许两个以上的进程同时进入临界区。 - 有限等待:任何进入临界区的要求应在有限的时间内得到满足。 - 让权等待:处于等待状态的进程应放弃占用CPU ,以使其他进程有机会得到CPU的使用权。 ### 硬件同步机制 - 关中断法(开关中断指令)也称为"硬件锁”, 是实现互斥最简单的方法。 - 做法:在进入锁测试之前关闭中断,直到完成锁测试并上锁之后才能打开中断。这样,进程在临界区执行期间,计算机系统不响应中断,从而不会引发调度,也就不会发生进程或线程切换。由此,保证了对锁的测试和关锁操作的连续性和完整性,有效地保证了互斥。 - 缺点: - 滥用关中断权力可能导致严重后果; - 关中断时间过长,会影响系统效率,限制了处理器交叉执行程序的能力; - 关中断方法也不适用于多CPU系统,因为在一个处理器上关中断并不能防止进程在其它处理器上执行相同的临界段代码。 - 利用Test and Set指令实现互斥 - 做法:这是一种借助一条硬件指令一-‘ 测试并建立"指令TS(Test- and-Set)以实 现互斥的方法。在许多计算机中都提供了这种指令。 - 缺点: - 不符合“让权等待”原则 - 利用swap指令实现线程互斥 - 缺点: - 不符合”让权等待“原则 ### 信号量机制 1965年荷兰Dijkstra提出的信号量( Semaphores )是-种卓有成效的进程同步工具,在长期的应用中,得到了很大的发展,从整型信号量经过记录型信号量,进而发展为"信号量集”机制。 - 优点 - 信号量就是OS提供的管理公有资源的有效手段。 - 信号量代表可用资源实体的数量。 #### 整型信号量 - 定义:把整型信号量定义为-个用于表示资源数目的整型量S ,除初始化外仅能通过两个原子操作wait(S),signal(S)来访问 - 原子操作P : - 荷兰语"proberen"一-“检测” 之意。意味着请求分配一个单位资源 - wait(S) - 原子操作V : - 荷兰语“verhogen"一- "增量”之意,意味着释放一个单位资源 - signal(S) #### 记录型信号量 - 在信号量机制中,除了需要一个用于代表资源数目的整型变量value外 ,还应增加一个进程链表L,用于链接上述的所有等待进程。 - 记录型信号量是由于它采用”了记录型的数据结构而得名的。 #### AND型信号量 - AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部地分配给进程,待进程使用完后再一起释放。 - 只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源,也不分配给他。 - 在wait操作中,增加了一个"AND"条件,故称为AND同步,或称为同时wait操作。 #### 信号量集 - 一次申请多个单位的临界资源 - 资源数量低于预设下限值时不予分配 - ![](../../../../assets/default.png) #### 应用 - 利用信号量实现进程互斥 - 为使多个进程互斥的访问某临界资源,须为该资源设置一互斥信号量mutex ,并设其初始值为1 ,然后将各进程访问资源的临界区CS置于wait(mutex)和signal(mutex)之间即可。 - 利用信号量实现前驱关系 - 希望S1→S2 ,只需使进程P1和P2共享一个公用信号量S=0 ,将signal(S)放在语句S1后,将wait(S)放在语句S2前。 ### 管程 - 信号量的缺点 - 同步操作分散:信号量机制中,同步操作分散在各个进程中,使用不当就可能导致各进程死锁(如P、V操作的次序错误、重复或遗漏) ; - 易读性差:要了解对于一组共享变量及信号量的操作是否正确,必须通读整个系统 - 管程的优点 - 把分散在各进程中的临界区集中起来进行管理,并把系统中的共享资源用数据结构抽象地表示出来。由于临界区是访问共享资源的代码段,建立一个“秘书”程序管理来到的访问。"秘书” 每次仅让一个进程来访,这样既便于对共享资源的管理,又实现了互斥访问。 管程是由若干个公共变量和所有访问这些变量的过程所组成的一个特殊的模块或软件包 - 基本思想: - 集中管理各进程中的临界区:管程把分散在各个进程中互斥地访问公共变量的那些临界区集中起来管理。 - 特点 - 管程的局部变量只能由该管程的过程存取; - 系统保证进程只能互斥地调用管程中的过程。 - 条件变量 - 管程中对每个条件变量,都须予以说明, 其形式为: condition x, y。该变 量应置于wait和signal之前,即可表示为X.wait和X.signal。 - 特征 - 模块化: 一个管程是一个基本程序单位,可以单独编译; - 抽象数据类型:管程是一种特殊的数据类型,其中不仅有数据,而且有对数据进行操作的代码,是对数据和操作的封装。 - 信息掩蔽:管程如何实现其功能相对于外部程序是半透明的。 - 优点 - 安全性:共享变量外部不可见,只能由管程中的操作存取; - 互斥性:管程在任何一个时刻只能有一个进程进入; - 等待机制:设置有等待队列及相应的操作,对资源进行管理。 - 管程和进程的区别 - 设置目的不同:管程是对共享资源进行管理,进程是资源分配和执行的基本单位。 - 数据结构不同:管程定义公用数据结构,进程定义私有数据结构PCB。 - 存在方式不同:进程有生命周期,管程是操作系统固有的部分,没有生命周期。 - 执行方式不同:管程被进程调用,没有并发性,进程具有并发执行性。 ## 进程通信 ### 共享存储器系统 - 模式: - 共享数据结构 - 进程公用某些数据结构,借以实现诸进程间的信息交换。 - 实现:公用数据结构的设置及对进程间同步的处理,都是程序员的职责。操作系统提供共享存储器 - 特点:低效。只适合传递相对少量的数据。 - 共享存储区 - 在存储器中划出一块共享存储区,诸进程可通过对共享存储区中数据的读或写 来实现通信。 ### 管道通信系统 - 管道:指用于连接-个读进程和一个写进程以实现他们之间通信的一个打开的共享文件,又名pipe文件。 - 管道只能采取半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道各个进程要互斥的访问管道 - 数据以字节流的形式写入管道,当管道写满时,写进程的write()系统调用将会被阻塞,等待读进程将数据取走。当读进程将数据全部取走后,管道变空,此时读进程的read()系统调用将会被阻塞 ### 消息传递系统 - 用格式化消息封装所需传输的数据,消息长度可以固定,也可以变化。 - 直接利用系统提供的一组通信命令(原语)进行通信。 - 操作系统隐藏了通信的实现细节,大大减化了通信程序编制的复杂性 - 应用广泛:微内核、多处理机系统、分布式系统、计算机网络等 - 消息传递系统的通信方式属于高级通信方式。又因其实现方式的不同而进一步分成 - 直接通信方式 - 发送进程利用OS提供的发送命令,直接把消息发送给目标进程。发送进程和接收进程都以显式方式提供对方的标识符。 - 通信原语: - Send(Receiver, message);发送一个消息给接收进程 - Receive(Sender, message);接收Sender发来的消息 - 间接通信方式。 - 信箱用来暂存发送进程发送给目标进程的消息,接收进程则从信箱中取出发送给自己的消息。 - 消息在信箱中可安全保存,只允许核准的目标用户随时读取 - 利用信箱通信方式,既可实时通信,又可非实时通信。 - 通信原语: - Send (MailBox, Message) ; - Receive (MailBox, Message) ; - 信箱可由操作系统创建,也可由用户进程创建,创建者是信箱的拥有者。 - 信箱分类: - 私用信箱 - 公用信箱 - 共享信箱 ### 客户-服务器系统 ## 线程 ### 线程的引入 - 进程的两个基本属性: - 进程是一个可拥有资源的独立单位 - 进程同时又是一个可独立调度和分派的基本单位。 - 为使程序能并发执行,系统还必须进行以下的一系列操作。 - 创建进程 - 撤消进程 - 进程切换 - 这样系统必须为之付出较大的时空开销。因此应分开进程的两个属性。即对于作为调度和分派的基本单位,不同时作为拥有资源的单位,以"轻装上阵”,反之亦然。 - 进程切换过程 - 切换页目录以使用新的地址空间 - 切换内核栈和硬件上下文 ### 线程 线程(thread)是一个可执行的实体单元。<mark>它代替以往的进程,成为现代操作系统中处理机调度的基本单位。</mark> - 特性 - 调度的基本单位 - 同一进程中的线程切换不会引起进程切换 - 不同进程中的线程切换必然引起进程切换 - 并发性 - 拥有资源 - 独立性 - 系统开销 - 支持多处理机系统 - 线程运行的三个状态 - 执行状态,表示线程正获得处理机而运行; - 就绪状态,指线程已具备了各种执行条件, 一旦获得CPU便可执行的状态; - 阻塞状态,指线程在执行中因某事件而受阻,处于暂停执行时的状态。 - 线程控制块TCB - 线程标识符; - 组寄存器,它包括程序计数器PC、状态寄存器和通用寄存器; - 线程运行状态,用于描述线程正处于何种运行状态; - 优先级,描述线程执行的优先程度; - 线程专有存储区,用于线程切换时存放现场保护信息,和相关统计信息; - 信号屏蔽,即对某些信号加以屏蔽。 - 堆栈,用来保存局部变量和返回地址。 - 用户栈和核心栈 - 用户级线程 - 定义 - 用户级线程仅存在于用户空间中。对于这种线程的创建、撤消、线程之间的同步与通信等功能,都无须利用系统调用来实现。 - 对于用户级线程的切换,通常是发生在一个应用进程的诸多线程之间,无须内核的支持。 - 切换的规则远比进程调度和切换的规则简单 - 实现 - 用于管理和控制线程的函数(过程)的集合,其中包括用于创建和撤消线程的函数、线程同步和通信的函数以及实现线程调度的函数等 ➢pthread_ creat , pthread_ exit , pthread_ join , pthread_ yield - 运行时系统中的所有函数都驻留在用户空间,并作为用户级线程与内核之间的接口。 - 优点 - 线程切换不需要转换到内核空间 - 调度算法可以是进程专用的 - 用户级线程的实现与OS平台无关 - 用户线程不占用内核内存,本身的内核空间开销也更小一些,可以节约资源 - 缺点 - 系统调用的阻塞问题。当线程执行一个系统调用时,不仅该线程被阻塞,而且,进程内的所有线程会被阻塞。而在内核支持线程方式中则进程中的其它线程仍然可以运行。 - 在单纯的用户级线程实现方式中,多线程应用不能利用多处理机进行多重处理的优点,内核每次分配给一个进程的仅有一个CPU ,因此,进程中仅有一个线程能执行,在该线程放弃CPU之前,其它线程只能等待。 - 内核支持线程KST - 定义 - 在内核的支持下运行的,即无论是用户进程中的线程,还是系统进程中的线程,他,们的创建、撤消和切换等,也是依靠内核实现的。 - 在内核空间还为每一个内核支持线程设置了一 个线程控制块 ,内核是根据该控制块而感知某线程的存在的,并对其加以控制。 - 实现 - 在支持线程的OS中,系统在创建进程时,便为他分配一个任务数据区,包括若干线程控制块空间。 - 优点 - 在多处理器系统中,内核能够同时调度同-进程的多个线程并行执行。 - 一个线程被阻塞,内核可以调度该进程的其它线程运行,也可以运行其它进程中的线程。 - 线程切换比较快,开销小。 - 内核本身也可以采用多线程技术,提高执行速度和效率 - 缺点 - 在同一进程间的线程控制权转移时,用户级与核心级的切换开销很大。 - 组合方式 - 内核支持多线程的建立、调度和管理,同时,也允许用户应用程序建立、调度和管理用户级线程。 - 这种方式能够结合两者的优点,克服其各自的不足。

操作系统复习 第三章

# 第三章 处理机调度与死锁 ## 处理机调度的层次 - 进程:是一个程序对某个数据集的执行过程,是分配资源的基本单位。 - 作业:是用户需要计算机完成的某项任务,是要求计算机所做工作的集合 - 一个作业通常包括几个进程,几个进程共同完成一个任务,即作业。 - 用户提交作业以后,当作业被调度,系统会为作业创建进程, 一个进程无法完成时,系统会为这个进程创建子进程。 - 作业的概念更多地用在批处理系统中。 - <mark>进程的概念几乎可以用在所有的多道程序系统中</mark>。 <mark>调度</mark>是多道程序的关键 **调度算法的目标:** - 提高资源利用率 - **公平性**。公平性是指应使诸进程都获得合理的CPU时间,不会发生进程 饥饿现象。 - **平衡性**。由于在系统中可能具有多种类型的进程,有的属于计算型作业, 有的属于I/O型。为使系统中的CPU和各种外部设备都能经常处于忙碌状态, 调度算法应尽可能保持系统资源使用的平衡性。 - **策略强制执行**。对所制订的策略其中包括安全策略,只要需要,就必须 予以准确地执行,即使会造成某些工作的延迟也要执行。 **批处理系统的目标**: - 平均周转周期更短 - 周转时间=作业完成时间-作业到达时间 - 平均周转时间=作业周转总时间/进程个数 - 带权周转时间:即作业的周转时间T与系统为它提供服务的时间T,之比,即.W = T/Ts。 - 可进一步反映调度的性能,更清晰地描述各进程在其周转时间中,等待和执行时间的具体分配状况. - 系统吞吐量高 - 由于吞吐量是指在单位时间内系统所完成的作业数 - 它与批处理作业的平均长度有关 - 如果单纯是为了获得高的系统吞吐量,就应尽量多地选择短作业运行。 - 处理机利用率高 - 如果单纯是为使处理机利用率高,应尽量多地选择计算量大的作业运行 **分时系统的目标**: - 响应时间快 - 响应时间指用户提交请求到系统首次响应为止的时间。 - 均衡性。 - 系统响应时间的快慢应与用户所请求的复杂性相适应。 **实时系统的目标**: - 截止时间的保证 - 开始执行的最迟时间 - 必须完成的最迟时间。 - 可预测性 ## 作业与作业调度 ### 作业 - 作业(Job) - 用户提交给计算机系统的任务。 - 由程序、数据、作业说明书组成。 - 作业步(Job Step) - 作业执行期间所经历在加工步骤。 - 典型的作业控制过程分:“编译” " 链接装配“ ”运行“ - 作业控制块(Job Control Block , JCB) - 作业控制块是批处理作业存在的标志,保存有系统对于作业进行管理所需要的全部信息,位于磁盘区域中 - 作业开始,系统输入程序为其建立一个作业控制块,进行初始化,大部分信息取自作业说明书。 - 系统输入程序、作业调度程序、作业控制程序、系统输出程序等需要访问作业控制块。 - 作业完成后,其作业控制块由系统输出程序撤消。 - ![](../../../../assets/default.png) ### 作业调度 - 先来先服务算法(First Come First Serve) - FCFS是最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。 - 基本原则是按照作业到达系统的先后次序来选择,或者说它是优先考虑在系统中等待时间最长的作业,而不管该作业所需执行时间的长短。 - 短作业优先(Short Job First)调度算法 - 对短作业或短进程优先调度的算法。可以分别用于作业调度和进程调度。 - SJF算法是以作业的长短来计算优先级,作业越短,其优先级越高。 - SJF调度算法:从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。 - 缺点 - (1)必须预知作业的运行时间。 - (2)对长作业非常不利,长作业的周转时间会明显地增长,更严重的是,该算法 完全忽视作业的等待时间,可能使作业等待时间过长,出现饥饿现象。 - (3)在采用SJF算法时,人一机无法实现交互。 - (4)该调度算法完全未考虑作业的紧迫程度,故不能保证紧迫性作业能得到及时处理。 - 优先级调度算法(Priority-Scheduling Algorithm) - 反应作业的紧迫性 - 高响应比优先调度算法 - 高响应比优先调度算法则是既考虑了作业的等待时间,又考虑作业运行时间的调度算法,因此既照顾了短作业,又不致使长作业的等待时间过长,从而改善了处理机调度的性能 - 为每个作业引入一个动态优先级,即优先级是可以改变的,令它随等待时间延长而增加,这将使长作业的优先级在等待期间不断地增加,等到足够的时间后,必然有机会获得处理机。该优先级的变化规律可描述为: $$ 优先权=\frac{等待时间+要求服务时间}{要求服务时间} $$ - 由于等待时间与服务时间之和就是系统对该作业的响应时间,故该优先级又相当于响应比RP。据此,优先又可表示为: $$ R_p=\frac{等待时间+要求服务时间}{要求服务时间}=\frac{响应时间}{要求服务时间} $$ - 特点 - (1)如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高, 因而该算法有利于短作业。 - (2)当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时 间愈长,其优先权愈高,因而它实现的是先来先服务。 - (3)对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待 时间足够长时,其优先级便可升到很高,从而也可获得处理机。 ## 进程调度 - 任务 - 保存处理机的现场信息。 - 按某种算法选取进程。 - 把处理器分配给进程。 - 组成部分 - 排队器 - 分派器 - 上下文切换器 - 调度方式 - 非抢占方式: - 处理机分配给某进程后,就一直让它运行下去,决不会因为时钟中断或任何其它原因去抢占当前正在运行进程的处理机,直至该进程完成,或发生某事件而被阻塞时,才把处理机分配给其它进程。 - 评价 - 实现简单、系统开销小 - 适用于大多数的批处理OS ,但在分时系统和要求比较严格的实时系统中,不宜采用这种调度方式 - 抢占方式 - 允许调度程序根据某种原则,去暂停某个正在执行的进程,将处理机重 新分配给另一进程。 - 在现代OS中广泛采用抢占方式,这是因为: - 对于批处理机系统,可以防止一-个长进程长时间地占用处理机,以确保处理机能为所有进程提供更为公平的服务。 - 在分时系统中,只有采用抢占方式才有可能实现人一机交互 - 在实时系统中,抢占方式能满足实时任务的需求。 - 抢占原则 - 优先权原则:优先权高的可以抢占优先权低的进程的处理机。 - 短作业(进程)优先原则:短作业(进程)可以抢占长作业(进程)的处理机。 - 时间片原则:各进程按时间片运行,一个时间片用完时,停止该进程执行重新进行调度。 ### 调度算法 - 轮转调度算法 - 在分时系统中,为保证能及时响应用户的请求,必须采用基于时间片轮转式进程调度算法。 - 在早期,分时系统中采用的是简单的时间片轮转法 - 系统将所有的就绪进程按FCFS策略排成一个就绪队列 - 系统可设置每隔一定时间(如30 ms)便产生一次中断,去激活进程调度程序进行调度,把CPU分配给队首进程,并令其执行一个时间片。 - 当它运行完毕后,又把处理机分配给就绪队列中新的队首进程,也让它执行一个时间片。 - 这样,就可以保证就绪队列中的所有进程在确定的时间段内,都能获得一个时间片的处理机时间。 - 在RR调度算法中,应在何时进行进程的切换,可分为两种情况: - 若一个时间片尚未用完,正在运行的进程便已经完成,就立即激活调度程序,将它从就绪队列中删除,再调度就绪队列中队首的进程运行,并启动一个新的时间片。 - 在一个时间片用完时,计时器中断处理程序被激活。如果进程尚未运行完毕,调度程序将把它送往就绪队列的末尾。 - 时间片大小: - 如果太小利于短作业,但是会频繁中断,进程.上下文切换,增加系统开销; - 如果太大则每个进程都能在时间片内完成,则退化为FCFS算法,无法满足交互式用户的需求。 - 一个比较可取的大小是,时间片略大于一次典型的交互所需要的时间。 - 优先级调度算法 - 优先级进程调度算法,是把处理机分配给就绪队列中优先级最高的进程。这时,又可进一步把该算法分成如下两种 - 非抢占式优先级调度算法。 - 抢占式优先级调度算法。 - 优先级类型 - 静态优先级是在创建进程时确定的,在进程的整个运行期间保持不变。优先级是利用某一范围内的一个整数来表示的,例如0 ~ 255中的某一整数,又把该整数称为优先数。确定进程优先级大小的依据有如下三个: - 进程类型。 - 进程对资源的需求。 - 用户要求。 - 动态优先级 - 动态优先级是指在创建进程之初,先赋予其-个优先级,然后其值随进程的推进或等待时间的增加而改变,以便获得更好的调度性能。 - 若所有进程都具有相同的优先级初值,则最先进入就绪队列的进程会因其优先级变得最高,从而得到cpu ,等于FCFS。 - 若所有就绪进程具有各不相同的优先级初值,那么对于优先级初值低的进程,在等待了足够的时间后,也可以获得处理机。 - 也可以规定,当运行的进程随着运行时间的推移动态降低其优先级,防止一个进程长期垄断cpu。 - 多队列调度算法 - 在多处理机系统中可以为每个处理机设置一个单独的就绪队列 - 多级反馈队列调度算法 - 是时间片轮转算法和优先级调度算法的综合和发展,通过动态调整进程优先级和时间片大小,不必事先估计进程的执行时间。 - <mark>FCFS+优先级+RR+抢占</mark> - 多级反馈队列可兼顾多方面的系统目标,是目前公认的一种<mark>较好</mark>的进程调度算法 - 调度机制 - 设置多个就绪队列并为各个队列赋予不同的优先级,第一个最高,依次降低。各个队列中进程执行时间片的大小设置为:优先权越高,时间片越短 - 每个队列都采用FCFS算法。当新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可撤离系统。否则,即它在一个时间片结束时尚未完成,调度程序将其转入第二队列的末尾等待调度;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列, ..... 依此类推。当进程最后被降到第n队列后,在第n队列中便采取按RR方式运行。 - 按队列优先级调度。调度程序首先调度最高优先级队列中的诸进程运行,仅当第一队列空闲时才调度第二队列中的进程运行;换言之,仅当第1~ (i-1)所有队列均空时,才会调度第队列中的进程运行。如果处理机正在第队列中为某进程服务时又有新进程进入任-优先级较高的队列,此时须立即把正在运行的进程放回到第队列的末尾,而把处理机分配给新到的高优先级进程。 - 注意 - 当现行进程正在执行它的C P U周期时,如果发生了时间片中断或有进程进入更高级的就绪队列时将引起剥夺,对前一-种情况,现行进程将进入下一级队列,对后一种情况,现行进程则进入本级队列末尾。 - 当一进程被唤醒时,它进入的是原先离开的那个队列,即与其当前优先级对 应的就绪队列。 - 一个进程的优先级被降低,仅发生在因时间片中断而被剥夺的时候。 - ![](../../../../assets/default.png) - 多级反馈队列调度算法具有较好的性能,能较好的满足各种类用户的需要。 - 终端型作业用户。大多属于较小的交互性作业,只要能使作业在第一队列的时间片内完成,便可令用户满意。 - 短批处理作业用户。周转时间仍然较短,至多在第二到三队列即可完成。 - 长批处理作业用户。将依次在1 ~ n级队列中轮转执行,不必担心作业长期得不到处理。 - 保证调度算法 - 在系统中有n个相同类型的进程同时运行,为公平起见,须保证每个进程都获得相同的处理机时间1/n。 - 跟踪计算每个进程自创建以来已经执行的处理时间。 - 计算每个进程应获得的处理机时间,即自创建以来的时间除以n。 - 计算进程获得处理机时间的比率,即进程实际执行的处理时间和应获得的处理机时间之比。 - 比较各进程获得处理机时间的比率。如进程A的比率最低,为0.5 ,而进程B的比率为0.8 ,进程C的比率为1.2等。 - 调度程序应选择比率最小的进程将处理机分配给它,并让该进程一-直运行,直到超过最接近它的进程比率为止。 - 公平共享调度算法 - 分配给每个进程相同的处理机时间,显然,这对诸进程而言,是体现了一定程度的公平,但如果各个用户所拥有的进程数不同,就会发生对用户的不公平问题。 - 用户1有4个进程A、B、C、D ,用户2只有一-个进程E。强制调度序列为: A E B E C E D E A E B E C E D E - 如果希望用户1所获得的处理机时间是用户2的2倍,则强制调度序列为: A B E C D E A B E C D E ## 实时调度 ### 实时调度的条件 - 实时系统 - 两种任务 - 硬实时任务( HRT )指必须满足最后期限的限制,否则会给系统带来不可接受的破坏或者致命错误。 - 软实时任务( SRT)也有一个与之关联的最后期限,并希望能满足这个期限的要求,但这并不是强制的,即使超过了最后期限,调度和完成这个任务仍然是有意义的。 - 提供必要的信息 - 就绪时间。 - 开始截止时间和完成截止时间。 - 处理时间。 - 资源要求。 - 优先级。 - 系统处理能力强 - 单处理机实时调度条件 - $$ \sum_{i+1}^{n}\frac{C_i}{P_i}\leq1 $$ - 其中C表示处理时间,P表示周期时间 - 提高处理能力的途径 - 采用单处理机系统,增强处理能力,减少每个任务处理时间; - 采用多处理机调度 - $$ \sum_{i+1}^{n}\frac{C_i}{P_i}\leq N $$ - N表示处理机个数 - 采用抢占式调度机制 - 在含有硬实时任务的实时系统中,广泛采用抢占机制。 - 当一个优先权更高的任务到达时,允许将当前任务暂时挂起,令高优先权任务立即投入运行,这样可满足该硬实时任务对截止时间的要求。但此种机制较复杂。 - 对于一些小的实时系统,如果能预知任务的开始截止时间,则对实时任务的调度可采用非抢占调度机制,以简化调度程序和对任务调度时所花费的系统开销。 - 具有快速切换机制 - 为保证要求较高的硬实时任务能及时运行,在实时系统中还应具有快速切换机制,以保证任务的快速切换。需要以下两种能力: - 对外部中断的快速响应能力。要求系统具有快速硬件中断机构,使可在紧迫的外部事件请求中断时及时响应。 - 快速的任务分派能力。在完成任务调度后,便应进行任务切换,为提高速度,应使系统中的运行功能单位适当的小,以减少任务切换的时间开销。 ### 实时调度算法 - 分类 - 根据实时任务性质,可将实时调度的算法分为: - 硬实时调度算法 - 软实时调度算法 - 按调度方式,则可分为: - 非抢占调度算法 - 非抢占式轮转调度算法。 - 非抢占式优先调度算法。 - 抢占调度算法 - 基于时钟中断的抢占式优先级调度算法。 - 立即抢占(Immediate Preemption)的优先级调度算法。 - ![](../../../../assets/default.png) - 最早截止时间优先(Earliest Deadline First)算法 - 根据任务的截止时间来确定任务的优先级。截止时间越早,其优先级越高。 - 该算法要求在系统中保持一个实时任务就绪队列,该队列按各任务截止时间的早晚排序,调度程序在选择任务时总是选择就绪队列中的第一个任务,为之分配处理机,使之投入运行。 - EDF算法既可以用于抢占式调度,也可用于非抢占式调度。 - 最低松弛度优先(Least Lexity First)算法 - 该算法是根据任务紧急(或松弛)的程度,来确定任务的优先级。任务的紧急程度越高,为之赋予的优先级就越高。 - 例如,任务A在200ms时必须完成,本身运行时间100ms ,则必须在100ms之前调度执行, A任务的紧急(松弛)程度为100ms ,又如任务B在400ms是必须完成,需运行150ms ,其松弛程度为250ms. - 该算法主要用于抢占调度方式中。 - 优先级倒置(Priority Inversion Problem) - 形成 - 当前OS广泛采用优先级调度算法和抢占方式,然而在系统中存在着影响进程运行的资源而可能产生"优先级倒置”的现象,即高优先级进程(或线程)被低优先级进程(或线程)延迟或阻塞 ## 死锁 ### 死锁概述 死锁( Deadlock ) 是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种状态时,若无外力作用,它们都将无法再向前推进。 | | 共同点 | 区别 | | --- | -------------- | ----------------------------------------------------------------------------------------------------- | | 死锁 | 都是进程无法向前推进的现象。 | 1.一定是循环等待对方手里的资源导致的 <br/>2.至少有2个或2个以上进程同时发生 <br/>3.进程处于阻塞态<br/>4.操作系统分配资源的策略不合理导致<br/>5.是管理者(操作系统)的问题 | | 饥饿 | | 1.只能由一个进程发生饥饿<br/>2.可能在阻塞态,也可能在就绪态<br/>3.操作系统分配资源的策略不合理导致<br/>4.是管理者(操作系统)的问题 | | 死循环 | | 1.可能只有一个<br/>2.可以是运行态<br/>3.由代码逻辑错误导致的<br/>4.是被管理者的问题 | - 资源 - 可重用性资源 - 每一个可重用性资源中的单元只能分配给一个进程使用,不允许多个进程共享。进程在使用可重用性资源时,须按照这样的顺序: - 请求资源 - 使用资源。 - 释放资源。 - 系统中每一类可重用性资源中的单元数目是相对固定的,进程在运行期间既不能创建也不能删除它。 - 可消耗性资源 - 可消耗性资源又称为临时性资源,它是在进程运行期间,由进程动态地创建和消耗的,它具有如下性质: - 每一类可消耗性资源的单元数目在进程运行期间是可以不断变化的,有时它可以有许多,有时可能为0 ; - 进程在运行过程中,可以不断地创造可消耗性资源的单元,将它们放入该资源类的缓冲区中,以增加该资源类的单元数目。 - 进程在运行过程中,可以请求若干个可消耗性资源单元,用于进程自己 的消耗,不再将它们返回给该资源类中。 - 可抢占性资源(CPU、内存等) - 某进程在获得这类资源后,该资源可以再被其它进程或系统抢占。 - 不可抢占性资源(打印机、磁带机等) - 一旦系统把某资源分配给该进程后,就不能将它强行收回,只能在进程用完后自行释放。 - 死锁原因 - 竞争不可抢占性资源 - 竞争可消耗资源 - 进程推进顺序不当 - 产生死锁的必要条件 - 互斥条件 - 请求和保持条件 - 不可抢占条件 - 循环等待条件 ### 预防死锁 预防死锁的方法是使四个必要条件中的第2、3、4条件之一不能成立来避免发生死锁。 必要条件1 ,因为它是由设备的固有条件所决定的,不仅不能改变,还应加以保证。、 - 破坏请求和保持条件 - 第一种协议 - 系统规定所有进程在开始运行之前,都必须-次性的申请其在整个运行过程所需的全部资源。 - 优点:算法简单、易于实现且很安全。 - 缺点:资源浪费严重和使进程经常会发生饥饿现象。 - 第二种协议 - 进程提出申请资源前必须释放已占有的一切资源。 - 当一个已经保持了某些不可被抢占资源的进程,提出新的资源请求而不能得到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着进程已占有的资源会被暂时地释放,或者说是被抢占了,从而破坏了”不可抢占”条件。 - 缺点:实现起来比较复杂且付出很大代价。可能会前功尽弃,反复申请和释放等情况,延长了周转时间,增加系统开销。 - 破坏循环等待条件 - 采用资源顺序分配法,可以破坏循环等待条件 - 采用资源顺序分配法,系统不会出现循环等待。因为在任何时刻,总有一个进程占有较高序号的资源,该进程继续请示的资源必然是空闲的。故该进程可一直向前推进。 - 优点: - 有序资源分配法提高了资源利用率 - 缺点: - 不方便增加新的设备,因为可能需要重新分配所有编号 - 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费; - 必须按规定申请资源,用户编程麻烦, ### 避免死锁 - 系统安全状态 - 该方法允许进程动态地申请资源,系统在进行资源分配之前,先计算资源分配的安全性。若此次分配不会导致系统从安全状态向不安全状态转换,便可将资源分配给进程;否则不分配资源,进程必须阻塞等待。从而避免发生死锁。 - 安全状态:在此状态系统能按某种顺序P1, P2... Pn来为各个进程分配其所需资源,使每个进程都可顺序地一个个地完成。这个序列{P1,P....Pn}称为安全序列。 - 不安全状态:系统不存在任何一个安全序列 - 银行家算法 - 银行家算法的数据结构 - 可利用资源向量Available [m] - m为系统中资源种类数, Available[j] =k表示系统中第j类资源数为k个。 - 最大需求矩阵Max[n][m] - n为系统中进程数, Max[i][j] =k表示进程对j类资源的最大需求数为k。 - 分配矩阵Allocation[n][m] - Allocation[i][j]=k表示进程i记分得j类资源的数目为k个。 - 需求矩阵Need[n][m] - Need[i][j]=k表示进程i还需要j类资源k个。 - Need[i][j] =Max[i][j]-Allocation[i][j] - 内容 - 设Request;[j]=k,表示进程P:需要k个R;类型的资源 - 如果Request;[j] <= Need[i,j],便转向步骤2 ;否则认为出错,因为它所请求的资源数已超过它所需要的最大值。 - 如果Request;[j] <= Available[j],便转向步骤3 ;否则,表示尚无足够资源,P需等待 - 系统试探着把资源分配给进程P,并修改下面数据结构中数值 - Available[j] = Available[j]- Request;[j]; - Allocation[ij] = Allocation[;j]+ Request;[j]; - Need[ij] =Need[ij]-Request;[j]; - 安全性 - (1 )设置两个向量: - 工作向量work :表示系统可提供给进程继续运行所需的各类资源数目,含有m个元素的一维数组,初始时, work =Available; - Finish:含n个元素的一维数组,表示系统是否有足够的资源分配给n个进程,使之运行完成。开始时先令Finish[i] =false (i=1..n);当有足够资源分配给进程时,再令Finish[i]=true。 - ( 2 )从进程集合中找到一个能满足下述条件的进程: - Finish[i]=false; - Need[ij] < =work[j]; - 若找到,执行步骤( 3),否则执行步骤( 4)。 - ( 3 )当进程Pi获得资源后,可顺利执行,直至完成,并释放出分配给它的资源,故应执行: - work[j]= work[j]+ Allocation[ij] ; - Finish[i]=true; - go tostep (2); - ( 4 )如果所有进程的Finish[i] =true都满足,则表示系统处于安全状态;否则,系统处于不安全状态。 ### 检查死锁 - 为了能对系统中是否已发生了死锁进行检测,在系统中必须 - ①保存有关资源的请求和分配信息; - ②提供一种算法,它利用这些信息来检测系统是否已进入死锁状态。 - ![](../../../../assets/default.png) - 死锁定理 - 如果资源分配图中没有环路,则系统中没有死锁; - 如果图中存在环路则系统中可能存在死锁 - 如果每个资源类中只包含一个资源实例,则环路是死锁存在的充分必要条件 - 如果每个资源类中资源实例个数不全为1 , 则环路是死锁存在的必要条件 - 死锁定理2 - 资源分配图化简 - 找到资源分配图中一个不孤立、不阻塞的进程节点,消去请求边与分配边,使之成为孤立点。 - 孤立点的资源释放后,可以分给其它进程,即将某进程的申请边变为分配边。 - 重复上述过程,当资源分配图中所有进程都成为孤立点时,称该资源分配图是可以完全简化的,否则称该资源分配图是不可完全简化的。 - **不孤立**:是指该进程存在有与之相连的有向边; - **不阻塞**:是指该进程除了已经分配的资源外,对它尚需要的资源,系统都能够满足,因此该进程不会被阻塞。 - **孤立点**:是指该进程既无请求边,也无分配边,即没有与之相连的有向边。 - <mark>一种资源分配状态为死锁状态的充要条件是资源分配图是不可完全简化的。</mark> ### 解除死锁 - 当发现进程死锁时,便应立即把它们从死锁状态中解脱出来。常采用的方法是: - 抢占资源:从其他进程剥夺足够数量的资源给死锁进程以解除死锁状态。 - 终止进程:最简单的是让全部进程都死掉;温和一点的是按照某种顺序逐个撤销进程,直至有足够的资源可用,使死锁状态消除为止。 - 终止所有死锁进程 - 这是一种最简单的方法,即是终止所有的死锁进程,死锁自然也就解除了,但所付出的代价可能会很大。因为其中有些进程可能已经运行了很长时间,已接近结束,一旦被终止真可谓“功亏一篑”,以后还得从头再来。 - 逐个终止进程 - 按照某种顺序,逐个地终止进程,直至有足够的资源,以打破循环等待,把系统从死锁状态解脱出来为止。 - 每终止一个进程,都需要用死锁检测算法确定系统死锁是否已经被解除,若末解除还需再终止另一个进程。 - 在采取逐个终止进程策略时,还涉及到应采用什么策略选择一个要终止的进程