Java

📝 3 篇文章
📅 最新: 2024/12/19

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; } } ```

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等的配置