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

网站建设的功能有哪些在网站上做漂浮

网站建设的功能有哪些,在网站上做漂浮,重庆大足网站建设,网站界面设计的流程SpringBoot开发实用篇开发实用篇中因为牵扯到SpringBoot整合各种各样的技术#xff0c;所以在整合每一个技术之前#xff0c;都会做一个快速的普及#xff0c;这样的话内容整个开发实用篇所包含的内容就会比较多。在学习的时候#xff0c;如果对某一个技术不是很清楚#…SpringBoot开发实用篇 开发实用篇中因为牵扯到SpringBoot整合各种各样的技术所以在整合每一个技术之前都会做一个快速的普及这样的话内容整个开发实用篇所包含的内容就会比较多。在学习的时候如果对某一个技术不是很清楚可以先跳过对应章节或者先补充一下技术知识。开发实用篇具体包含的内容如下热部署配置高级测试数据层解决方案整合第三方技术监控 看目录感觉内容量并不是很大但是在数据层解决方案和整合第三方技术中包含了大量的知识。1.热部署 什么是热部署简单说就是你程序改了现在要重新启动服务器嫌麻烦不用重启服务器会自己悄悄的把更新后的程序给重新加载一遍这就是热部署。 热部署的功能是如何实现的呢这就要分两种情况来说了非springboot工程和springboot工程的热部署实现方式完全不一样。先说一下原始的非springboot项目是如何实现热部署的。非springboot项目热部署实现原理 开发非springboot项目时我们要制作一个web工程并通过tomcat启动通常需要先安装tomcat服务器到磁盘中开发的程序配置发布到安装的tomcat服务器上。如果想实现热部署的效果这种情况其实有两种做法一种是在tomcat服务器的配置文件中进行配置这种做法与你使用什么IDE工具无关不管你使用eclipse还是idea都行。还有一种做法是通过IDE工具进行配置比如在idea工具中进行设置这种形式需要依赖IDE工具每款IDE工具不同对应的配置也不太一样。但是核心思想是一样的就是使用服务器去监控其中加载的应用发现产生了变化就重新加载一次。 上面所说的非springboot项目实现热部署看上去是一个非常简单的过程几乎每个小伙伴都能自己写出来。如果你不会写我给你个最简单的思路但是实际设计要比这复杂一些。例如启动一个定时任务任务启动时记录每个文件的大小以后每5秒比对一下每个文件的大小是否有改变或者是否有新文件。如果没有改变放行如果有改变刷新当前记录的文件信息然后重新启动服务器这就可以实现热部署了。当然这个过程肯定不能这么做比如我把一个打印输出的字符串abc改成cba比对大小是没有变化的但是内容缺实变了所以这么做肯定不行只是给大家打个比方而且重启服务器这就是冷启动了不能算热部署领会精神吧。 看上去这个过程也没多复杂在springboot项目中难道还有其他的弯弯绕吗还真有。springboot项目热部署实现原理 基于springboot开发的web工程其实有一个显著的特征就是tomcat服务器内置了还记得内嵌服务器吗服务器是以一个对象的形式在spring容器中运行的。本来我们期望于tomcat服务器加载程序后由tomcat服务器盯着程序你变化后我就重新启动重新加载但是现在tomcat和我们的程序是平级的了都是spring容器中的组件这下就麻烦了缺乏了一个直接的管理权那该怎么做呢简单再搞一个程序X在spring容器中盯着你原始开发的程序A不就行了吗确实搞一个盯着程序A的程序X就行了如果你自己开发的程序A变化了那么程序X就命令tomcat容器重新加载程序A就OK了。并且这样做有一个好处spring容器中东西不用全部重新加载一遍只需要重新加载你开发的程序那一部分就可以了这下效率又高了挺好。1-1.手动启动热部署步骤①导入开发者工具对应的坐标dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdoptionaltrue/optional /dependency步骤②构建项目可以使用快捷键激活此功能 对应的快捷键一定要记得CTRLF9 以上过程就实现了springboot工程的热部署是不是挺简单的。不过这里需要把底层的工作工程给普及一下。重启与重载 一个springboot项目在运行时实际上是分两个过程进行的根据加载的东西不同划分成base类加载器与restart类加载器。base类加载器用来加载jar包中的类jar包中的类和配置文件由于不会发生变化因此不管加载多少次加载的内容不会发生变化restart类加载器用来加载开发者自己开发的类、配置文件、页面等信息这一类文件受开发者影响 当springboot项目启动时base类加载器执行加载jar包中的信息后restart类加载器执行加载开发者制作的内容。当执行构建项目后由于jar中的信息不会变化因此base类加载器无需再次执行所以仅仅运行restart类加载即可也就是将开发者自己制作的内容重新加载就行了这就完成了一次热部署的过程也可以说热部署的过程实际上是重新加载restart类加载器中的信息。总结使用开发者工具可以为当前项目开启热部署功能使用构建项目操作对工程进行热部署1-2.自动启动热部署 自动热部署其实就是设计一个开关打开这个开关后IDE工具就可以自动热部署。因此这个操作和IDE工具有关以下以idea为例设置idea中启动热部署步骤①设置自动构建项目 打开【File】选择【settings...】,在面板左侧的菜单中找到【Compile】选项然后勾选【Build project automatically】意思是自动构建项目 自动构建项目选项勾选后步骤②允许在程序运行时进行自动构建 使用快捷键【Ctrl】【Alt】【Shit】【/】打开维护面板选择第1项【Registry...】 在选项中搜索comple然后勾选对应项即可 这样程序在运行的时候就可以进行自动构建了实现了热部署的效果。关注 如果你每敲一个字母服务器就重新构建一次这未免有点太频繁了所以idea设置当idea工具失去焦点5秒后进行热部署。其实就是你从idea工具中切换到其他工具时进行热部署比如改完程序需要到浏览器上去调试这个时候idea就自动进行热部署操作。总结自动热部署要开启自动构建项目自动热部署要开启在程序运行时自动构建项目1-3.参与热部署监控的文件范围配置 通过修改项目中的文件你可以发现其实并不是所有的文件修改都会激活热部署的原因在于在开发者工具中有一组配置当满足了配置中的条件后才会启动热部署配置中默认不参与热部署的目录信息如下/META-INF/maven/META-INF/resources/resources/static/public/templates 以上目录中的文件如果发生变化是不参与热部署的。如果想修改配置可以通过application.yml文件进行设定哪些文件不参与热部署操作spring:devtools:restart:# 设置不参与热部署的文件或文件夹exclude: static/**,public/**,config/application.yml总结通过配置可以修改不参与热部署的文件或目录思考 热部署功能是一个典型的开发阶段使用的功能到了线上环境运行程序时这个功能就没有意义了。能否关闭热部署功能呢咱们下一节再说。1-4.关闭热部署 线上环境运行时是不可能使用热部署功能的所以需要强制关闭此功能通过配置可以关闭此功能。spring:devtools:restart:enabled: false 如果当心配置文件层级过多导致相符覆盖最终引起配置失效可以提高配置的层级在更高层级中配置关闭热部署。例如在启动容器前通过系统属性设置关闭热部署功能。SpringBootApplication public class SSMPApplication {public static void main(String[] args) {System.setProperty(spring.devtools.restart.enabled,false);SpringApplication.run(SSMPApplication.class);} } 其实上述担心略微有点多余因为线上环境的维护是不可能出现修改代码的操作的这么做唯一的作用是降低资源消耗毕竟那双盯着你项目是不是产生变化的眼睛只要闭上了就不具有热部署功能了这个开关的作用就是禁用对应功能。总结通过配置可以关闭热部署功能降低线上程序的资源消耗2.配置高级 进入开发实用篇第二章内容配置高级其实配置在基础篇讲了一部分在运维实用篇讲了一部分这里还要讲讲的东西有什么区别呢距离开发过程越来越接近解决的问题也越来越靠近线上环境下面就开启本章的学习。2-1.ConfigurationProperties 在基础篇学习了ConfigurationProperties注解此注解的作用是用来为bean绑定属性的。开发者可以在yml配置文件中以对象的格式添加若干属性servers:ip-address: 192.168.0.1 port: 2345timeout: -1 然后再开发一个用来封装数据的实体类注意要提供属性对应的setter方法Component Data public class ServerConfig {private String ipAddress;private int port;private long timeout; } 使用ConfigurationProperties注解就可以将配置中的属性值关联到开发的模型类上Component Data ConfigurationProperties(prefix servers) public class ServerConfig {private String ipAddress;private int port;private long timeout; } 这样加载对应bean的时候就可以直接加载配置属性值了。但是目前我们学的都是给自定义的bean使用这种形式加载属性值如果是第三方的bean呢能不能用这种形式加载属性值呢为什么会提出这个疑问原因就在于当前ConfigurationProperties注解是写在类定义的上方而第三方开发的bean源代码不是你自己书写的你也不可能到源代码中去添加ConfigurationProperties注解这种问题该怎么解决呢下面就来说说这个问题。 使用ConfigurationProperties注解其实可以为第三方bean加载属性格式特殊一点而已。步骤①使用Bean注解定义第三方beanBean public DruidDataSource datasource(){DruidDataSource ds new DruidDataSource();return ds; }步骤②在yml中定义要绑定的属性注意datasource此时全小写datasource:driverClassName: com.mysql.jdbc.Driver步骤③使用ConfigurationProperties注解为第三方bean进行属性绑定注意前缀是全小写的datasourceBean ConfigurationProperties(prefix datasource) public DruidDataSource datasource(){DruidDataSource ds new DruidDataSource();return ds; } 操作方式完全一样只不过ConfigurationProperties注解不仅能添加到类上还可以添加到方法上添加到类上是为spring容器管理的当前类的对象绑定属性添加到方法上是为spring容器管理的当前方法的返回值对象绑定属性其实本质上都一样。 做到这其实就出现了一个新的问题目前我们定义bean不是通过类注解定义就是通过Bean定义使用ConfigurationProperties注解可以为bean进行属性绑定那在一个业务系统中哪些bean通过注解ConfigurationProperties去绑定属性了呢因为这个注解不仅可以写在类上还可以写在方法上所以找起来就比较麻烦了。为了解决这个问题spring给我们提供了一个全新的注解专门标注使用ConfigurationProperties注解绑定属性的bean是哪些。这个注解叫做EnableConfigurationProperties。具体如何使用呢步骤①在配置类上开启EnableConfigurationProperties注解并标注要使用ConfigurationProperties注解绑定属性的类SpringBootApplication EnableConfigurationProperties(ServerConfig.class) public class Springboot13ConfigurationApplication { }步骤②在对应的类上直接使用ConfigurationProperties进行属性绑定Data ConfigurationProperties(prefix servers) public class ServerConfig {private String ipAddress;private int port;private long timeout; } 注意观察现在绑定属性的ServerConfig类并没有声明Component注解。当使用EnableConfigurationProperties注解时spring会默认将其标注的类定义为bean因此无需再次声明Component注解了。 最后再说一个小技巧使用ConfigurationProperties注解时会出现一个提示信息 出现这个提示后只需要添加一个坐标此提醒就消失了dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactId /dependency总结使用ConfigurationProperties可以为使用Bean声明的第三方bean绑定属性当使用EnableConfigurationProperties声明进行属性绑定的bean后无需使用Component注解再次进行bean声明2-2.宽松绑定/松散绑定 在进行属性绑定时可能会遇到如下情况为了进行标准命名开发者会将属性名严格按照驼峰命名法书写在yml配置文件中将datasource修改为dataSource如下dataSource:driverClassName: com.mysql.jdbc.Driver 此时程序可以正常运行然后又将代码中的前缀datasource修改为dataSource如下Bean ConfigurationProperties(prefix dataSource) public DruidDataSource datasource(){DruidDataSource ds new DruidDataSource();return ds; } 此时就发生了编译错误而且并不是idea工具导致的运行后依然会出现问题配置属性名dataSource是无效的Configuration property name dataSource is not valid:Invalid characters: SBean: datasourceReason: Canonical names should be kebab-case (- separated), lowercase alpha-numeric characters and must start with a letterAction: Modify dataSource so that it conforms to the canonical names requirements. 为什么会出现这种问题这就要来说一说springboot进行属性绑定时的一个重要知识点了有关属性名称的宽松绑定也可以称为宽松绑定。 什么是宽松绑定实际上是springboot进行编程时人性化设计的一种体现即配置文件中的命名格式与变量名的命名格式可以进行格式上的最大化兼容。兼容到什么程度呢几乎主流的命名格式都支持例如 在ServerConfig中的ipAddress属性名Component Data ConfigurationProperties(prefix servers) public class ServerConfig {private String ipAddress; } 可以与下面的配置属性名规则全兼容servers:ipAddress: 192.168.0.2 # 驼峰模式ip_address: 192.168.0.2 # 下划线模式ip-address: 192.168.0.2 # 烤肉串模式IP_ADDRESS: 192.168.0.2 # 常量模式 也可以说以上4种模式最终都可以匹配到ipAddress这个属性名。为什么这样呢原因就是在进行匹配时配置中的名称要去掉中划线和下划线后忽略大小写的情况下去与java代码中的属性名进行忽略大小写的等值匹配以上4种命名去掉下划线中划线忽略大小写后都是一个词ipaddressjava代码中的属性名忽略大小写后也是ipaddress这样就可以进行等值匹配了这就是为什么这4种格式都能匹配成功的原因。不过springboot官方推荐使用烤肉串模式也就是中划线模式。 到这里我们掌握了一个知识点就是命名的规范问题。再来看开始出现的编程错误信息Configuration property name dataSource is not valid:Invalid characters: SBean: datasourceReason: Canonical names should be kebab-case (- separated), lowercase alpha-numeric characters and must start with a letterAction: Modify dataSource so that it conforms to the canonical names requirements. 其中Reason描述了报错的原因规范的名称应该是烤肉串(kebab)模式(case)即使用-分隔使用小写字母数字作为标准字符且必须以字母开头。然后再看我们写的名称dataSource就不满足上述要求。闹了半天在书写前缀时这个词不是随意支持的必须使用上述标准。编程写了这么久基本上编程习惯都养成了到这里又被springboot教育了没辙谁让人家东西好用呢按照人家的要求写吧。 最后说一句以上规则仅针对springboot中ConfigurationProperties注解进行属性绑定时有效对Value注解进行属性映射无效。有人就说那我不用你不就行了不用你小看springboot的推广能力了到原理篇我们看源码时你会发现内部全是这玩意儿算了拿人手短吃人嘴短认怂吧。总结ConfigurationProperties绑定属性时支持属性名宽松绑定这个宽松体现在属性名的命名规则上Value注解不支持松散绑定规则绑定前缀名推荐采用烤肉串命名规则即使用中划线做分隔符2-3.常用计量单位绑定 在前面的配置中我们书写了如下配置值其中第三项超时时间timeout描述了服务器操作超时时间当前值是-1表示永不超时。servers:ip-address: 192.168.0.1 port: 2345timeout: -1 但是每个人都这个值的理解会产生不同比如线上服务器完成一次主从备份配置超时时间240这个240如果单位是秒就是超时时间4分钟如果单位是分钟就是超时时间4小时。面对一次线上服务器的主从备份设置4分钟简直是开玩笑别说拷贝过程备份之前的压缩过程4分钟也搞不定这个时候问题就来了怎么解决这个误会 除了加强约定之外springboot充分利用了JDK8中提供的全新的用来表示计量单位的新数据类型从根本上解决这个问题。以下模型类中添加了两个JDK8中新增的类分别是Duration和DataSizeComponent Data ConfigurationProperties(prefix servers) public class ServerConfig {DurationUnit(ChronoUnit.HOURS)private Duration serverTimeOut;DataSizeUnit(DataUnit.MEGABYTES)private DataSize dataSize; }Duration表示时间间隔可以通过DurationUnit注解描述时间单位例如上例中描述的单位为小时ChronoUnit.HOURSDataSize表示存储空间可以通过DataSizeUnit注解描述存储空间单位例如上例中描述的单位为MBDataUnit.MEGABYTES2-4.校验 目前我们在进行属性绑定时可以通过松散绑定规则在书写时放飞自我了但是在书写时由于无法感知模型类中的数据类型就会出现类型不匹配的问题比如代码中需要int类型配置中给了非法的数值例如写一个“a这种数据肯定无法有效的绑定还会引发错误。 SpringBoot给出了强大的数据校验功能可以有效的避免此类问题的发生。在JAVAEE的JSR303规范中给出了具体的数据校验标准开发者可以根据自己的需要选择对应的校验框架此处使用Hibernate提供的校验框架来作为实现进行数据校验。书写应用格式非常固定话不多说直接上步骤步骤①开启校验框架!--1.导入JSR303规范-- dependencygroupIdjavax.validation/groupIdartifactIdvalidation-api/artifactId /dependency !--使用hibernate框架提供的校验器做实现-- dependencygroupIdorg.hibernate.validator/groupIdartifactIdhibernate-validator/artifactId /dependency步骤②在需要开启校验功能的类上使用注解Validated开启校验功能Component Data ConfigurationProperties(prefix servers) //开启对当前bean的属性注入校验 Validated public class ServerConfig { }步骤③对具体的字段设置校验规则Component Data ConfigurationProperties(prefix servers) //开启对当前bean的属性注入校验 Validated public class ServerConfig {//设置具体的规则Max(value 8888,message 最大值不能超过8888)Min(value 202,message 最小值不能低于202)private int port; } 通过设置数据格式校验就可以有效避免非法数据加载其实使用起来还是挺轻松的基本上就是一个格式。总结开启Bean属性校验功能一共3步导入JSR303与Hibernate校验框架坐标、使用Validated注解启用校验功能、使用具体校验规则规范数据校验格式2-5.数据类型转换 有关spring属性注入的问题到这里基本上就讲完了但是最近一名开发者向我咨询了一个问题我觉得需要给各位学习者分享一下。在学习阶段其实我们遇到的问题往往复杂度比较低单一性比较强但是到了线上开发时都是综合性的问题而这个开发者遇到的问题就是由于bean的属性注入引发的灾难。 先把问题描述一下这位开发者连接数据库正常操作但是运行程序后显示的信息是密码错误。java.sql.SQLException: Access denied for user rootlocalhost (using password: YES) 其实看到这个报错几乎所有的学习者都能分辨出来这是用户名和密码不匹配就就是密码输入错了但是问题就在于密码并没有输入错误这就比较讨厌了。给的报错信息无法帮助你有效的分析问题甚至会给你带到沟里。如果是初学者估计这会心态就崩了我密码没错啊你怎么能说我有错误呢来看看用户名密码的配置是如何写的spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?serverTimezoneUTCusername: rootpassword: 0127 这名开发者的生日是1月27日所以密码就使用了0127其实问题就出在这里了。 之前在基础篇讲属性注入时提到过类型相关的知识在整数相关知识中有这么一句话支持二进制八进制十六进制 这个问题就处在这里了因为0127在开发者眼中是一个字符串“0127”但是在springboot看来这就是一个数字而且是一个八进制的数字。当后台使用String类型接收数据时如果配置文件中配置了一个整数值他是先安装整数进行处理读取后再转换成字符串。巧了0127撞上了八进制的格式所以最终以十进制数字87的结果存在了。 这里提两个注意点第一字符串标准书写加上引号包裹养成习惯第二遇到0开头的数据多注意吧。总结yaml文件中对于数字的定义支持进制书写格式如需使用字符串请使用引号明确标注3.测试 说完bean配置相关的内容下面要对前面讲过的一个知识做加强了测试。测试是保障程序正确性的唯一屏障在企业级开发中更是不可缺少但是由于测试代码往往不产生实际效益所以一些小型公司并不是很关注导致一些开发者从小型公司进入中大型公司后往往这一块比较短板所以还是要拿出来把这一块知识好好说说做一名专业的开发人员。3-1.加载测试专用属性 测试过程本身并不是一个复杂的过程但是很多情况下测试时需要模拟一些线上情况或者模拟一些特殊情况。如果当前环境按照线上环境已经设定好了例如是下面的配置env:maxMemory: 32GBminMemory: 16GB 但是你现在想测试对应的兼容性需要测试如下配置env:maxMemory: 16GBminMemory: 8GB 这个时候我们能不能每次测试的时候都去修改源码application.yml中的配置进行测试呢显然是不行的。每次测试前改过来每次测试后改回去这太麻烦了。于是我们就想需要在测试环境中创建一组临时属性去覆盖我们源码中设定的属性这样测试用例就相当于是一个独立的环境能够独立测试这样就方便多了。临时属性 springboot已经为我们开发者早就想好了这种问题该如何解决并且提供了对应的功能入口。在测试用例程序中可以通过对注解SpringBootTest添加属性来模拟临时属性具体如下//properties属性可以为当前测试用例添加临时的属性配置 SpringBootTest(properties {test.proptestValue1}) public class PropertiesAndArgsTest {Value(${test.prop})private String msg;Testvoid testProperties(){System.out.println(msg);} } 使用注解SpringBootTest的properties属性就可以为当前测试用例添加临时的属性覆盖源码配置文件中对应的属性值进行测试。临时参数 除了上述这种情况在前面讲解使用命令行启动springboot程序时讲过通过命令行参数也可以设置属性值。而且线上启动程序时通常都会添加一些专用的配置信息。作为运维人员他们才不懂java更不懂这些配置的信息具体格式该怎么写那如果我们作为开发者提供了对应的书写内容后能否提前测试一下这些配置信息是否有效呢当时是可以的还是通过注解SpringBootTest的另一个属性来进行设定。//args属性可以为当前测试用例添加临时的命令行参数 SpringBootTest(args{--test.proptestValue2}) public class PropertiesAndArgsTest {Value(${test.prop})private String msg;Testvoid testProperties(){System.out.println(msg);} }使用注解SpringBootTest的args属性就可以为当前测试用例模拟命令行参数并进行测试在这个属性加载优先级的顺序中明确规定了命令行参数的优先级排序是11而配置属性的优先级是3结果不言而喻了args属性配置优先于properties属性配置加载。总结加载测试临时属性可以通过注解SpringBootTest的properties和args属性进行设定此设定应用范围仅适用于当前测试用例3-2.加载测试专用配置 上一节提出了临时配置一些专用于测试环境的bean的需求这一节我们就来解决这个问题。 学习过Spring的知识我们都知道其实一个spring环境中可以设置若干个配置文件或配置类若干个配置信息可以同时生效。现在我们的需求就是在测试环境中再添加一个配置类然后启动测试环境时生效此配置就行了。其实做法和spring环境中加载多个配置信息的方式完全一样。具体操作步骤如下步骤①在测试包test中创建专用的测试环境配置类Configuration public class MsgConfig {Beanpublic String msg(){return bean msg;} } 上述配置仅用于演示当前实验效果实际开发可不能这么注入String类型的数据步骤②在启动测试环境时导入测试环境专用的配置类使用Import注解即可实现SpringBootTest Import({MsgConfig.class}) public class ConfigurationTest {Autowiredprivate String msg;Testvoid testConfiguration(){System.out.println(msg);} } 到这里就通过Import属性实现了基于开发环境的配置基础上对配置进行测试环境的追加操作实现了11的配置环境效果。这样我们就可以实现每一个不同的测试用例加载不同的bean的效果丰富测试用例的编写同时不影响开发环境的配置。总结定义测试环境专用的配置类然后通过Import注解在具体的测试中导入临时的配置例如测试用例方便测试过程且上述配置不影响其他的测试类环境思考 当前我们已经可以实现业务层和数据层的测试并且通过临时配置控制每个测试用例加载不同的测试数据。但是实际企业开发不仅要保障业务层与数据层的功能安全有效也要保障表现层的功能正常。但是我们目的对表现层的测试都是通过postman手工测试的并没有在打包过程中体现表现层功能被测试通过。能否在测试用例中对表现层进行功能测试呢还真可以咱们下一节再讲。3-3.Web环境模拟测试 在测试中对表现层功能进行测试需要一个基础和一个功能。所谓的一个基础是运行测试程序时必须启动web环境不然没法测试web功能。一个功能是必须在测试程序中具备发送web请求的能力不然无法实现web功能的测试。所以在测试用例中测试表现层接口这项工作就转换成了两件事一如何在测试类中启动web测试二如何在测试类中发送web请求。下面一件事一件事进行先说第一个测试类中启动web环境 每一个springboot的测试类上方都会标准SpringBootTest注解而注解带有一个属性叫做webEnvironment。通过该属性就可以设置在测试用例中启动web环境具体如下SpringBootTest(webEnvironment SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebTest { } 测试类中启动web环境时可以指定启动的Web环境对应的端口springboot提供了4种设置值分别如下MOCK根据当前设置确认是否启动web环境例如使用了Servlet的API就启动web环境属于适配性的配置DEFINED_PORT使用自定义的端口作为web服务器端口RANDOM_PORT使用随机端口作为web服务器端口NONE不启动web环境 通过上述配置现在启动测试程序时就可以正常启用web环境了建议大家测试时使用RANDOM_PORT避免代码中因为写死设定引发线上功能打包测试时由于端口冲突导致意外现象的出现。就是说你程序中写了用8080端口结果线上环境8080端口被占用了结果你代码中所有写的东西都要改这就是写死代码的代价。现在你用随机端口就可以测试出来你有没有这种问题的隐患了。 测试环境中的web环境已经搭建好了下面就可以来解决第二个问题了如何在程序代码中发送web请求。测试类中发送请求 对于测试类中发送请求其实java的API就提供对应的功能只不过平时各位小伙伴接触的比较少所以较为陌生。springboot为了便于开发者进行对应的功能开发对其又进行了包装简化了开发步骤具体操作如下步骤①在测试类中开启web虚拟调用功能通过注解AutoConfigureMockMvc实现此功能的开启SpringBootTest(webEnvironment SpringBootTest.WebEnvironment.RANDOM_PORT) //开启虚拟MVC调用 AutoConfigureMockMvc public class WebTest { }步骤②定义发起虚拟调用的对象MockMVC通过自动装配的形式初始化对象SpringBootTest(webEnvironment SpringBootTest.WebEnvironment.RANDOM_PORT) //开启虚拟MVC调用 AutoConfigureMockMvc public class WebTest {Testvoid testWeb(Autowired MockMvc mvc) {} }步骤③创建一个虚拟请求对象封装请求的路径并使用MockMVC对象发送对应请求SpringBootTest(webEnvironment SpringBootTest.WebEnvironment.RANDOM_PORT) //开启虚拟MVC调用 AutoConfigureMockMvc public class WebTest {Testvoid testWeb(Autowired MockMvc mvc) throws Exception {//http://localhost:8080/books//创建虚拟请求当前访问/booksMockHttpServletRequestBuilder builder MockMvcRequestBuilders.get(/books);//执行对应的请求mvc.perform(builder);} } 执行测试程序现在就可以正常的发送/books对应的请求了注意访问路径不要写http://localhost:8080/books因为前面的服务器IP地址和端口使用的是当前虚拟的web环境无需指定仅指定请求的具体路径即可。总结在测试类中测试web层接口要保障测试类启动时启动web容器使用SpringBootTest注解的webEnvironment属性可以虚拟web环境用于测试为测试方法注入MockMvc对象通过MockMvc对象可以发送虚拟请求模拟web请求调用过程思考 目前已经成功的发送了请求但是还没有起到测试的效果测试过程必须出现预计值与真实值的比对结果才能确认测试结果是否通过虚拟请求中能对哪些请求结果进行比对呢咱们下一节再讲。web环境请求结果比对 上一节已经在测试用例中成功的模拟出了web环境并成功的发送了web请求本节就来解决发送请求后如何比对发送结果的问题。其实发完请求得到的信息只有一种就是响应对象。至于响应对象中包含什么就可以比对什么。常见的比对内容如下响应状态匹配Test void testStatus(Autowired MockMvc mvc) throws Exception {MockHttpServletRequestBuilder builder MockMvcRequestBuilders.get(/books);ResultActions action mvc.perform(builder);//设定预期值 与真实值进行比较成功测试通过失败测试失败//定义本次调用的预期值StatusResultMatchers status MockMvcResultMatchers.status();//预计本次调用时成功的状态200ResultMatcher ok status.isOk();//添加预计值到本次调用过程中进行匹配action.andExpect(ok); }响应体匹配非json数据格式Test void testBody(Autowired MockMvc mvc) throws Exception {MockHttpServletRequestBuilder builder MockMvcRequestBuilders.get(/books);ResultActions action mvc.perform(builder);//设定预期值 与真实值进行比较成功测试通过失败测试失败//定义本次调用的预期值ContentResultMatchers content MockMvcResultMatchers.content();ResultMatcher result content.string(springboot2);//添加预计值到本次调用过程中进行匹配action.andExpect(result); }响应体匹配json数据格式开发中的主流使用方式Test void testJson(Autowired MockMvc mvc) throws Exception {MockHttpServletRequestBuilder builder MockMvcRequestBuilders.get(/books);ResultActions action mvc.perform(builder);//设定预期值 与真实值进行比较成功测试通过失败测试失败//定义本次调用的预期值ContentResultMatchers content MockMvcResultMatchers.content();ResultMatcher result content.json({\id\:1,\name\:\springboot2\,\type\:\springboot\});//添加预计值到本次调用过程中进行匹配action.andExpect(result); }响应头信息匹配Test void testContentType(Autowired MockMvc mvc) throws Exception {MockHttpServletRequestBuilder builder MockMvcRequestBuilders.get(/books);ResultActions action mvc.perform(builder);//设定预期值 与真实值进行比较成功测试通过失败测试失败//定义本次调用的预期值HeaderResultMatchers header MockMvcResultMatchers.header();ResultMatcher contentType header.string(Content-Type, application/json);//添加预计值到本次调用过程中进行匹配action.andExpect(contentType); } 基本上齐了头信息正文信息状态信息都有了就可以组合出一个完美的响应结果比对结果了。以下范例就是三种信息同时进行匹配校验也是一个完整的信息匹配过程。Test void testGetById(Autowired MockMvc mvc) throws Exception {MockHttpServletRequestBuilder builder MockMvcRequestBuilders.get(/books);ResultActions action mvc.perform(builder);StatusResultMatchers status MockMvcResultMatchers.status();ResultMatcher ok status.isOk();action.andExpect(ok);HeaderResultMatchers header MockMvcResultMatchers.header();ResultMatcher contentType header.string(Content-Type, application/json);action.andExpect(contentType);ContentResultMatchers content MockMvcResultMatchers.content();ResultMatcher result content.json({\id\:1,\name\:\springboot\,\type\:\springboot\});action.andExpect(result); }总结web虚拟调用可以对本地虚拟请求的返回响应信息进行比对分为响应头信息比对、响应体信息比对、响应状态信息比对3-4.数据层测试回滚 当前我们的测试程序可以完美的进行表现层、业务层、数据层接口对应的功能测试了但是测试用例开发完成后在打包的阶段由于test生命周期属于必须被运行的生命周期如果跳过会给系统带来极高的安全隐患所以测试用例必须执行。但是新的问题就呈现了测试用例如果测试时产生了事务提交就会在测试过程中对数据库数据产生影响进而产生垃圾数据。这个过程不是我们希望发生的作为开发者测试用例该运行运行但是过程中产生的数据不要在我的系统中留痕这样该如何处理呢 springboot早就为开发者想到了这个问题并且针对此问题给出了最简解决方案在原始测试用例中添加注解Transactional即可实现当前测试用例的事务不提交。当程序运行后只要注解Transactional出现的位置存在注解SpringBootTestspringboot就会认为这是一个测试程序无需提交事务所以也就可以避免事务的提交。SpringBootTest Transactional Rollback(true) public class DaoTest {Autowiredprivate BookService bookService;Testvoid testSave(){Book book new Book();book.setName(springboot3);book.setType(springboot3);book.setDescription(springboot3);bookService.save(book);} } 如果开发者想提交事务也可以再添加一个RollBack的注解设置回滚状态为false即可正常提交事务是不是很方便springboot在辅助开发者日常工作这一块展现出了惊人的能力实在太贴心了。总结在springboot的测试类中通过添加注解Transactional来阻止测试用例提交事务通过注解Rollback控制springboot测试类执行结果是否提交事务需要配合注解Transactional使用思考 当前测试程序已经近乎完美了但是由于测试用例中书写的测试数据属于固定数据往往失去了测试的意义开发者可以针对测试用例进行针对性开发这样就有可能出现测试用例不能完美呈现业务逻辑代码是否真实有效的达成业务目标的现象解决方案其实很容易想测试用例的数据只要随机产生就可以了能实现吗咱们下一节再讲。3-5.测试用例数据设定 对于测试用例的数据固定书写肯定是不合理的springboot提供了在配置中使用随机值的机制确保每次运行程序加载的数据都是随机的。具体如下testcase:book:id: ${random.int}id2: ${random.int(10)}type: ${random.int!5,10!}name: ${random.value}uuid: ${random.uuid}publishTime: ${random.long} 当前配置就可以在每次运行程序时创建一组随机数据避免每次运行时数据都是固定值的尴尬现象发生有助于测试功能的进行。数据的加载按照之前加载数据的形式使用ConfigurationProperties注解即可Component Data ConfigurationProperties(prefix testcase.book) public class BookCase {private int id;private int id2;private int type;private String name;private String uuid;private long publishTime; } 对于随机值的产生还有一些小的限定规则比如产生的数值性数据可以设置范围等具体如下${random.int}表示随机整数${random.int(10)}表示10以内的随机数${random.int(10,20)}表示10到20的随机数其中()可以是任意字符例如[]!!均可总结使用随机数据可以替换测试用例中书写的固定数据提高测试用例中的测试数据有效性4.数据层解决方案 开发实用篇前三章基本上是开胃菜从第四章开始开发实用篇进入到了噩梦难度了从这里开始不再是单纯的在springboot内部搞事情了要涉及到很多相关知识。本章节主要内容都是和数据存储与读取相关前期学习的知识与数据层有关的技术基本上都围绕在数据库这个层面上所以本章要讲的第一个大的分支就是SQL解决方案相关的内容除此之外数据的来源还可以是非SQL技术相关的数据操作因此第二部分围绕着NOSQL解决方案讲解。至于什么是NOSQL解决方案讲到了再说吧。下面就从SQL解决方案说起。4-1.SQL 回忆一下之前做SSMP整合的时候数据层解决方案涉及到了哪些技术MySQL数据库与MyBatisPlus框架后面又学了Druid数据源的配置所以现在数据层解决方案可以说是MysqlDruidMyBatisPlus。而三个技术分别对应了数据层操作的三个层面数据源技术Druid持久化技术MyBatisPlus数据库技术MySQL 下面的研究就分为三个层面进行研究对应上面列出的三个方面咱们就从第一个数据源技术开始说起。数据源技术 目前我们使用的数据源技术是Druid运行时可以在日志中看到对应的数据源初始化信息具体如下INFO 28600 --- [ main] c.a.d.s.b.a.DruidDataSourceAutoConfigure : Init DruidDataSource INFO 28600 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited 如果不使用Druid数据源程序运行后是什么样子呢是独立的数据库连接对象还是有其他的连接池技术支持呢将Druid技术对应的starter去掉再次运行程序可以在日志中找到如下初始化信息INFO 31820 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... INFO 31820 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 虽然没有DruidDataSource相关的信息了但是我们发现日志中有HikariDataSource这个信息就算不懂这是个什么技术看名字也能看出来以DataSource结尾的名称这一定是一个数据源技术。我们又没有手工添加这个技术这个技术哪里来的呢这就是这一节要讲的知识springboot内嵌数据源。 数据层技术是每一个企业级应用程序都会用到的而其中必定会进行数据库连接的管理。springboot根据开发者的习惯出发开发者提供了数据源技术就用你提供的开发者没有提供那总不能手工管理一个一个的数据库连接对象啊怎么办我给你一个默认的就好了这样省心又省事大家都方便。 springboot提供了3款内嵌数据源技术分别如下HikariCPTomcat提供DataSourceCommons DBCP 第一种HikartCP这是springboot官方推荐的数据源技术作为默认内置数据源使用。啥意思你不配置数据源那就用这个。 第二种Tomcat提供的DataSource如果不想用HikartCP并且使用tomcat作为web服务器进行web程序的开发使用这个。为什么是Tomcat不是其他web服务器呢因为web技术导入starter后默认使用内嵌tomcat既然都是默认使用的技术了那就一用到底数据源也用它的。有人就提出怎么才能不使用HikartCP用tomcat提供的默认数据源对象呢把HikartCP技术的坐标排除掉就OK了。 第三种DBCP这个使用的条件就更苛刻了既不使用HikartCP也不使用tomcat的DataSource时默认给你用这个。 springboot这心操的也是稀碎啊就怕你自己管不好连接对象给你一顿推荐真是开发界的最强辅助。既然都给你奶上了那就受用吧怎么配置使用这些东西呢之前我们配置druid时使用druid的starter对应的配置如下spring:datasource:druid: url: jdbc:mysql://localhost:3306/ssm_db?serverTimezoneUTCdriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: root 换成是默认的数据源HikariCP后直接吧druid删掉就行了如下spring:datasource:url: jdbc:mysql://localhost:3306/ssm_db?serverTimezoneUTCdriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: root 当然也可以写上是对hikari做的配置但是url地址要单独配置如下spring:datasource:url: jdbc:mysql://localhost:3306/ssm_db?serverTimezoneUTChikari:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: root 这就是配置hikari数据源的方式。如果想对hikari做进一步的配置可以继续配置其独立的属性。例如spring:datasource:url: jdbc:mysql://localhost:3306/ssm_db?serverTimezoneUTChikari:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: rootmaximum-pool-size: 50 如果不想使用hikari数据源使用tomcat的数据源或者DBCP配置格式也是一样的。学习到这里以后我们做数据层时数据源对象的选择就不再是单一的使用druid数据源技术了可以根据需要自行选择。总结springboot技术提供了3种内置的数据源技术分别是Hikari、tomcat内置数据源、DBCP持久化技术 说完数据源解决方案再来说一下持久化解决方案。springboot充分发挥其最强辅助的特征给开发者提供了一套现成的数据层技术叫做JdbcTemplate。其实这个技术不能说是springboot提供的因为不使用springboot技术一样能使用它谁提供的呢spring技术提供的所以在springboot技术范畴中这个技术也是存在的毕竟springboot技术是加速spring程序开发而创建的。 这个技术其实就是回归到jdbc最原始的编程形式来进行数据层的开发下面直接上操作步骤步骤①导入jdbc对应的坐标记得是starterdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId /dependency步骤②自动装配JdbcTemplate对象SpringBootTest class Springboot15SqlApplicationTests {Testvoid testJdbcTemplate(Autowired JdbcTemplate jdbcTemplate){} }步骤③使用JdbcTemplate实现查询操作非实体类封装数据的查询操作Test void testJdbcTemplate(Autowired JdbcTemplate jdbcTemplate){String sql select * from tbl_book;ListMapString, Object maps jdbcTemplate.queryForList(sql);System.out.println(maps); }步骤④使用JdbcTemplate实现查询操作实体类封装数据的查询操作Test void testJdbcTemplate(Autowired JdbcTemplate jdbcTemplate){String sql select * from tbl_book;RowMapperBook rm new RowMapperBook() {Overridepublic Book mapRow(ResultSet rs, int rowNum) throws SQLException {Book temp new Book();temp.setId(rs.getInt(id));temp.setName(rs.getString(name));temp.setType(rs.getString(type));temp.setDescription(rs.getString(description));return temp;}};ListBook list jdbcTemplate.query(sql, rm);System.out.println(list); }步骤⑤使用JdbcTemplate实现增删改操作Test void testJdbcTemplateSave(Autowired JdbcTemplate jdbcTemplate){String sql insert into tbl_book values(3,springboot1,springboot2,springboot3);jdbcTemplate.update(sql); } 如果想对JdbcTemplate对象进行相关配置可以在yml文件中进行设定具体如下spring:jdbc:template:query-timeout: -1 # 查询超时时间max-rows: 500 # 最大行数fetch-size: -1 # 缓存行数总结SpringBoot内置JdbcTemplate持久化解决方案使用JdbcTemplate需要导入spring-boot-starter-jdbc的坐标数据库技术 截止到目前springboot给开发者提供了内置的数据源解决方案和持久化解决方案在数据层解决方案三件套中还剩下一个数据库莫非springboot也提供有内置的解决方案还真有还不是一个三个这一节就来说说内置的数据库解决方案。 springboot提供了3款内置的数据库分别是H2HSQLDerby 以上三款数据库除了可以独立安装之外还可以像是tomcat服务器一样采用内嵌的形式运行在spirngboot容器中。内嵌在容器中运行那必须是java对象啊对这三款数据库底层都是使用java语言开发的。 我们一直使用MySQL数据库就挺好的为什么有需求用这个呢原因就在于这三个数据库都可以采用内嵌容器的形式运行在应用程序运行后如果我们进行测试工作此时测试的数据无需存储在磁盘上但是又要测试使用内嵌数据库就方便了运行在内存中该测试测试该运行运行等服务器关闭后一切烟消云散多好省得你维护外部数据库了。这也是内嵌数据库的最大优点方便进行功能测试。 下面以H2数据库为例讲解如何使用这些内嵌数据库操作步骤也非常简单简单才好用嘛步骤①导入H2数据库对应的坐标一共2个dependencygroupIdcom.h2database/groupIdartifactIdh2/artifactId /dependency dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId /dependency步骤②将工程设置为web工程启动工程时启动H2数据库dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId /dependency步骤③通过配置开启H2数据库控制台访问程序也可以使用其他的数据库连接软件操作spring:h2:console:enabled: truepath: /h2 web端访问路径/h2访问密码123456如果访问失败先配置下列数据源启动程序运行后再次访问/h2路径就可以正常访问了datasource:url: jdbc:h2:~/testhikari:driver-class-name: org.h2.Driverusername: sapassword: 123456步骤④使用JdbcTemplate或MyBatisPlus技术操作数据库略 其实我们只是换了一个数据库而已其他的东西都不受影响。一个重要提醒别忘了上线时把内存级数据库关闭采用MySQL数据库作为数据持久化方案关闭方式就是设置enabled属性为false即可。总结H2内嵌式数据库启动方式添加坐标添加配置H2数据库线上运行时请务必关闭 到这里SQL相关的数据层解决方案就讲完了现在的可选技术就丰富的多了。数据源技术Druid、Hikari、tomcat DataSource、DBCP持久化技术MyBatisPlus、MyBatis、JdbcTemplate数据库技术MySQL、H2、HSQL、Derby 现在开发程序时就可以在以上技术中任选一种组织成一套数据库解决方案了。4-2.NoSQL SQL数据层解决方案说完了下面来说收NoSQL数据层解决方案。这个NoSQL是什么意思呢从字面来看No表示否定NoSQL就是非关系型数据库解决方案意思就是数据该存存该取取只是这些数据不放在关系型数据库中了那放在哪里自然是一些能够存储数据的其他相关技术中了比如Redis等。本节讲解的内容就是springboot如何整合这些技术在springboot官方文档中提供了10种相关技术的整合方案我们将讲解国内市场上最流行的几款NoSQL数据库整合方案分别是Redis、MongoDB、ES。 因为每个小伙伴学习这门课程的时候起点不同为了便于各位学习者更好的学习每种技术在讲解整合前都会先讲一下安装和基本使用然后再讲整合。如果对某个技术比较熟悉的小伙伴可以直接跳过安装的学习过程直接看整合方案即可。此外上述这些技术最佳使用方案都是在Linux服务器上部署但是考虑到各位小伙伴的学习起点差异过大所以下面的课程都是以Windows平台作为安装基础讲解如果想看Linux版软件安装可以再找到对应技术的学习文档查阅学习。SpringBoot整合Redis Redis是一款采用key-value数据存储格式的内存级NoSQL数据库重点关注数据存储格式是key-value格式也就是键值对的存储形式。与MySQL数据库不同MySQL数据库有表、有字段、有记录Redis没有这些东西就是一个名称对应一个值并且数据以存储在内存中使用为主。什么叫以存储在内存中为主其实Redis有它的数据持久化方案分别是RDB和AOF但是Redis自身并不是为了数据持久化而生的主要是在内存中保存数据加速数据访问的所以说是一款内存级数据库。 Redis支持多种数据存储格式比如可以直接存字符串也可以存一个map集合list集合后面会涉及到一些不同格式的数据操作这个需要先学习一下才能进行整合所以在基本操作中会介绍一些相关操作。下面就先安装再操作最后说整合安装 windows版安装包下载地址https://github.com/tporadowski/redis/releases 下载的安装包有两种形式一种是一键安装的msi文件还有一种是解压缩就能使用的zip文件哪种形式都行这里就不介绍安装过程了本课程采用的是msi一键安装的msi文件进行安装的。 啥是msi其实就是一个文件安装包不仅安装软件还帮你把安装软件时需要的功能关联在一起打包操作。比如如安装序列、创建和设置安装路径、设置系统依赖项、默认设定安装选项和控制安装过程的属性。说简单点就是一站式服务安装过程一条龙操作一气呵成就是为小白用户提供的软件安装程序。 安装完毕后会得到如下文件其中有两个文件对应两个命令是启动Redis的核心命令需要再CMD命令行模式执行。启动服务器redis-server.exe redis.windows.conf 初学者无需调整服务器对外服务端口默认6379。启动客户端redis-cli.exe 如果启动redis服务器失败可以先启动客户端然后执行shutdown操作后退出此时redis服务器就可以正常执行了。基本操作 服务器启动后使用客户端就可以连接服务器类似于启动完MySQL数据库然后启动SQL命令行操作数据库。 放置一个字符串数据到redis中先为数据定义一个名称比如name,age等然后使用命令set设置数据到redis服务器中即可set name itheima set age 12 从redis中取出已经放入的数据根据名称取就可以得到对应数据。如果没有对应数据就会得到(nil)get name get age 以上使用的数据存储是一个名称对应一个值如果要维护的数据过多可以使用别的数据存储结构。例如hash它是一种一个名称下可以存储多个数据的存储模型并且每个数据也可以有自己的二级存储名称。向hash结构中存储数据格式如下hset a a1 aa1 #对外key名称是a在名称为a的存储模型中a1这个key中保存了数据aa1 hset a a2 aa2 获取hash结构中的数据命令如下hget a a1 #得到aa1 hget a a2 #得到aa2 有关redis的基础操作就普及到这里需要全面掌握redis技术请参看相关教程学习。整合 在进行整合之前先梳理一下整合的思想springboot整合任何技术其实就是在springboot中使用对应技术的API。如果两个技术没有交集就不存在整合的概念了。所谓整合其实就是使用springboot技术去管理其他技术几个问题是躲不掉的。 第一需要先导入对应技术的坐标而整合之后这些坐标都有了一些变化 第二任何技术通常都会有一些相关的设置信息整合之后这些信息如何写写在哪是一个问题 第三没有整合之前操作如果是模式A的话整合之后如果没有给开发者带来一些便捷操作那整合将毫无意义所以整合后操作肯定要简化一些那对应的操作方式自然也有所不同 按照上面的三个问题去思考springboot整合所有技术是一种通用思想在整合的过程中会逐步摸索出整合的套路而且适用性非常强经过若干种技术的整合后基本上可以总结出一套固定思维。 下面就开始springboot整合redis操作步骤如下步骤①导入springboot整合redis的starter坐标dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId /dependency 上述坐标可以在创建模块的时候通过勾选的形式进行选择归属NoSQL分类中步骤②进行基础配置spring:redis:host: localhostport: 6379 操作redis最基本的信息就是操作哪一台redis服务器所以服务器地址属于基础配置信息不可缺少。但是即便你不配置目前也是可以用的。因为以上两组信息都有默认配置刚好就是上述配置值。步骤③使用springboot整合redis的专用客户端接口操作此处使用的是RedisTemplateSpringBootTest class Springboot16RedisApplicationTests {Autowiredprivate RedisTemplate redisTemplate;Testvoid set() {ValueOperations ops redisTemplate.opsForValue();ops.set(age,41);}Testvoid get() {ValueOperations ops redisTemplate.opsForValue();Object age ops.get(name);System.out.println(age);}Testvoid hset() {HashOperations ops redisTemplate.opsForHash();ops.put(info,b,bb);}Testvoid hget() {HashOperations ops redisTemplate.opsForHash();Object val ops.get(info, b);System.out.println(val);} } 在操作redis时需要先确认操作何种数据根据数据种类得到操作接口。例如使用opsForValue()获取string类型的数据操作接口使用opsForHash()获取hash类型的数据操作接口剩下的就是调用对应api操作了。各种类型的数据操作接口如下总结springboot整合redis步骤导入springboot整合redis的starter坐标进行基础配置使用springboot整合redis的专用客户端接口RedisTemplate操作StringRedisTemplate 由于redis内部不提供java对象的存储格式因此当操作的数据以对象的形式存在时会进行转码转换成字符串格式后进行操作。为了方便开发者使用基于字符串为数据的操作springboot整合redis时提供了专用的API接口StringRedisTemplate你可以理解为这是RedisTemplate的一种指定数据泛型的操作API。SpringBootTest public class StringRedisTemplateTest {Autowiredprivate StringRedisTemplate stringRedisTemplate;Testvoid get(){ValueOperationsString, String ops stringRedisTemplate.opsForValue();String name ops.get(name);System.out.println(name);} }redis客户端选择springboot整合redis技术提供了多种客户端兼容模式默认提供的是lettucs客户端技术也可以根据需要切换成指定客户端技术例如jedis客户端技术切换成jedis客户端技术操作步骤如下步骤①导入jedis坐标dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactId /dependency jedis坐标受springboot管理无需提供版本号步骤②配置客户端技术类型设置为jedisspring:redis:host: localhostport: 6379client-type: jedis步骤③根据需要设置对应的配置spring:redis:host: localhostport: 6379client-type: jedislettuce:pool:max-active: 16jedis:pool:max-active: 16lettcus与jedis区别jedis连接Redis服务器是直连模式当多线程模式下使用jedis会存在线程安全问题解决方案可以通过配置连接池使每个连接专用这样整体性能就大受影响lettcus基于Netty框架进行与Redis服务器连接底层设计中采用StatefulRedisConnection。 StatefulRedisConnection自身是线程安全的可以保障并发访问安全问题所以一个连接可以被多线程复用。当然lettcus也支持多连接实例一起工作总结springboot整合redis提供了StringRedisTemplate对象以字符串的数据格式操作redis如果需要切换redis客户端实现技术可以通过配置的形式进行SpringBoot整合ES 下面是一款NoSQL解决方案只不过他的作用不是为了直接加速数据的读写而是加速数据的查询的叫做ES技术。 ESElasticsearch是一个分布式全文搜索引擎重点是全文搜索。 那什么是全文搜索呢比如用户要买一本书以Java为关键字进行搜索不管是书名中还是书的介绍中甚至是书的作者名字只要包含java就作为查询结果返回给用户查看上述过程就使用了全文搜索技术。搜索的条件不再是仅用于对某一个字段进行比对而是在一条数据中使用搜索条件去比对更多的字段只要能匹配上就列入查询结果这就是全文搜索的目的。而ES技术就是一种可以实现上述效果的技术。 要实现全文搜索的效果不可能使用数据库中like操作去进行比对这种效率太低了。ES设计了一种全新的思想来实现全文搜索。具体操作过程如下将被查询的字段的数据全部文本信息进行查分分成若干个词例如“中华人民共和国”就会被拆分成三个词分别是“中华”、“人民”、“共和国”此过程有专业术语叫做分词。分词的策略不同分出的效果不一样不同的分词策略称为分词器。将分词得到的结果存储起来对应每条数据的id例如id为1的数据中名称这一项的值是“中华人民共和国”那么分词结束后就会出现“中华”对应id为1“人民”对应id为1“共和国”对应id为1例如id为2的数据中名称这一项的值是“人民代表大会“那么分词结束后就会出现“人民”对应id为2“代表”对应id为2“大会”对应id为2此时就会出现如下对应结果按照上述形式可以对所有文档进行分词。需要注意分词的过程不是仅对一个字段进行而是对每一个参与查询的字段都执行最终结果汇总到一个表格中分词结果关键字对应id中华1人民1,2共和国1代表2大会2当进行查询时如果输入“人民”作为查询条件可以通过上述表格数据进行比对得到id值1,2然后根据id值就可以得到查询的结果数据了。 上述过程中分词结果关键字内容每一个都不相同作用有点类似于数据库中的索引是用来加速数据查询的。但是数据库中的索引是对某一个字段进行添加索引而这里的分词结果关键字不是一个完整的字段值只是一个字段中的其中的一部分内容。并且索引使用时是根据索引内容查找整条数据全文搜索中的分词结果关键字查询后得到的并不是整条的数据而是数据的id要想获得具体数据还要再次查询因此这里为这种分词结果关键字起了一个全新的名称叫做倒排索引。 通过上述内容的学习发现使用ES其实准备工作还是挺多的必须先建立文档的倒排索引然后才能继续使用。快速了解一下ES的工作原理下面直接开始我们的学习老规矩先安装再操作最后说整合。安装 windows版安装包下载地址https://www.elastic.co/cn/downloads/elasticsearch 下载的安装包是解压缩就能使用的zip文件解压缩完毕后会得到如下文件bin目录包含所有的可执行命令config目录包含ES服务器使用的配置文件jdk目录此目录中包含了一个完整的jdk工具包版本17当ES升级时使用最新版本的jdk确保不会出现版本支持性不足的问题lib目录包含ES运行的依赖jar文件logs目录包含ES运行后产生的所有日志文件modules目录包含ES软件中所有的功能模块也是一个一个的jar包。和jar目录不同jar目录是ES运行期间依赖的jar包modules是ES软件自己的功能jar包plugins目录包含ES软件安装的插件默认为空启动服务器elasticsearch.bat 双击elasticsearch.bat文件即可启动ES服务器默认服务端口9200。通过浏览器访问http://localhost:9200看到如下信息视为ES服务器正常启动{name : CZBK-**********,cluster_name : elasticsearch,cluster_uuid : j137DSswTPG8U4Yb-0T1Mg,version : {number : 7.16.2,build_flavor : default,build_type : zip,build_hash : 2b937c44140b6559905130a8650c64dbd0879cfb,build_date : 2021-12-18T19:42:46.604893745Z,build_snapshot : false,lucene_version : 8.10.1,minimum_wire_compatibility_version : 6.8.0,minimum_index_compatibility_version : 6.0.0-beta1},tagline : You Know, for Search }基本操作 ES中保存有我们要查询的数据只不过格式和数据库存储数据格式不同而已。在ES中我们要先创建倒排索引这个索引的功能又点类似于数据库的表然后将数据添加到倒排索引中添加的数据称为文档。所以要进行ES的操作要先创建索引再添加文档这样才能进行后续的查询操作。 要操作ES可以通过Rest风格的请求来进行也就是说发送一个请求就可以执行一个操作。比如新建索引删除索引这些操作都可以使用发送请求的形式来进行。创建索引books是索引名称下同PUT请求 http://localhost:9200/books发送请求后看到如下信息即索引创建成功{acknowledged: true,shards_acknowledged: true,index: books }重复创建已经存在的索引会出现错误信息reason属性中描述错误原因{error: {root_cause: [{type: resource_already_exists_exception,reason: index [books/VgC_XMVAQmedaiBNSgO2-w] already exists,index_uuid: VgC_XMVAQmedaiBNSgO2-w,index: books}],type: resource_already_exists_exception,reason: index [books/VgC_XMVAQmedaiBNSgO2-w] already exists, # books索引已经存在index_uuid: VgC_XMVAQmedaiBNSgO2-w,index: book},status: 400 }查询索引GET请求 http://localhost:9200/books查询索引得到索引相关信息如下{book: {aliases: {},mappings: {},settings: {index: {routing: {allocation: {include: {_tier_preference: data_content}}},number_of_shards: 1,provided_name: books,creation_date: 1645768584849,number_of_replicas: 1,uuid: VgC_XMVAQmedaiBNSgO2-w,version: {created: 7160299}}}} }如果查询了不存在的索引会返回错误信息例如查询名称为book的索引后信息如下{error: {root_cause: [{type: index_not_found_exception,reason: no such index [book],resource.type: index_or_alias,resource.id: book,index_uuid: _na_,index: book}],type: index_not_found_exception,reason: no such index [book], # 没有book索引resource.type: index_or_alias,resource.id: book,index_uuid: _na_,index: book},status: 404 }删除索引DELETE请求 http://localhost:9200/books删除所有后给出删除结果{acknowledged: true }如果重复删除会给出错误信息同样在reason属性中描述具体的错误原因{error: {root_cause: [{type: index_not_found_exception,reason: no such index [books],resource.type: index_or_alias,resource.id: book,index_uuid: _na_,index: book}],type: index_not_found_exception,reason: no such index [books], # 没有books索引resource.type: index_or_alias,resource.id: book,index_uuid: _na_,index: book},status: 404 }创建索引并指定分词器 前面创建的索引是未指定分词器的可以在创建索引时添加请求参数设置分词器。目前国内较为流行的分词器是IK分词器使用前先在下对应的分词器然后使用。IK分词器下载地址https://github.com/medcl/elasticsearch-analysis-ik/releases 分词器下载后解压到ES安装目录的plugins目录中即可安装分词器后需要重新启动ES服务器。使用IK分词器创建索引格式PUT请求 http://localhost:9200/books请求参数如下注意是json格式的参数 {mappings:{ #定义mappings属性替换创建索引时对应的mappings属性 properties:{ #定义索引中包含的属性设置id:{ #设置索引中包含id属性type:keyword #当前属性可以被直接搜索},name:{ #设置索引中包含name属性type:text, #当前属性是文本信息参与分词 analyzer:ik_max_word, #使用IK分词器进行分词 copy_to:all #分词结果拷贝到all属性中},type:{type:keyword},description:{type:text, analyzer:ik_max_word, copy_to:all},all:{ #定义属性用来描述多个字段的分词结果集合当前属性可以参与查询type:text, analyzer:ik_max_word}}} } 创建完毕后返回结果和不使用分词器创建索引的结果是一样的此时可以通过查看索引信息观察到添加的请求参数mappings已经进入到了索引属性中{books: {aliases: {},mappings: { #mappings属性已经被替换properties: {all: {type: text,analyzer: ik_max_word},description: {type: text,copy_to: [all],analyzer: ik_max_word},id: {type: keyword},name: {type: text,copy_to: [all],analyzer: ik_max_word},type: {type: keyword}}},settings: {index: {routing: {allocation: {include: {_tier_preference: data_content}}},number_of_shards: 1,provided_name: books,creation_date: 1645769809521,number_of_replicas: 1,uuid: DohYKvr_SZO4KRGmbZYmTQ,version: {created: 7160299}}}} }目前我们已经有了索引了但是索引中还没有数据所以要先添加数据ES中称数据为文档下面进行文档操作。添加文档有三种方式POST请求 http://localhost:9200/books/_doc #使用系统生成id POST请求 http://localhost:9200/books/_create/1 #使用指定id POST请求 http://localhost:9200/books/_doc/1 #使用指定id不存在创建存在更新版本递增文档通过请求参数传递数据格式json {name:springboot,type:springboot,description:springboot } 查询文档GET请求 http://localhost:9200/books/_doc/1 #查询单个文档 GET请求 http://localhost:9200/books/_search #查询全部文档条件查询GET请求 http://localhost:9200/books/_search?qname:springboot # q查询属性名:查询属性值删除文档DELETE请求 http://localhost:9200/books/_doc/1修改文档全量更新PUT请求 http://localhost:9200/books/_doc/1文档通过请求参数传递数据格式json {name:springboot,type:springboot,description:springboot }修改文档部分更新POST请求 http://localhost:9200/books/_update/1文档通过请求参数传递数据格式json { doc:{ #部分更新并不是对原始文档进行更新而是对原始文档对象中的doc属性中的指定属性更新name:springboot #仅更新提供的属性值未提供的属性值不参与更新操作} }整合 使用springboot整合ES该如何进行呢老规矩导入坐标做配置使用API接口操作。整合Redis如此整合MongoDB如此整合ES依然如此。太没有新意了其实不是没有新意这就是springboot的强大之处所有东西都做成相同规则对开发者来说非常友好。 下面就开始springboot整合ES操作步骤如下步骤①导入springboot整合ES的starter坐标dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-elasticsearch/artifactId /dependency步骤②进行基础配置spring:elasticsearch:rest:uris: http://localhost:9200 配置ES服务器地址端口9200步骤③使用springboot整合ES的专用客户端接口ElasticsearchRestTemplate来进行操作SpringBootTest class Springboot18EsApplicationTests {Autowiredprivate ElasticsearchRestTemplate template; } 上述操作形式是ES早期的操作方式使用的客户端被称为Low Level Client这种客户端操作方式性能方面略显不足于是ES开发了全新的客户端操作方式称为High Level Client。高级别客户端与ES版本同步更新但是springboot最初整合ES的时候使用的是低级别客户端所以企业开发需要更换成高级别的客户端模式。 下面使用高级别客户端方式进行springboot整合ES操作步骤如下步骤①导入springboot整合ES高级别客户端的坐标此种形式目前没有对应的starterdependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactId /dependency步骤②使用编程的形式设置连接的ES服务器并获取客户端对象SpringBootTest class Springboot18EsApplicationTests {private RestHighLevelClient client;Testvoid testCreateClient() throws IOException {HttpHost host HttpHost.create(http://localhost:9200);RestClientBuilder builder RestClient.builder(host);client new RestHighLevelClient(builder);client.close();} } 配置ES服务器地址与端口9200记得客户端使用完毕需要手工关闭。由于当前客户端是手工维护的因此不能通过自动装配的形式加载对象。步骤③使用客户端对象操作ES例如创建索引SpringBootTest class Springboot18EsApplicationTests {private RestHighLevelClient client;Testvoid testCreateIndex() throws IOException {HttpHost host HttpHost.create(http://localhost:9200);RestClientBuilder builder RestClient.builder(host);client new RestHighLevelClient(builder);CreateIndexRequest request new CreateIndexRequest(books);client.indices().create(request, RequestOptions.DEFAULT); client.close();} } 高级别客户端操作是通过发送请求的方式完成所有操作的ES针对各种不同的操作设定了各式各样的请求对象上例中创建索引的对象是CreateIndexRequest其他操作也会有自己专用的Request对象。 当前操作我们发现无论进行ES何种操作第一步永远是获取RestHighLevelClient对象最后一步永远是关闭该对象的连接。在测试中可以使用测试类的特性去帮助开发者一次性的完成上述操作但是在业务书写时还需要自行管理。将上述代码格式转换成使用测试类的初始化方法和销毁方法进行客户端对象的维护。SpringBootTest class Springboot18EsApplicationTests {BeforeEach //在测试类中每个操作运行前运行的方法void setUp() {HttpHost host HttpHost.create(http://localhost:9200);RestClientBuilder builder RestClient.builder(host);client new RestHighLevelClient(builder);}AfterEach //在测试类中每个操作运行后运行的方法void tearDown() throws IOException {client.close();}private RestHighLevelClient client;Testvoid testCreateIndex() throws IOException {CreateIndexRequest request new CreateIndexRequest(books);client.indices().create(request, RequestOptions.DEFAULT);} } 现在的书写简化了很多也更合理。下面使用上述模式将所有的ES操作执行一遍测试结果创建索引IK分词器Test void testCreateIndexByIK() throws IOException {CreateIndexRequest request new CreateIndexRequest(books);String json {\n \mappings\:{\n \properties\:{\n \id\:{\n \type\:\keyword\\n },\n \name\:{\n \type\:\text\,\n \analyzer\:\ik_max_word\,\n \copy_to\:\all\\n },\n \type\:{\n \type\:\keyword\\n },\n \description\:{\n \type\:\text\,\n \analyzer\:\ik_max_word\,\n \copy_to\:\all\\n },\n \all\:{\n \type\:\text\,\n \analyzer\:\ik_max_word\\n }\n }\n }\n };//设置请求中的参数request.source(json, XContentType.JSON);client.indices().create(request, RequestOptions.DEFAULT); } IK分词器是通过请求参数的形式进行设置的设置请求参数使用request对象中的source方法进行设置至于参数是什么取决于你的操作种类。当请求中需要参数时均可使用当前形式进行参数设置。添加文档Test //添加文档 void testCreateDoc() throws IOException {Book book bookDao.selectById(1);IndexRequest request new IndexRequest(books).id(book.getId().toString());String json JSON.toJSONString(book);request.source(json,XContentType.JSON);client.index(request,RequestOptions.DEFAULT); } 添加文档使用的请求对象是IndexRequest与创建索引使用的请求对象不同。批量添加文档Test //批量添加文档 void testCreateDocAll() throws IOException {ListBook bookList bookDao.selectList(null);BulkRequest bulk new BulkRequest();for (Book book : bookList) {IndexRequest request new IndexRequest(books).id(book.getId().toString());String json JSON.toJSONString(book);request.source(json,XContentType.JSON);bulk.add(request);}client.bulk(bulk,RequestOptions.DEFAULT); } 批量做时先创建一个BulkRequest的对象可以将该对象理解为是一个保存request对象的容器将所有的请求都初始化好后添加到BulkRequest对象中再使用BulkRequest对象的bulk方法一次性执行完毕。按id查询文档Test //按id查询 void testGet() throws IOException {GetRequest request new GetRequest(books,1);GetResponse response client.get(request, RequestOptions.DEFAULT);String json response.getSourceAsString();System.out.println(json); } 根据id查询文档使用的请求对象是GetRequest。按条件查询文档Test //按条件查询 void testSearch() throws IOException {SearchRequest request new SearchRequest(books);SearchSourceBuilder builder new SearchSourceBuilder();builder.query(QueryBuilders.termQuery(all,spring));request.source(builder);SearchResponse response client.search(request, RequestOptions.DEFAULT);SearchHits hits response.getHits();for (SearchHit hit : hits) {String source hit.getSourceAsString();//System.out.println(source);Book book JSON.parseObject(source, Book.class);System.out.println(book);} } 按条件查询文档使用的请求对象是SearchRequest查询时调用SearchRequest对象的termQuery方法需要给出查询属性名此处支持使用合并字段也就是前面定义索引属性时添加的all属性。 springboot整合ES的操作到这里就说完了与前期进行springboot整合redis和mongodb的差别还是蛮大的主要原始就是我们没有使用springboot整合ES的客户端对象。至于操作由于ES操作种类过多所以显得操作略微有点复杂。有关springboot整合ES就先学习到这里吧。总结springboot整合ES步骤导入springboot整合ES的High Level Client坐标手工管理客户端对象包括初始化和关闭操作使用High Level Client根据操作的种类不同选择不同的Request对象完成对应操作5.整合第三方技术 通过第四章的学习我们领略到了springboot在整合第三方技术时强大的一致性在第五章中我们要使用springboot继续整合各种各样的第三方技术通过本章的学习可以将之前学习的springboot整合第三方技术的思想贯彻到底还是那三板斧。导坐标、做配置、调API。 springboot能够整合的技术实在是太多了可以说是万物皆可整。本章将从企业级开发中常用的一些技术作为出发点对各种各样的技术进行整合。5-1.缓存 企业级应用主要作用是信息处理当需要读取数据时由于受限于数据库的访问效率导致整体系统性能偏低。应用程序直接与数据库打交道访问效率低。为了改善上述现象开发者通常会在应用程序与数据库之间建立一种临时的数据存储机制该区域中的数据在内存中保存读写速度较快可以有效解决数据库访问效率低下的问题。这一块临时存储数据的区域就是缓存。 使用缓存后应用程序与缓存打交道缓存与数据库打交道数据访问效率提高 缓存是什么缓存是一种介于数据永久存储介质与应用程序之间的数据临时存储介质使用缓存可以有效的减少低速数据读取过程的次数例如磁盘IO提高系统性能。此外缓存不仅可以用于提高永久性存储介质的数据读取效率还可以提供临时的数据存储空间。而springboot提供了对市面上几乎所有的缓存技术进行整合的方案下面就一起开启springboot整合缓存之旅。SpringBoot内置缓存解决方案 springboot技术提供有内置的缓存解决方案可以帮助开发者快速开启缓存技术并使用缓存技术进行数据的快速操作例如读取缓存数据和写入数据到缓存。步骤①导入springboot提供的缓存技术对应的starterdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-cache/artifactId /dependency步骤②启用缓存在引导类上方标注注解EnableCaching配置springboot程序中可以使用缓存SpringBootApplication //开启缓存功能 EnableCaching public class Springboot19CacheApplication {public static void main(String[] args) {SpringApplication.run(Springboot19CacheApplication.class, args);} }步骤③设置操作的数据是否使用缓存Service public class BookServiceImpl implements BookService {Autowiredprivate BookDao bookDao;Cacheable(valuecacheSpace,key#id)public Book getById(Integer id) {return bookDao.selectById(id);} } 在业务方法上面使用注解Cacheable声明当前方法的返回值放入缓存中其中要指定缓存的存储位置以及缓存中保存当前方法返回值对应的名称。上例中value属性描述缓存的存储位置可以理解为是一个存储空间名key属性描述了缓存中保存数据的名称使用#id读取形参中的id值作为缓存名称。 使用Cacheable注解后执行当前操作如果发现对应名称在缓存中没有数据就正常读取数据然后放入缓存如果对应名称在缓存中有数据就终止当前业务方法执行直接返回缓存中的数据。手机验证码案例 为了便于下面演示各种各样的缓存技术我们创建一个手机验证码的案例环境模拟使用缓存保存手机验证码的过程。 手机验证码案例需求如下输入手机号获取验证码组织文档以短信形式发送给用户页面模拟输入手机号和验证码验证结果 为了描述上述操作我们制作两个表现层接口一个用来模拟发送短信的过程其实就是根据用户提供的手机号生成一个验证码然后放入缓存另一个用来模拟验证码校验的过程其实就是使用传入的手机号和验证码进行匹配并返回最终匹配结果。下面直接制作本案例的模拟代码先以上例中springboot提供的内置缓存技术来完成当前案例的制作。步骤①导入springboot提供的缓存技术对应的starterdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-cache/artifactId /dependency步骤②启用缓存在引导类上方标注注解EnableCaching配置springboot程序中可以使用缓存SpringBootApplication //开启缓存功能 EnableCaching public class Springboot19CacheApplication {public static void main(String[] args) {SpringApplication.run(Springboot19CacheApplication.class, args);} }步骤③定义验证码对应的实体类封装手机号与验证码两个属性Data public class SMSCode {private String tele;private String code; }步骤④定义验证码功能的业务层接口与实现类public interface SMSCodeService {public String sendCodeToSMS(String tele);public boolean checkCode(SMSCode smsCode); }Service public class SMSCodeServiceImpl implements SMSCodeService {Autowiredprivate CodeUtils codeUtils;CachePut(value smsCode, key #tele)public String sendCodeToSMS(String tele) {String code codeUtils.generator(tele);return code;}public boolean checkCode(SMSCode smsCode) {//取出内存中的验证码与传递过来的验证码比对如果相同返回trueString code smsCode.getCode();String cacheCode codeUtils.get(smsCode.getTele());return code.equals(cacheCode);} } 获取验证码后当验证码失效时必须重新获取验证码因此在获取验证码的功能上不能使用Cacheable注解Cacheable注解是缓存中没有值则放入值缓存中有值则取值。此处的功能仅仅是生成验证码并放入缓存并不具有从缓存中取值的功能因此不能使用Cacheable注解应该使用仅具有向缓存中保存数据的功能使用CachePut注解即可。 对于校验验证码的功能建议放入工具类中进行。步骤⑤定义验证码的生成策略与根据手机号读取验证码的功能Component public class CodeUtils {private String [] patch {000000,00000,0000,000,00,0,};public String generator(String tele){int hash tele.hashCode();int encryption 20206666;long result hash ^ encryption;long nowTime System.currentTimeMillis();result result ^ nowTime;long code result % 1000000;code code 0 ? -code : code;String codeStr code ;int len codeStr.length();return patch[len] codeStr;}Cacheable(value smsCode,key#tele)public String get(String tele){return null;} }步骤⑥定义验证码功能的web层接口一个方法用于提供手机号获取验证码一个方法用于提供手机号和验证码进行校验RestController RequestMapping(/sms) public class SMSCodeController {Autowiredprivate SMSCodeService smsCodeService;GetMappingpublic String getCode(String tele){String code smsCodeService.sendCodeToSMS(tele);return code;}PostMappingpublic boolean checkCode(SMSCode smsCode){return smsCodeService.checkCode(smsCode);} }SpringBoot整合Ehcache缓存 手机验证码的案例已经完成了下面就开始springboot整合各种各样的缓存技术第一个整合Ehcache技术。Ehcache是一种缓存技术使用springboot整合Ehcache其实就是变更一下缓存技术的实现方式话不多说直接开整步骤①导入Ehcache的坐标dependencygroupIdnet.sf.ehcache/groupIdartifactIdehcache/artifactId /dependency 此处为什么不是导入Ehcache的starter而是导入技术坐标呢其实springboot整合缓存技术做的是通用格式不管你整合哪种缓存技术只是实现变化了操作方式一样。这也体现出springboot技术的优点统一同类技术的整合方式。步骤②配置缓存技术实现使用Ehcachespring:cache:type: ehcacheehcache:config: ehcache.xml 配置缓存的类型type为ehcache此处需要说明一下当前springboot可以整合的缓存技术中包含有ehcach所以可以这样书写。其实这个type不可以随便写的不是随便写一个名称就可以整合的。 由于ehcache的配置有独立的配置文件格式因此还需要指定ehcache的配置文件以便于读取相应配置?xml version1.0 encodingUTF-8? ehcache xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:noNamespaceSchemaLocationhttp://ehcache.org/ehcache.xsdupdateCheckfalsediskStore pathD:\ehcache /!--默认缓存策略 --!-- external是否永久存在设置为true则不会被清除此时与timeout冲突通常设置为false--!-- diskPersistent是否启用磁盘持久化--!-- maxElementsInMemory最大缓存数量--!-- overflowToDisk超过最大缓存数量是否持久化到磁盘--!-- timeToIdleSeconds最大不活动间隔设置过长缓存容易溢出设置过短无效果可用于记录时效性数据例如验证码--!-- timeToLiveSeconds最大存活时间--!-- memoryStoreEvictionPolicy缓存清除策略--defaultCacheeternalfalsediskPersistentfalsemaxElementsInMemory1000overflowToDiskfalsetimeToIdleSeconds60timeToLiveSeconds60memoryStoreEvictionPolicyLRU /cachenamesmsCodeeternalfalsediskPersistentfalsemaxElementsInMemory1000overflowToDiskfalsetimeToIdleSeconds10timeToLiveSeconds10memoryStoreEvictionPolicyLRU / /ehcache 注意前面的案例中设置了数据保存的位置是smsCodeCachePut(value smsCode, key #tele) public String sendCodeToSMS(String tele) {String code codeUtils.generator(tele);return code; } 这个设定需要保障ehcache中有一个缓存空间名称叫做smsCode的配置前后要统一。在企业开发过程中通过设置不同名称的cache来设定不同的缓存策略应用于不同的缓存数据。 到这里springboot整合Ehcache就做完了可以发现一点原始代码没有任何修改仅仅是加了一组配置就可以变更缓存供应商了这也是springboot提供了统一的缓存操作接口的优势变更实现并不影响原始代码的书写。总结springboot使用Ehcache作为缓存实现需要导入Ehcache的坐标修改设置配置缓存供应商为ehcache并提供对应的缓存配置文件SpringBoot整合Redis缓存 上节使用Ehcache替换了springboot内置的缓存技术其实springboot支持的缓存技术还很多下面使用redis技术作为缓存解决方案来实现手机验证码案例。 比对使用Ehcache的过程加坐标改缓存实现类型为ehcache做Ehcache的配置。如果还成redis做缓存呢一模一样加坐标改缓存实现类型为redis做redis的配置。差别之处只有一点redis的配置可以在yml文件中直接进行配置无需制作独立的配置文件。步骤①导入redis的坐标dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId /dependency步骤②配置缓存技术实现使用redisspring:redis:host: localhostport: 6379cache:type: redis 如果需要对redis作为缓存进行配置注意不是对原始的redis进行配置而是配置redis作为缓存使用相关的配置隶属于spring.cache.redis节点下注意不要写错位置了。spring:redis:host: localhostport: 6379cache:type: redisredis:use-key-prefix: falsekey-prefix: sms_cache-null-values: falsetime-to-live: 10s总结springboot使用redis作为缓存实现需要导入redis的坐标修改设置配置缓存供应商为redis并提供对应的缓存配置SpringBoot整合j2cache缓存 jetcache可以在限定范围内构建多级缓存但是灵活性不足不能随意搭配缓存本节介绍一种可以随意搭配缓存解决方案的缓存整合框架j2cache。下面就来讲解如何使用这种缓存框架以Ehcache与redis整合为例步骤①导入j2cache、redis、ehcache坐标dependencygroupIdnet.oschina.j2cache/groupIdartifactIdj2cache-core/artifactIdversion2.8.4-release/version /dependency dependencygroupIdnet.oschina.j2cache/groupIdartifactIdj2cache-spring-boot2-starter/artifactIdversion2.8.0-release/version /dependency dependencygroupIdnet.sf.ehcache/groupIdartifactIdehcache/artifactId /dependency j2cache的starter中默认包含了redis坐标官方推荐使用redis作为二级缓存因此此处无需导入redis坐标步骤②配置一级与二级缓存并配置一二级缓存间数据传递方式配置书写在名称为j2cache.properties的文件中。如果使用ehcache还需要单独添加ehcache的配置文件# 1级缓存 j2cache.L1.provider_class ehcache ehcache.configXml ehcache.xml# 2级缓存 j2cache.L2.provider_class net.oschina.j2cache.cache.support.redis.SpringRedisProvider j2cache.L2.config_section redis redis.hosts localhost:6379# 1级缓存中的数据如何到达二级缓存 j2cache.broadcast net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy 此处配置不能乱配置需要参照官方给出的配置说明进行。例如1级供应商选择ehcache供应商名称仅仅是一个ehcache但是2级供应商选择redis时要写专用的Spring整合Redis的供应商类名SpringRedisProvider而且这个名称并不是所有的redis包中能提供的也不是spring包中提供的。因此配置j2cache必须参照官方文档配置而且还要去找专用的整合包导入对应坐标才可以使用。 一级与二级缓存最重要的一个配置就是两者之间的数据沟通方式此类配置也不是随意配置的并且不同的缓存解决方案提供的数据沟通方式差异化很大需要查询官方文档进行设置。步骤③使用缓存Service public class SMSCodeServiceImpl implements SMSCodeService {Autowiredprivate CodeUtils codeUtils;Autowiredprivate CacheChannel cacheChannel;public String sendCodeToSMS(String tele) {String code codeUtils.generator(tele);cacheChannel.set(sms,tele,code);return code;}public boolean checkCode(SMSCode smsCode) {String code cacheChannel.get(sms,smsCode.getTele()).asString();return smsCode.getCode().equals(code);} } j2cache的使用和jetcache比较类似但是无需开启使用的开关直接定义缓存对象即可使用缓存对象名CacheChannel。 j2cache的使用不复杂配置是j2cache的核心毕竟是一个整合型的缓存框架。缓存相关的配置过多可以查阅j2cache-core核心包中的j2cache.properties文件中的说明。如下#J2Cache configuration ######################################### # Cache Broadcast Method # values: # jgroups - use jgroupss multicast # redis - use redis publish/subscribe mechanism (using jedis) # lettuce - use redis publish/subscribe mechanism (using lettuce, Recommend) # rabbitmq - use RabbitMQ publisher/consumer mechanism # rocketmq - use RocketMQ publisher/consumer mechanism # none - dont notify the other nodes in cluster # xx.xxxx.xxxx.Xxxxx your own cache broadcast policy classname that implement net.oschina.j2cache.cluster.ClusterPolicy ######################################### j2cache.broadcast redis# jgroups properties jgroups.channel.name j2cache jgroups.configXml /network.xml# RabbitMQ properties rabbitmq.exchange j2cache rabbitmq.host localhost rabbitmq.port 5672 rabbitmq.username guest rabbitmq.password guest# RocketMQ properties rocketmq.name j2cache rocketmq.topic j2cache # use ; to split multi hosts rocketmq.hosts 127.0.0.1:9876######################################### # Level 12 provider # values: # none - disable this level cache # ehcache - use ehcache2 as level 1 cache # ehcache3 - use ehcache3 as level 1 cache # caffeine - use caffeine as level 1 cache(only in memory) # redis - use redis as level 2 cache (using jedis) # lettuce - use redis as level 2 cache (using lettuce) # readonly-redis - use redis as level 2 cache ,but never write data to it. if use this provider, you must uncomment j2cache.L2.config_section to make the redis configurations available. # memcached - use memcached as level 2 cache (xmemcached), # [classname] - use custom provider #########################################j2cache.L1.provider_class caffeine j2cache.L2.provider_class redis# When L2 provider isnt redis, using L2.config_section redis to read redis configurations # j2cache.L2.config_section redis# Enable/Disable ttl in redis cache data (if disabled, the object in redis will never expire, default:true) # NOTICE: redis hash mode (redis.storage hash) do not support this feature) j2cache.sync_ttl_to_redis true# Whether to cache null objects by default (default false) j2cache.default_cache_null_object true######################################### # Cache Serialization Provider # values: # fst - using fast-serialization (recommend) # kryo - using kryo serialization # json - using fsts json serialization (testing) # fastjson - using fastjson serialization (embed non-static class not support) # java - java standard # fse - using fse serialization # [classname implements Serializer] #########################################j2cache.serialization json #json.map.person net.oschina.j2cache.demo.Person######################################### # Ehcache configuration ########################################## ehcache.configXml /ehcache.xml# ehcache3.configXml /ehcache3.xml # ehcache3.defaultHeapSize 1000######################################### # Caffeine configuration # caffeine.region.[name] size, xxxx[s|m|h|d] # ######################################### caffeine.properties /caffeine.properties######################################### # Redis connection configuration ################################################################################## # Redis Cluster Mode # # single - single redis server # sentinel - master-slaves servers # cluster - cluster servers (数据库配置无效使用 database 0 # sharded - sharded servers (密码、数据库必须在 hosts 中指定且连接池配置无效 ; redis://user:password127.0.0.1:6379/0 # #########################################redis.mode single#redis storage mode (generic|hash) redis.storage generic## redis pub/sub channel name redis.channel j2cache ## redis pub/sub server (using redis.hosts when empty) redis.channel.host #cluster name just for sharded redis.cluster_name j2cache## redis cache namespace optional, default[empty] redis.namespace ## redis command scan parameter count, default[1000] #redis.scanCount 1000## connection # Separate multiple redis nodes with commas, such as 192.168.0.10:6379,192.168.0.11:6379,192.168.0.12:6379redis.hosts 127.0.0.1:6379 redis.timeout 2000 redis.password redis.database 0 redis.ssl false## redis pool properties redis.maxTotal 100 redis.maxIdle 10 redis.maxWaitMillis 5000 redis.minEvictableIdleTimeMillis 60000 redis.minIdle 1 redis.numTestsPerEvictionRun 10 redis.lifo false redis.softMinEvictableIdleTimeMillis 10 redis.testOnBorrow true redis.testOnReturn false redis.testWhileIdle true redis.timeBetweenEvictionRunsMillis 300000 redis.blockWhenExhausted false redis.jmxEnabled false######################################### # Lettuce scheme # # redis - single redis server # rediss - single redis server with ssl # redis-sentinel - redis sentinel # redis-cluster - cluster servers # ################################################################################## # Lettuce Mode # # single - single redis server # sentinel - master-slaves servers # cluster - cluster servers (数据库配置无效使用 database 0 # sharded - sharded servers (密码、数据库必须在 hosts 中指定且连接池配置无效 ; redis://user:password127.0.0.1:6379/0 # ########################################### redis command scan parameter count, default[1000] #lettuce.scanCount 1000 lettuce.mode single lettuce.namespace lettuce.storage hash lettuce.channel j2cache lettuce.scheme redis lettuce.hosts 127.0.0.1:6379 lettuce.password lettuce.database 0 lettuce.sentinelMasterId lettuce.maxTotal 100 lettuce.maxIdle 10 lettuce.minIdle 10 # timeout in milliseconds lettuce.timeout 10000 # redis cluster topology refresh interval in milliseconds lettuce.clusterTopologyRefresh 3000######################################### # memcached server configurations # refer to https://gitee.com/mirrors/XMemcached #########################################memcached.servers 127.0.0.1:11211 memcached.username memcached.password memcached.connectionPoolSize 10 memcached.connectTimeout 1000 memcached.failureMode false memcached.healSessionInterval 1000 memcached.maxQueuedNoReplyOperations 100 memcached.opTimeout 100 memcached.sanitizeKeys false总结j2cache是一个缓存框架自身不具有缓存功能它提供多种缓存整合在一起使用的方案j2cache需要通过复杂的配置设置各级缓存以及缓存之间数据交换的方式j2cache操作接口通过CacheChannel实现5-2.任务 springboot整合第三方技术第二部分我们来说说任务系统其实这里说的任务系统指的是定时任务。定时任务是企业级开发中必不可少的组成部分诸如长周期业务数据的计算例如年度报表诸如系统脏数据的处理再比如系统性能监控报告还有抢购类活动的商品上架这些都离不开定时任务。本节将介绍两种不同的定时任务技术。Quartz Quartz技术是一个比较成熟的定时任务框架怎么说呢springboot对其进行整合后简化了一系列的配置将很多配置采用默认设置这样开发阶段就简化了很多。再学习springboot整合Quartz前先普及几个Quartz的概念。工作Job用于定义具体执行的工作工作明细JobDetail用于描述定时工作相关的信息触发器Trigger描述了工作明细与调度器的对应关系调度器Scheduler用于描述触发工作的执行规则通常使用cron表达式定义规则 简单说就是你定时干什么事情这就是工作工作不可能就是一个简单的方法还要设置一些明细信息。工作啥时候执行设置一个调度器可以简单理解成设置一个工作执行的时间。工作和调度都是独立定义的它们两个怎么配合到一起呢用触发器。完了就这么多。下面开始springboot整合Quartz。步骤①导入springboot整合Quartz的starterdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-quartz/artifactId /dependency步骤②定义任务Bean按照Quartz的开发规范制作继承QuartzJobBeanpublic class MyQuartz extends QuartzJobBean {Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {System.out.println(quartz task run...);} }步骤③创建Quartz配置类定义工作明细JobDetail与触发器的TriggerbeanConfiguration public class QuartzConfig {Beanpublic JobDetail printJobDetail(){//绑定具体的工作return JobBuilder.newJob(MyQuartz.class).storeDurably().build();}Beanpublic Trigger printJobTrigger(){ScheduleBuilder schedBuilder CronScheduleBuilder.cronSchedule(0/5 * * * * ?);//绑定对应的工作明细return TriggerBuilder.newTrigger().forJob(printJobDetail()).withSchedule(schedBuilder).build();} } 工作明细中要设置对应的具体工作使用newJob()操作传入对应的工作任务类型即可。 触发器需要绑定任务使用forJob()操作传入绑定的工作明细对象。此处可以为工作明细设置名称然后使用名称绑定也可以直接调用对应方法绑定。触发器中最核心的规则是执行时间此处使用调度器定义执行时间执行时间描述方式使用的是cron表达式。有关cron表达式的规则各位小伙伴可以去参看相关课程学习略微复杂而且格式不能乱设置不是写个格式就能用的写不好就会出现冲突问题。总结springboot整合Quartz就是将Quartz对应的核心对象交给spring容器管理包含两个对象JobDetail和Trigger对象JobDetail对象描述的是工作的执行信息需要绑定一个QuartzJobBean类型的对象Trigger对象定义了一个触发器需要为其指定绑定的JobDetail是哪个同时要设置执行周期调度器思考 上面的操作看上去不多但是Quartz将其中的对象划分粒度过细导致开发的时候有点繁琐spring针对上述规则进行了简化开发了自己的任务管理组件——Task如何用呢咱们下节再说。Task spring根据定时任务的特征将定时任务的开发简化到了极致。怎么说呢要做定时任务总要告诉容器有这功能吧然后定时执行什么任务直接告诉对应的bean什么时间执行就行了就这么简单一起来看怎么做步骤①开启定时任务功能在引导类上开启定时任务功能的开关使用注解EnableSchedulingSpringBootApplication //开启定时任务功能 EnableScheduling public class Springboot22TaskApplication {public static void main(String[] args) {SpringApplication.run(Springboot22TaskApplication.class, args);} }步骤②定义Bean在对应要定时执行的操作上方使用注解Scheduled定义执行的时间执行时间的描述方式还是cron表达式Component public class MyBean {Scheduled(cron 0/1 * * * * ?)public void print(){System.out.println(Thread.currentThread().getName() :spring task run...);} } 完事这就完成了定时任务的配置。总体感觉其实什么东西都没少只不过没有将所有的信息都抽取成bean而是直接使用注解绑定定时执行任务的事情而已。 如何想对定时任务进行相关配置可以通过配置文件进行spring:task:scheduling:pool:size: 1 # 任务调度线程池大小 默认 1thread-name-prefix: ssm_ # 调度线程名称前缀 默认 scheduling- shutdown:await-termination: false # 线程池关闭时等待所有任务完成await-termination-period: 10s # 调度线程关闭前最大等待时间确保最后一定关闭总结spring task需要使用注解EnableScheduling开启定时任务功能为定时执行的的任务设置执行周期描述方式cron表达式5-3.邮件 springboot整合第三方技术第三部分我们来说说邮件系统发邮件是java程序的基本操作springboot整合javamail其实就是简化开发。不熟悉邮件的小伙伴可以先学习完javamail的基础操作再来看这一部分内容才能感触到springboot整合javamail究竟简化了哪些操作。简化的多码其实不多差别不大只是还个格式而已。 学习邮件发送之前先了解3个概念这些概念规范了邮件操作过程中的标准。SMTPSimple Mail Transfer Protocol简单邮件传输协议用于发送电子邮件的传输协议POP3Post Office Protocol - Version 3用于接收电子邮件的标准协议IMAPInternet Mail Access Protocol互联网消息协议是POP3的替代协议 简单说就是SMPT是发邮件的标准POP3是收邮件的标准IMAP是对POP3的升级。我们制作程序中操作邮件通常是发邮件所以SMTP是使用的重点收邮件大部分都是通过邮件客户端完成所以开发收邮件的代码极少。除非你要读取邮件内容然后解析做邮件功能的统一处理。例如HR的邮箱收到求职者的简历可以读取后统一处理。但是为什么不制作独立的投递简历的系统呢所以说好奇怪的需求因为要想收邮件就要规范发邮件的人的书写格式这个未免有点强人所难并且极易收到外部攻击你不可能使用白名单来收邮件。如果能使用白名单来收邮件然后解析邮件还不如开发个系统给白名单中的人专用呢更安全总之就是鸡肋了。下面就开始学习springboot如何整合javamail发送邮件。发送简单邮件步骤①导入springboot整合javamail的starterdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-mail/artifactId /dependency步骤②配置邮箱的登录信息spring:mail:host: smtp.126.comusername: test126.compassword: test java程序仅用于发送邮件邮件的功能还是邮件供应商提供的所以这里是用别人的邮件服务要配置对应信息。 host配置的是提供邮件服务的主机协议当前程序仅用于发送邮件因此配置的是smtp的协议。 password并不是邮箱账号的登录密码是邮件供应商提供的一个加密后的密码也是为了保障系统安全性。不然外部人员通过地址访问下载了配置文件直接获取到了邮件密码就会有极大的安全隐患。有关该密码的获取每个邮件供应商提供的方式都不一样此处略过。可以到邮件供应商的设置页面找POP3或IMAP这些关键词找到对应的获取位置。下例仅供参考步骤③使用JavaMailSender接口发送邮件Service public class SendMailServiceImpl implements SendMailService {Autowiredprivate JavaMailSender javaMailSender;//发送人private String from testqq.com;//接收人private String to test126.com;//标题private String subject 测试邮件;//正文private String context 测试邮件正文内容;Overridepublic void sendMail() {SimpleMailMessage message new SimpleMailMessage();message.setFrom(from(小甜甜));message.setTo(to);message.setSubject(subject);message.setText(context);javaMailSender.send(message);} } 将发送邮件的必要信息发件人、收件人、标题、正文封装到SimpleMailMessage对象中可以根据规则设置发送人昵称等。发送多组件邮件附件、复杂正文 发送简单邮件仅需要提供对应的4个基本信息就可以了如果想发送复杂的邮件需要更换邮件对象。使用MimeMessage可以发送特殊的邮件。发送网页正文邮件Service public class SendMailServiceImpl2 implements SendMailService {Autowiredprivate JavaMailSender javaMailSender;//发送人private String from testqq.com;//接收人private String to test126.com;//标题private String subject 测试邮件;//正文private String context img srcABC.JPG/a hrefhttps://www.itcast.cn点开有惊喜/a;public void sendMail() {try {MimeMessage message javaMailSender.createMimeMessage();MimeMessageHelper helper new MimeMessageHelper(message);helper.setFrom(to(小甜甜));helper.setTo(from);helper.setSubject(subject);helper.setText(context,true); //此处设置正文支持html解析javaMailSender.send(message);} catch (Exception e) {e.printStackTrace();}} }发送带有附件的邮件Service public class SendMailServiceImpl2 implements SendMailService {Autowiredprivate JavaMailSender javaMailSender;//发送人private String from testqq.com;//接收人private String to test126.com;//标题private String subject 测试邮件;//正文private String context 测试邮件正文;public void sendMail() {try {MimeMessage message javaMailSender.createMimeMessage();MimeMessageHelper helper new MimeMessageHelper(message,true); //此处设置支持附件helper.setFrom(to(小甜甜));helper.setTo(from);helper.setSubject(subject);helper.setText(context);//添加附件File f1 new File(springboot_23_mail-0.0.1-SNAPSHOT.jar);File f2 new File(resources\\logo.png);helper.addAttachment(f1.getName(),f1);helper.addAttachment(最靠谱的培训结构.png,f2);javaMailSender.send(message);} catch (Exception e) {e.printStackTrace();}} }总结springboot整合javamail其实就是简化了发送邮件的客户端对象JavaMailSender的初始化过程通过配置的形式加载信息简化开发过程5-4.消息 springboot整合第三方技术最后一部分我们来说说消息中间件首先先介绍一下消息的应用。消息的概念 从广义角度来说消息其实就是信息但是和信息又有所不同。信息通常被定义为一组数据而消息除了具有数据的特征之外还有消息的来源与接收的概念。通常发送消息的一方称为消息的生产者接收消息的一方称为消息的消费者。这样比较后发现其实消息和信息差别还是很大的。 为什么要设置生产者和消费者呢这就是要说到消息的意义了。信息通常就是一组数据但是消息由于有了生产者和消费者就出现了消息中所包含的信息可以被二次解读生产者发送消息可以理解为生产者发送了一个信息也可以理解为生产者发送了一个命令消费者接收消息可以理解为消费者得到了一个信息也可以理解为消费者得到了一个命令。对比一下我们会发现信息是一个基本数据而命令则可以关联下一个行为动作这样就可以理解为基于接收的消息相当于得到了一个行为动作使用这些行为动作就可以组织成一个业务逻辑进行进一步的操作。总的来说消息其实也是一组信息只是为其赋予了全新的含义因为有了消息的流动并且是有方向性的流动带来了基于流动的行为产生的全新解读。开发者就可以基于消息的这种特殊解将其换成代码中的指令。 对于消息的理解初学者总认为消息内部的数据非常复杂这是一个误区。比如我发送了一个消息要求接受者翻译发送过去的内容。初学者会认为消息中会包含被翻译的文字已经本次操作要执行翻译操作而不是打印操作。其实这种现象有点过度解读了发送的消息中仅仅包含被翻译的文字但是可以通过控制不同的人接收此消息来确认要做的事情。例如发送被翻译的文字仅到A程序而A程序只能进行翻译操作这样就可以发送简单的信息完成复杂的业务了是通过接收消息的主体不同进而执行不同的操作而不会在消息内部定义数据的操作行为当然如果开发者希望消息中包含操作种类信息也是可以的只是提出消息的内容可以更简单更单一。 对于消息的生产者与消费者的工作模式还可以将消息划分成两种模式同步消费与异步消息。 所谓同步消息就是生产者发送完消息等待消费者处理消费者处理完将结果告知生产者然后生产者继续向下执行业务。这种模式过于卡生产者的业务执行连续性在现在的企业级开发中上述这种业务场景通常不会采用消息的形式进行处理。 所谓异步消息就是生产者发送完消息无需等待消费者处理完毕生产者继续向下执行其他动作。比如生产者发送了一个日志信息给日志系统发送过去以后生产者就向下做其他事情了无需关注日志系统的执行结果。日志系统根据接收到的日志信息继续进行业务执行是单纯的记录日志还是记录日志并报警这些和生产者无关这样生产者的业务执行效率就会大幅度提升。并且可以通过添加多个消费者来处理同一个生产者发送的消息来提高系统的高并发性改善系统工作效率提高用户体验。一旦某一个消费者由于各种问题宕机了也不会对业务产生影响提高了系统的高可用性。 以上简单的介绍了一下消息这种工作模式存在的意义希望对各位学习者有所帮助。Java处理消息的标准规范 目前企业级开发中广泛使用的消息处理技术共三大类具体如下JMSAMQPMQTT 为什么是三大类而不是三个技术呢因为这些都是规范就想JDBC技术是个规范开发针对规范开发运行还要靠实现类例如MySQL提供了JDBC的实现最终运行靠的还是实现。并且这三类规范都是针对异步消息进行处理的也符合消息的设计本质处理异步的业务。对以上三种消息规范做一下普及JMS JMSJava Message Service,这是一个规范作用等同于JDBC规范提供了与消息服务相关的API接口。JMS消息模型 JMS规范中规范了消息有两种模型。分别是点对点模型和发布订阅模型。 点对点模型peer-2-peer生产者会将消息发送到一个保存消息的容器中通常使用队列模型使用队列保存消息。一个队列的消息只能被一个消费者消费或未被及时消费导致超时。这种模型下生产者和消费者是一对一绑定的。 发布订阅模型publish-subscribe生产者将消息发送到一个保存消息的容器中也是使用队列模型来保存。但是消息可以被多个消费者消费生产者和消费者完全独立相互不需要感知对方的存在。 以上这种分类是从消息的生产和消费过程来进行区分针对消息所包含的信息不同还可以进行不同类别的划分。JMS消息种类 根据消息中包含的数据种类划分可以将消息划分成6种消息。TextMessageMapMessageBytesMessageStreamMessageObjectMessageMessage 只有消息头和属性 JMS主张不同种类的消息消费方式不同可以根据使用需要选择不同种类的消息。但是这一点也成为其诟病之处后面再说。整体上来说JMS就是典型的保守派什么都按照J2EE的规范来做一套规范定义若干个标准每个标准下又提供一大批API。目前对JMS规范实现的消息中间件技术还是挺多的毕竟是皇家御用肯定有人舔例如ActiveMQ、Redis、HornetMQ。但是也有一些不太规范的实现参考JMS的标准设计但是又不完全满足其规范例如RabbitMQ、RocketMQ。AMQP JMS的问世为消息中间件提供了很强大的规范性支撑但是使用的过程中就开始被人诟病比如JMS设置的极其复杂的多种类消息处理机制。本来分门别类处理挺好的为什么会被诟病呢原因就在于JMS的设计是J2EE规范站在Java开发的角度思考问题。但是现实往往是复杂度很高的。比如我有一个.NET开发的系统A有一个Java开发的系统B现在要从A系统给B系统发业务消息结果两边数据格式不统一没法操作。JMS不是可以统一数据格式吗提供了6种数据种类总有一款适合你啊。NO一个都不能用。因为A系统的底层语言不是Java语言开发的根本不支持那些对象。这就意味着如果想使用现有的业务系统A继续开发已经不可能了必须推翻重新做使用Java语言开发的A系统。 这时候有人就提出说你搞那么复杂整那么多种类干什么找一种大家都支持的消息数据类型不就解决这个跨平台的问题了吗大家一想对啊于是AMQP孕育而生。 单从上面的说明中其实可以明确感知到AMQP的出现解决的是消息传递时使用的消息种类的问题化繁为简但是其并没有完全推翻JMS的操作API所以说AMQP仅仅是一种协议规范了数据传输的格式而已。 AMQPadvanced message queuing protocol一种协议高级消息队列协议也是消息代理规范规范了网络交换的数据格式兼容JMS操作。优点 具有跨平台性服务器供应商生产者消费者可以使用不同的语言来实现JMS消息种类 AMQP消息种类byte[] AMQP在JMS的消息模型基础上又进行了进一步的扩展除了点对点和发布订阅的模型开发了几种全新的消息模型适应各种各样的消息发送。AMQP消息模型direct exchangefanout exchangetopic exchangeheaders exchangesystem exchange 目前实现了AMQP协议的消息中间件技术也很多而且都是较为流行的技术例如RabbitMQ、StormMQ、RocketMQMQTT MQTTMessage Queueing Telemetry Transport消息队列遥测传输专为小设备设计是物联网IOT生态系统中主要成分之一。由于与JavaEE企业级开发没有交集此处不作过多的说明。 除了上述3种J2EE企业级应用中广泛使用的三种异步消息传递技术还有一种技术也不能忽略Kafka。KafKa Kafka一种高吞吐量的分布式发布订阅消息系统提供实时消息功能。Kafka技术并不是作为消息中间件为主要功能的产品但是其拥有发布订阅的工作模式也可以充当消息中间件来使用而且目前企业级开发中其身影也不少见。 本节内容讲围绕着上述内容中的几种实现方案讲解springboot整合各种各样的消息中间件。由于各种消息中间件必须先安装再使用下面的内容采用Windows系统安装降低各位学习者的学习难度基本套路和之前学习NoSQL解决方案一样先安装再整合。购物订单发送手机短信案例 为了便于下面演示各种各样的消息中间件技术我们创建一个购物过程生成订单时为用户发送短信的案例环境模拟使用消息中间件实现发送手机短信的过程。 手机验证码案例需求如下执行下单业务时模拟此过程调用消息服务将要发送短信的订单id传递给消息中间件消息处理服务接收到要发送的订单id后输出订单id模拟发短信由于不涉及数据读写仅开发业务层与表现层其中短信处理的业务代码独立开发代码如下订单业务 业务层接口public interface OrderService {void order(String id); } 模拟传入订单id执行下订单业务参数为虚拟设定实际应为订单对应的实体类 业务层实现Service public class OrderServiceImpl implements OrderService {Autowiredprivate MessageService messageService;Overridepublic void order(String id) {//一系列操作包含各种服务调用处理各种业务System.out.println(订单处理开始);//短信消息处理messageService.sendMessage(id);System.out.println(订单处理结束);System.out.println();} } 业务层转调短信处理的服务MessageService 表现层服务RestController RequestMapping(/orders) public class OrderController {Autowiredprivate OrderService orderService;PostMapping({id})public void order(PathVariable String id){orderService.order(id);} } 表现层对外开发接口传入订单id即可模拟短信处理业务 业务层接口public interface MessageService {void sendMessage(String id);String doMessage(); } 短信处理业务层接口提供两个操作发送要处理的订单id到消息中间件另一个操作目前暂且设计成处理消息实际消息的处理过程不应该是手动执行应该是自动执行到具体实现时再进行设计 业务层实现Service public class MessageServiceImpl implements MessageService {private ArrayListString msgList new ArrayListString();Overridepublic void sendMessage(String id) {System.out.println(待发送短信的订单已纳入处理队列idid);msgList.add(id);}Overridepublic String doMessage() {String id msgList.remove(0);System.out.println(已完成短信发送业务idid);return id;} } 短信处理业务层实现中使用集合先模拟消息队列观察效果 表现层服务RestController RequestMapping(/msgs) public class MessageController {Autowiredprivate MessageService messageService;GetMappingpublic String doMessage(){String id messageService.doMessage();return id;} } 短信处理表现层接口暂且开发出一个处理消息的入口但是此业务是对应业务层中设计的模拟接口实际业务不需要设计此接口。 下面开启springboot整合各种各样的消息中间件从严格满足JMS规范的ActiveMQ开始SpringBoot整合ActiveMQ ActiveMQ是MQ产品中的元老级产品早期标准MQ产品之一在AMQP协议没有出现之前占据了消息中间件市场的绝大部分份额后期因为AMQP系列产品的出现迅速走弱目前仅在一些线上运行的产品中出现新产品开发较少采用。安装 windows版安装包下载地址https://activemq.apache.org/components/classic/download/ 下载的安装包是解压缩就能使用的zip文件解压缩完毕后会得到如下文件启动服务器activemq.bat 运行bin目录下的win32或win64目录下的activemq.bat命令即可根据自己的操作系统选择即可默认对外服务端口61616。访问web管理服务 ActiveMQ启动后会启动一个Web控制台服务可以通过该服务管理ActiveMQ。http://127.0.0.1:8161/ web管理服务默认端口8161访问后可以打开ActiveMQ的管理界面如下 首先输入访问用户名和密码初始化用户名和密码相同均为admin成功登录后进入管理后台界面如下 看到上述界面视为启动ActiveMQ服务成功。启动失败 在ActiveMQ启动时要占用多个端口以下为正常启动信息wrapper | -- Wrapper Started as Console wrapper | Launching a JVM... jvm 1 | Wrapper (Version 3.2.3) http://wrapper.tanukisoftware.org jvm 1 | Copyright 1999-2006 Tanuki Software, Inc. All Rights Reserved. jvm 1 | jvm 1 | Java Runtime: Oracle Corporation 1.8.0_172 D:\soft\jdk1.8.0_172\jre jvm 1 | Heap sizes: current249344k free235037k max932352k jvm 1 | JVM args: -Dactivemq.home../.. -Dactivemq.base../.. -Djavax.net.ssl.keyStorePasswordpassword -Djavax.net.ssl.trustStorePasswordpassword -Djavax.net.ssl.keyStore../../conf/broker.ks -Djavax.net.ssl.trustStore../../conf/broker.ts -Dcom.sun.management.jmxremote -Dorg.apache.activemq.UseDedicatedTaskRunnertrue -Djava.util.logging.config.filelogging.properties -Dactivemq.conf../../conf -Dactivemq.data../../data -Djava.security.auth.login.config../../conf/login.config -Xmx1024m -Djava.library.path../../bin/win64 -Dwrapper.key7ySrCD75XhLCpLjd -Dwrapper.port32000 -Dwrapper.jvm.port.min31000 -Dwrapper.jvm.port.max31999 -Dwrapper.pid9364 -Dwrapper.version3.2.3 -Dwrapper.native_librarywrapper -Dwrapper.cpu.timeout10 -Dwrapper.jvmid1 jvm 1 | Extensions classpath: jvm 1 | [..\..\lib,..\..\lib\camel,..\..\lib\optional,..\..\lib\web,..\..\lib\extra] jvm 1 | ACTIVEMQ_HOME: ..\.. jvm 1 | ACTIVEMQ_BASE: ..\.. jvm 1 | ACTIVEMQ_CONF: ..\..\conf jvm 1 | ACTIVEMQ_DATA: ..\..\data jvm 1 | Loading message broker from: xbean:activemq.xml jvm 1 | INFO | Refreshing org.apache.activemq.xbean.XBeanBrokerFactory$15f3ebfe0: startup date [Mon Feb 28 16:07:48 CST 2022]; root of context hierarchy jvm 1 | INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[D:\soft\activemq\bin\win64\..\..\data\kahadb] jvm 1 | INFO | KahaDB is version 7 jvm 1 | INFO | PListStore:[D:\soft\activemq\bin\win64\..\..\data\localhost\tmp_storage] started jvm 1 | INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10434-1646035669595-0:1) is starting jvm 1 | INFO | Listening for connections at: tcp://CZBK-20210302VL:61616?maximumConnections1000wireFormat.maxFrameSize104857600 jvm 1 | INFO | Connector openwire started jvm 1 | INFO | Listening for connections at: amqp://CZBK-20210302VL:5672?maximumConnections1000wireFormat.maxFrameSize104857600 jvm 1 | INFO | Connector amqp started jvm 1 | INFO | Listening for connections at: stomp://CZBK-20210302VL:61613?maximumConnections1000wireFormat.maxFrameSize104857600 jvm 1 | INFO | Connector stomp started jvm 1 | INFO | Listening for connections at: mqtt://CZBK-20210302VL:1883?maximumConnections1000wireFormat.maxFrameSize104857600 jvm 1 | INFO | Connector mqtt started jvm 1 | INFO | Starting Jetty server jvm 1 | INFO | Creating Jetty connector jvm 1 | WARN | ServletContexto.e.j.s.ServletContextHandler7350746f{/,null,STARTING} has uncovered http methods for path: / jvm 1 | INFO | Listening for connections at ws://CZBK-20210302VL:61614?maximumConnections1000wireFormat.maxFrameSize104857600 jvm 1 | INFO | Connector ws started jvm 1 | INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10434-1646035669595-0:1) started jvm 1 | INFO | For help or more information please see: http://activemq.apache.org jvm 1 | WARN | Store limit is 102400 mb (current store usage is 0 mb). The data directory: D:\soft\activemq\bin\win64\..\..\data\kahadb only has 68936 mb of usable space. - resetting to maximum available disk space: 68936 mb jvm 1 | INFO | ActiveMQ WebConsole available at http://127.0.0.1:8161/ jvm 1 | INFO | ActiveMQ Jolokia REST API available at http://127.0.0.1:8161/api/jolokia/ 其中占用的端口有61616、5672、61613、1883、61614如果启动失败请先管理对应端口即可。以下就是某个端口占用的报错信息可以从抛出异常的位置看出启动5672端口时端口被占用显示java.net.BindException: Address already in use: JVM_Bind。Windows系统中终止端口运行的操作参看【命令行启动常见问题及解决方案】wrapper | -- Wrapper Started as Console wrapper | Launching a JVM... jvm 1 | Wrapper (Version 3.2.3) http://wrapper.tanukisoftware.org jvm 1 | Copyright 1999-2006 Tanuki Software, Inc. All Rights Reserved. jvm 1 | jvm 1 | Java Runtime: Oracle Corporation 1.8.0_172 D:\soft\jdk1.8.0_172\jre jvm 1 | Heap sizes: current249344k free235038k max932352k jvm 1 | JVM args: -Dactivemq.home../.. -Dactivemq.base../.. -Djavax.net.ssl.keyStorePasswordpassword -Djavax.net.ssl.trustStorePasswordpassword -Djavax.net.ssl.keyStore../../conf/broker.ks -Djavax.net.ssl.trustStore../../conf/broker.ts -Dcom.sun.management.jmxremote -Dorg.apache.activemq.UseDedicatedTaskRunnertrue -Djava.util.logging.config.filelogging.properties -Dactivemq.conf../../conf -Dactivemq.data../../data -Djava.security.auth.login.config../../conf/login.config -Xmx1024m -Djava.library.path../../bin/win64 -Dwrapper.keyQPJoy9ZoXeWmmwTS -Dwrapper.port32000 -Dwrapper.jvm.port.min31000 -Dwrapper.jvm.port.max31999 -Dwrapper.pid14836 -Dwrapper.version3.2.3 -Dwrapper.native_librarywrapper -Dwrapper.cpu.timeout10 -Dwrapper.jvmid1 jvm 1 | Extensions classpath: jvm 1 | [..\..\lib,..\..\lib\camel,..\..\lib\optional,..\..\lib\web,..\..\lib\extra] jvm 1 | ACTIVEMQ_HOME: ..\.. jvm 1 | ACTIVEMQ_BASE: ..\.. jvm 1 | ACTIVEMQ_CONF: ..\..\conf jvm 1 | ACTIVEMQ_DATA: ..\..\data jvm 1 | Loading message broker from: xbean:activemq.xml jvm 1 | INFO | Refreshing org.apache.activemq.xbean.XBeanBrokerFactory$12c9392f5: startup date [Mon Feb 28 16:06:16 CST 2022]; root of context hierarchy jvm 1 | INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[D:\soft\activemq\bin\win64\..\..\data\kahadb] jvm 1 | INFO | KahaDB is version 7 jvm 1 | INFO | PListStore:[D:\soft\activemq\bin\win64\..\..\data\localhost\tmp_storage] started jvm 1 | INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) is starting jvm 1 | INFO | Listening for connections at: tcp://CZBK-20210302VL:61616?maximumConnections1000wireFormat.maxFrameSize104857600 jvm 1 | INFO | Connector openwire started jvm 1 | ERROR | Failed to start Apache ActiveMQ (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) jvm 1 | java.io.IOException: Transport Connector could not be registered in JMX: java.io.IOException: Failed to bind to server socket: amqp://0.0.0.0:5672?maximumConnections1000wireFormat.maxFrameSize104857600 due to: java.net.BindException: Address already in use: JVM_Bind jvm 1 | at org.apache.activemq.util.IOExceptionSupport.create(IOExceptionSupport.java:28) jvm 1 | at org.apache.activemq.broker.BrokerService.registerConnectorMBean(BrokerService.java:2288) jvm 1 | at org.apache.activemq.broker.BrokerService.startTransportConnector(BrokerService.java:2769) jvm 1 | at org.apache.activemq.broker.BrokerService.startAllConnectors(BrokerService.java:2665) jvm 1 | at org.apache.activemq.broker.BrokerService.doStartBroker(BrokerService.java:780) jvm 1 | at org.apache.activemq.broker.BrokerService.startBroker(BrokerService.java:742) jvm 1 | at org.apache.activemq.broker.BrokerService.start(BrokerService.java:645) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerService.afterPropertiesSet(XBeanBrokerService.java:73) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) jvm 1 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) jvm 1 | at java.lang.reflect.Method.invoke(Method.java:498) jvm 1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1748) jvm 1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1685) jvm 1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1615) jvm 1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) jvm 1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481) jvm 1 | at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312) jvm 1 | at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) jvm 1 | at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308) jvm 1 | at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) jvm 1 | at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:756) jvm 1 | at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) jvm 1 | at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542) jvm 1 | at org.apache.xbean.spring.context.ResourceXmlApplicationContext.init(ResourceXmlApplicationContext.java:64) jvm 1 | at org.apache.xbean.spring.context.ResourceXmlApplicationContext.init(ResourceXmlApplicationContext.java:52) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory$1.init(XBeanBrokerFactory.java:104) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory.createApplicationContext(XBeanBrokerFactory.java:104) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory.createBroker(XBeanBrokerFactory.java:67) jvm 1 | at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:71) jvm 1 | at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:54) jvm 1 | at org.apache.activemq.console.command.StartCommand.runTask(StartCommand.java:87) jvm 1 | at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63) jvm 1 | at org.apache.activemq.console.command.ShellCommand.runTask(ShellCommand.java:154) jvm 1 | at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63) jvm 1 | at org.apache.activemq.console.command.ShellCommand.main(ShellCommand.java:104) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) jvm 1 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) jvm 1 | at java.lang.reflect.Method.invoke(Method.java:498) jvm 1 | at org.apache.activemq.console.Main.runTaskClass(Main.java:262) jvm 1 | at org.apache.activemq.console.Main.main(Main.java:115) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) jvm 1 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) jvm 1 | at java.lang.reflect.Method.invoke(Method.java:498) jvm 1 | at org.tanukisoftware.wrapper.WrapperSimpleApp.run(WrapperSimpleApp.java:240) jvm 1 | at java.lang.Thread.run(Thread.java:748) jvm 1 | Caused by: java.io.IOException: Failed to bind to server socket: amqp://0.0.0.0:5672?maximumConnections1000wireFormat.maxFrameSize104857600 due to: java.net.BindException: Address already in use: JVM_Bind jvm 1 | at org.apache.activemq.util.IOExceptionSupport.create(IOExceptionSupport.java:34) jvm 1 | at org.apache.activemq.transport.tcp.TcpTransportServer.bind(TcpTransportServer.java:146) jvm 1 | at org.apache.activemq.transport.tcp.TcpTransportFactory.doBind(TcpTransportFactory.java:62) jvm 1 | at org.apache.activemq.transport.TransportFactorySupport.bind(TransportFactorySupport.java:40) jvm 1 | at org.apache.activemq.broker.TransportConnector.createTransportServer(TransportConnector.java:335) jvm 1 | at org.apache.activemq.broker.TransportConnector.getServer(TransportConnector.java:145) jvm 1 | at org.apache.activemq.broker.TransportConnector.asManagedConnector(TransportConnector.java:110) jvm 1 | at org.apache.activemq.broker.BrokerService.registerConnectorMBean(BrokerService.java:2283) jvm 1 | ... 46 more jvm 1 | Caused by: java.net.BindException: Address already in use: JVM_Bind jvm 1 | at java.net.DualStackPlainSocketImpl.bind0(Native Method) jvm 1 | at java.net.DualStackPlainSocketImpl.socketBind(DualStackPlainSocketImpl.java:106) jvm 1 | at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387) jvm 1 | at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:190) jvm 1 | at java.net.ServerSocket.bind(ServerSocket.java:375) jvm 1 | at java.net.ServerSocket.init(ServerSocket.java:237) jvm 1 | at javax.net.DefaultServerSocketFactory.createServerSocket(ServerSocketFactory.java:231) jvm 1 | at org.apache.activemq.transport.tcp.TcpTransportServer.bind(TcpTransportServer.java:143) jvm 1 | ... 52 more jvm 1 | INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) is shutting down jvm 1 | INFO | socketQueue interrupted - stopping jvm 1 | INFO | Connector openwire stopped jvm 1 | INFO | Could not accept connection during shutdown : null (null) jvm 1 | INFO | Connector amqp stopped jvm 1 | INFO | Connector stomp stopped jvm 1 | INFO | Connector mqtt stopped jvm 1 | INFO | Connector ws stopped jvm 1 | INFO | PListStore:[D:\soft\activemq\bin\win64\..\..\data\localhost\tmp_storage] stopped jvm 1 | INFO | Stopping async queue tasks jvm 1 | INFO | Stopping async topic tasks jvm 1 | INFO | Stopped KahaDB jvm 1 | INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) uptime 0.426 seconds jvm 1 | INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) is shutdown jvm 1 | INFO | Closing org.apache.activemq.xbean.XBeanBrokerFactory$12c9392f5: startup date [Mon Feb 28 16:06:16 CST 2022]; root of context hierarchy jvm 1 | WARN | Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name org.apache.activemq.xbean.XBeanBrokerService#0 defined in class path resource [activemq.xml]: Invocation of init method failed; nested exception is java.io.IOException: Transport Connector could not be registered in JMX: java.io.IOException: Failed to bind to server socket: amqp://0.0.0.0:5672?maximumConnections1000wireFormat.maxFrameSize104857600 due to: java.net.BindException: Address already in use: JVM_Bind jvm 1 | ERROR: java.lang.RuntimeException: Failed to execute start task. Reason: java.lang.IllegalStateException: BeanFactory not initialized or already closed - call refresh before accessing beans via the ApplicationContext jvm 1 | java.lang.RuntimeException: Failed to execute start task. Reason: java.lang.IllegalStateException: BeanFactory not initialized or already closed - call refresh before accessing beans via the ApplicationContext jvm 1 | at org.apache.activemq.console.command.StartCommand.runTask(StartCommand.java:91) jvm 1 | at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63) jvm 1 | at org.apache.activemq.console.command.ShellCommand.runTask(ShellCommand.java:154) jvm 1 | at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63) jvm 1 | at org.apache.activemq.console.command.ShellCommand.main(ShellCommand.java:104) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) jvm 1 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) jvm 1 | at java.lang.reflect.Method.invoke(Method.java:498) jvm 1 | at org.apache.activemq.console.Main.runTaskClass(Main.java:262) jvm 1 | at org.apache.activemq.console.Main.main(Main.java:115) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) jvm 1 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) jvm 1 | at java.lang.reflect.Method.invoke(Method.java:498) jvm 1 | at org.tanukisoftware.wrapper.WrapperSimpleApp.run(WrapperSimpleApp.java:240) jvm 1 | at java.lang.Thread.run(Thread.java:748) jvm 1 | Caused by: java.lang.IllegalStateException: BeanFactory not initialized or already closed - call refresh before accessing beans via the ApplicationContext jvm 1 | at org.springframework.context.support.AbstractRefreshableApplicationContext.getBeanFactory(AbstractRefreshableApplicationContext.java:164) jvm 1 | at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1034) jvm 1 | at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:555) jvm 1 | at org.apache.xbean.spring.context.ResourceXmlApplicationContext.init(ResourceXmlApplicationContext.java:64) jvm 1 | at org.apache.xbean.spring.context.ResourceXmlApplicationContext.init(ResourceXmlApplicationContext.java:52) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory$1.init(XBeanBrokerFactory.java:104) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory.createApplicationContext(XBeanBrokerFactory.java:104) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory.createBroker(XBeanBrokerFactory.java:67) jvm 1 | at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:71) jvm 1 | at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:54) jvm 1 | at org.apache.activemq.console.command.StartCommand.runTask(StartCommand.java:87) jvm 1 | ... 16 more jvm 1 | ERROR: java.lang.IllegalStateException: BeanFactory not initialized or already closed - call refresh before accessing beans via the ApplicationContext jvm 1 | java.lang.IllegalStateException: BeanFactory not initialized or already closed - call refresh before accessing beans via the ApplicationContext jvm 1 | at org.springframework.context.support.AbstractRefreshableApplicationContext.getBeanFactory(AbstractRefreshableApplicationContext.java:164) jvm 1 | at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1034) jvm 1 | at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:555) jvm 1 | at org.apache.xbean.spring.context.ResourceXmlApplicationContext.init(ResourceXmlApplicationContext.java:64) jvm 1 | at org.apache.xbean.spring.context.ResourceXmlApplicationContext.init(ResourceXmlApplicationContext.java:52) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory$1.init(XBeanBrokerFactory.java:104) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory.createApplicationContext(XBeanBrokerFactory.java:104) jvm 1 | at org.apache.activemq.xbean.XBeanBrokerFactory.createBroker(XBeanBrokerFactory.java:67) jvm 1 | at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:71) jvm 1 | at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:54) jvm 1 | at org.apache.activemq.console.command.StartCommand.runTask(StartCommand.java:87) jvm 1 | at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63) jvm 1 | at org.apache.activemq.console.command.ShellCommand.runTask(ShellCommand.java:154) jvm 1 | at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63) jvm 1 | at org.apache.activemq.console.command.ShellCommand.main(ShellCommand.java:104) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) jvm 1 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) jvm 1 | at java.lang.reflect.Method.invoke(Method.java:498) jvm 1 | at org.apache.activemq.console.Main.runTaskClass(Main.java:262) jvm 1 | at org.apache.activemq.console.Main.main(Main.java:115) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) jvm 1 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) jvm 1 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) jvm 1 | at java.lang.reflect.Method.invoke(Method.java:498) jvm 1 | at org.tanukisoftware.wrapper.WrapperSimpleApp.run(WrapperSimpleApp.java:240) jvm 1 | at java.lang.Thread.run(Thread.java:748) wrapper | -- Wrapper Stopped 请按任意键继续. . .整合 做了这么多springboot整合第三方技术已经摸到门路了加坐标做配置调接口直接开工步骤①导入springboot整合ActiveMQ的starterdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-activemq/artifactId /dependency步骤②配置ActiveMQ的服务器地址spring:activemq:broker-url: tcp://localhost:61616步骤③使用JmsMessagingTemplate操作ActiveMQService public class MessageServiceActivemqImpl implements MessageService {Autowiredprivate JmsMessagingTemplate messagingTemplate;Overridepublic void sendMessage(String id) {System.out.println(待发送短信的订单已纳入处理队列idid);messagingTemplate.convertAndSend(order.queue.id,id);}Overridepublic String doMessage() {String id messagingTemplate.receiveAndConvert(order.queue.id,String.class);System.out.println(已完成短信发送业务idid);return id;} } 发送消息需要先将消息的类型转换成字符串然后再发送所以是convertAndSend定义消息发送的位置和具体的消息内容此处使用id作为消息内容。 接收消息需要先将消息接收到然后再转换成指定的数据类型所以是receiveAndConvert接收消息除了提供读取的位置还要给出转换后的数据的具体类型。步骤④使用消息监听器在服务器启动后监听指定位置当消息出现后立即消费消息Component public class MessageListener {JmsListener(destination order.queue.id)SendTo(order.other.queue.id)public String receive(String id){System.out.println(已完成短信发送业务idid);return new:id;} } 使用注解JmsListener定义当前方法监听ActiveMQ中指定名称的消息队列。 如果当前消息队列处理完还需要继续向下传递当前消息到另一个队列中使用注解SendTo即可这样即可构造连续执行的顺序消息队列。步骤⑤切换消息模型由点对点模型到发布订阅模型修改jms配置即可spring:activemq:broker-url: tcp://localhost:61616jms:pub-sub-domain: true pub-sub-domain默认值为false即点对点模型修改为true后就是发布订阅模型。总结springboot整合ActiveMQ提供了JmsMessagingTemplate对象作为客户端操作消息队列操作ActiveMQ需要配置ActiveMQ服务器地址默认端口61616企业开发时通常使用监听器来处理消息队列中的消息设置监听器使用注解JmsListener配置jms的pub-sub-domain属性可以在点对点模型和发布订阅模型间切换消息模型SpringBoot整合RabbitMQ RabbitMQ是MQ产品中的目前较为流行的产品之一它遵从AMQP协议。RabbitMQ的底层实现语言使用的是Erlang所以安装RabbitMQ需要先安装Erlang。Erlang安装 windows版安装包下载地址https://www.erlang.org/downloads 下载完毕后得到exe安装文件一键傻瓜式安装安装完毕需要重启需要重启需要重启。 安装的过程中可能会出现依赖Windows组件的提示根据提示下载安装即可都是自动执行的如下 Erlang安装后需要配置环境变量否则RabbitMQ将无法找到安装的Erlang。需要配置项如下作用等同JDK配置环境变量的作用。ERLANG_HOMEPATH安装 windows版安装包下载地址https://rabbitmq.com/install-windows.html 下载完毕后得到exe安装文件一键傻瓜式安装安装完毕后会得到如下文件启动服务器rabbitmq-service.bat start # 启动服务 rabbitmq-service.bat stop # 停止服务 rabbitmqctl status # 查看服务状态 运行sbin目录下的rabbitmq-service.bat命令即可start参数表示启动stop参数表示退出默认对外服务端口5672。 注意启动rabbitmq的过程实际上是开启rabbitmq对应的系统服务需要管理员权限方可执行。 说明有没有感觉5672的服务端口很熟悉activemq与rabbitmq有一个端口冲突问题学习阶段无论操作哪一个请确保另一个处于关闭状态。 说明不喜欢命令行的小伙伴可以使用任务管理器中的服务页找到RabbitMQ服务使用鼠标右键菜单控制服务的启停。访问web管理服务 RabbitMQ也提供有web控制台服务但是此功能是一个插件需要先启用才可以使用。rabbitmq-plugins.bat list # 查看当前所有插件的运行状态 rabbitmq-plugins.bat enable rabbitmq_management # 启动rabbitmq_management插件 启动插件后可以在插件运行状态中查看是否运行运行后通过浏览器即可打开服务后台管理界面http://localhost:15672 web管理服务默认端口15672访问后可以打开RabbitMQ的管理界面如下 首先输入访问用户名和密码初始化用户名和密码相同均为guest成功登录后进入管理后台界面如下整合(direct模型) RabbitMQ满足AMQP协议因此不同的消息模型对应的制作不同先使用最简单的direct模型开发。步骤①导入springboot整合amqp的starteramqp协议默认实现为rabbitmq方案dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-amqp/artifactId /dependency步骤②配置RabbitMQ的服务器地址spring:rabbitmq:host: localhostport: 5672步骤③初始化直连模式系统设置 由于RabbitMQ不同模型要使用不同的交换机因此需要先初始化RabbitMQ相关的对象例如队列交换机等Configuration public class RabbitConfigDirect {Beanpublic Queue directQueue(){return new Queue(direct_queue);}Beanpublic Queue directQueue2(){return new Queue(direct_queue2);}Beanpublic DirectExchange directExchange(){return new DirectExchange(directExchange);}Beanpublic Binding bindingDirect(){return BindingBuilder.bind(directQueue()).to(directExchange()).with(direct);}Beanpublic Binding bindingDirect2(){return BindingBuilder.bind(directQueue2()).to(directExchange()).with(direct2);} } 队列Queue与直连交换机DirectExchange创建后还需要绑定他们之间的关系Binding这样就可以通过交换机操作对应队列。步骤④使用AmqpTemplate操作RabbitMQService public class MessageServiceRabbitmqDirectImpl implements MessageService {Autowiredprivate AmqpTemplate amqpTemplate;Overridepublic void sendMessage(String id) {System.out.println(待发送短信的订单已纳入处理队列rabbitmq directidid);amqpTemplate.convertAndSend(directExchange,direct,id);} } amqp协议中的操作API接口名称看上去和jms规范的操作API接口很相似但是传递参数差异很大。步骤⑤使用消息监听器在服务器启动后监听指定位置当消息出现后立即消费消息Component public class MessageListener {RabbitListener(queues direct_queue)public void receive(String id){System.out.println(已完成短信发送业务(rabbitmq direct)idid);} } 使用注解RabbitListener定义当前方法监听RabbitMQ中指定名称的消息队列。整合(topic模型)步骤①同上步骤②同上步骤③初始化主题模式系统设置Configuration public class RabbitConfigTopic {Beanpublic Queue topicQueue(){return new Queue(topic_queue);}Beanpublic Queue topicQueue2(){return new Queue(topic_queue2);}Beanpublic TopicExchange topicExchange(){return new TopicExchange(topicExchange);}Beanpublic Binding bindingTopic(){return BindingBuilder.bind(topicQueue()).to(topicExchange()).with(topic.*.id);}Beanpublic Binding bindingTopic2(){return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with(topic.orders.*);} } 主题模式支持routingKey匹配模式*表示匹配一个单词#表示匹配任意内容这样就可以通过主题交换机将消息分发到不同的队列中详细内容请参看RabbitMQ系列课程。匹配键topic.*.*topic.#topic.order.idtruetrueorder.topic.idfalsefalsetopic.sm.order.idfalsetruetopic.sm.idfalsetruetopic.id.ordertruetruetopic.idfalsetruetopic.orderfalsetrue步骤④使用AmqpTemplate操作RabbitMQService public class MessageServiceRabbitmqTopicImpl implements MessageService {Autowiredprivate AmqpTemplate amqpTemplate;Overridepublic void sendMessage(String id) {System.out.println(待发送短信的订单已纳入处理队列rabbitmq topicidid);amqpTemplate.convertAndSend(topicExchange,topic.orders.id,id);} } 发送消息后根据当前提供的routingKey与绑定交换机时设定的routingKey进行匹配规则匹配成功消息才会进入到对应的队列中。步骤⑤使用消息监听器在服务器启动后监听指定队列Component public class MessageListener {RabbitListener(queues topic_queue)public void receive(String id){System.out.println(已完成短信发送业务(rabbitmq topic 1)idid);}RabbitListener(queues topic_queue2)public void receive2(String id){System.out.println(已完成短信发送业务(rabbitmq topic 22222222)idid);} } 使用注解RabbitListener定义当前方法监听RabbitMQ中指定名称的消息队列。总结springboot整合RabbitMQ提供了AmqpTemplate对象作为客户端操作消息队列操作ActiveMQ需要配置ActiveMQ服务器地址默认端口5672企业开发时通常使用监听器来处理消息队列中的消息设置监听器使用注解RabbitListenerRabbitMQ有5种消息模型使用的队列相同但是交换机不同。交换机不同对应的消息进入的策略也不同SpringBoot整合Kafka安装 windows版安装包下载地址https://kafka.apache.org/downloads 下载完毕后得到tgz压缩文件使用解压缩软件解压缩即可使用解压后得到如下文件 建议使用windows版2.8.1版本。启动服务器 kafka服务器的功能相当于RocketMQ中的brokerkafka运行还需要一个类似于命名服务器的服务。在kafka安装目录中自带一个类似于命名服务器的工具叫做zookeeper它的作用是注册中心相关知识请到对应课程中学习。zookeeper-server-start.bat ..\..\config\zookeeper.properties # 启动zookeeper kafka-server-start.bat ..\..\config\server.properties # 启动kafka 运行bin目录下的windows目录下的zookeeper-server-start命令即可启动注册中心默认对外服务端口2181。 运行bin目录下的windows目录下的kafka-server-start命令即可启动kafka服务器默认对外服务端口9092。创建主题 和之前操作其他MQ产品相似kakfa也是基于主题操作操作之前需要先初始化topic。# 创建topic kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic itheima # 查询topic kafka-topics.bat --zookeeper 127.0.0.1:2181 --list # 删除topic kafka-topics.bat --delete --zookeeper localhost:2181 --topic itheima测试服务器启动状态 Kafka提供有一套测试服务器功能的测试程序运行bin目录下的windows目录下的命令即可使用。kafka-console-producer.bat --broker-list localhost:9092 --topic itheima # 测试生产消息 kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic itheima --from-beginning # 测试消息消费整合步骤①导入springboot整合Kafka的starter此坐标由springboot维护版本dependencygroupIdorg.springframework.kafka/groupIdartifactIdspring-kafka/artifactId /dependency步骤②配置Kafka的服务器地址spring:kafka:bootstrap-servers: localhost:9092consumer:group-id: order 设置默认的生产者消费者所属组id。步骤③使用KafkaTemplate操作KafkaService public class MessageServiceKafkaImpl implements MessageService {Autowiredprivate KafkaTemplateString,String kafkaTemplate;Overridepublic void sendMessage(String id) {System.out.println(待发送短信的订单已纳入处理队列kafkaidid);kafkaTemplate.send(itheima2022,id);} } 使用send方法发送消息需要传入topic名称。步骤④使用消息监听器在服务器启动后监听指定位置当消息出现后立即消费消息Component public class MessageListener {KafkaListener(topics itheima2022)public void onMessage(ConsumerRecordString,String record){System.out.println(已完成短信发送业务(kafka)idrecord.value());} } 使用注解KafkaListener定义当前方法监听Kafka中指定topic的消息接收到的消息封装在对象ConsumerRecord中获取数据从ConsumerRecord对象中获取即可。总结springboot整合Kafka使用KafkaTemplate对象作为客户端操作消息队列操作Kafka需要配置Kafka服务器地址默认端口9092企业开发时通常使用监听器来处理消息队列中的消息设置监听器使用注解KafkaListener。接收消息保存在形参ConsumerRecord对象中6.监控 在说监控之前需要回顾一下软件业的发展史。最早的软件完成一些非常简单的功能代码不多错误也少。随着软件功能的逐步完善软件的功能变得越来越复杂功能不能得到有效的保障这个阶段出现了针对软件功能的检测也就是软件测试。伴随着计算机操作系统的逐步升级软件的运行状态也变得开始让人捉摸不透出现了不稳定的状况。伴随着计算机网络的发展程序也从单机状态切换成基于计算机网络的程序应用于网络的程序开始出现由于网络的不稳定性程序的运行状态让使用者更加堪忧。互联网的出现彻底打破了软件的思维模式随之而来的互联网软件就更加凸显出应对各种各样复杂的网络情况之下的弱小。计算机软件的运行状况已经成为了软件运行的一个大话题针对软件的运行状况就出现了全新的思维建立起了初代的软件运行状态监控。 什么是监控就是通过软件的方式展示另一个软件的运行情况运行的情况则通过各种各样的指标数据反馈给监控人员。例如网络是否顺畅、服务器是否在运行、程序的功能是否能够整百分百运行成功内存是否够用等等等等。 本章要讲解的监控就是对软件的运行情况进行监督但是springboot程序与非springboot程序的差异还是很大的为了方便监控软件的开发springboot提供了一套功能接口为开发者加速开发过程。6-1.监控的意义 对于现代的互联网程序来说规模越来越大功能越来越复杂还要追求更好的客户体验因此要监控的信息量也就比较大了。由于现在的互联网程序大部分都是基于微服务的程序一个程序的运行需要若干个服务来保障因此第一个要监控的指标就是服务是否正常运行也就是监控服务状态是否处理宕机状态。一旦发现某个服务宕机了必须马上给出对应的解决方案避免整体应用功能受影响。其次由于互联网程序服务的客户量是巨大的当客户的请求在短时间内集中达到服务器后就会出现各种程序运行指标的波动。比如内存占用严重请求无法及时响应处理等这就是第二个要监控的重要指标监控服务运行指标。虽然软件是对外提供用户的访问需求完成对应功能的但是后台的运行是否平稳是否出现了不影响客户使用的功能隐患这些也是要密切监控的此时就需要在不停机的情况下监控系统运行情况日志是一个不错的手段。如果在众多日志中找到开发者或运维人员所关注的日志信息简单快速有效的过滤出要看的日志也是监控系统需要考虑的问题这就是第三个要监控的指标监控程序运行日志。虽然我们期望程序一直平稳运行但是由于突发情况的出现例如服务器被攻击、服务器内存溢出等情况造成了服务器宕机此时当前服务不能满足使用需要就要将其重启甚至关闭如果快速控制服务器的启停也是程序运行过程中不可回避的问题这就是第四个监控项管理服务状态。以上这些仅仅是从大的方面来思考监控这个问题还有很多的细节点例如上线了一个新功能定时提醒用户续费这种功能不是上线后马上就运行的但是当前功能是否真的启动如果快速的查询到这个功能已经开启这也是监控中要解决的问题等等。看来监控真的是一项非常重要的工作。 通过上述描述可以看出监控很重要。那具体的监控要如何开展呢还要从实际的程序运行角度出发。比如现在有3个服务支撑着一个程序的运行每个服务都有自己的运行状态。 此时被监控的信息就要在三个不同的程序中去查询并展示但是三个服务是服务于一个程序的运行的如果不能合并到一个平台上展示监控工作量巨大而且信息对称性差要不停的在三个监控端查看数据。如果将业务放大成30个300个3000个呢看来必须有一个单独的平台将多个被监控的服务对应的监控指标信息汇总在一起这样更利于监控工作的开展。 新的程序专门用来监控新的问题就出现了是被监控程序主动上报信息还是监控程序主动获取信息如果监控程序不能主动获取信息这就意味着监控程序有可能看到的是很久之前被监控程序上报的信息万一被监控程序宕机了监控程序就无法区分究竟是好久没法信息了还是已经下线了。所以监控程序必须具有主动发起请求获取被监控服务信息的能力。 如果监控程序要监控服务时主动获取对方的信息。那监控程序如何知道哪些程序被自己监控呢不可能在监控程序中设置我监控谁这样互联网上的所有程序岂不是都可以被监控到这样的话信息安全将无法得到保障。合理的做法只能是在被监控程序启动时上报监控程序告诉监控程序你可以监控我了。看来需要在被监控程序端做主动上报的操作这就要求被监控程序中配置对应的监控程序是谁。 被监控程序可以提供各种各样的指标数据给监控程序看但是每一个指标都代表着公司的机密信息并不是所有的指标都可以给任何人看的乃至运维人员所以对被监控指标的是否开放出来给监控系统看也需要做详细的设定。 以上描述的整个过程就是一个监控系统的基本流程。总结监控是一个非常重要的工作是保障程序正常运行的基础手段监控的过程通过一个监控程序进行它汇总所有被监控的程序的信息集中统一展示被监控程序需要主动上报自己被监控同时要设置哪些指标被监控6-2.可视化监控平台 springboot抽取了大部分监控系统的常用指标提出了监控的总思想。然后就有好心的同志根据监控的总思想制作了一个通用性很强的监控系统因为是基于springboot监控的核心思想制作的所以这个程序被命名为Spring Boot Admin。 Spring Boot Admin这是一个开源社区项目用于管理和监控SpringBoot应用程序。这个项目中包含有客户端和服务端两部分而监控平台指的就是服务端。我们做的程序如果需要被监控将我们做的程序制作成客户端然后配置服务端地址后服务端就可以通过HTTP请求的方式从客户端获取对应的信息并通过UI界面展示对应信息。 下面就来开发这套监控程序先制作服务端其实服务端可以理解为是一个web程序收到一些信息后展示这些信息。服务端开发步骤①导入springboot admin对应的starter版本与当前使用的springboot版本保持一致并将其配置成web工程dependencygroupIdde.codecentric/groupIdartifactIdspring-boot-admin-starter-server/artifactIdversion2.5.4/version /dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId /dependency 上述过程可以通过创建项目时使用勾选的形式完成。步骤②在引导类上添加注解EnableAdminServer声明当前应用启动后作为SpringBootAdmin的服务器使用SpringBootApplication EnableAdminServer public class Springboot25AdminServerApplication {public static void main(String[] args) {SpringApplication.run(Springboot25AdminServerApplication.class, args);} } 做到这里这个服务器就开发好了启动后就可以访问当前程序了界面如下。 由于目前没有启动任何被监控的程序所以里面什么信息都没有。下面制作一个被监控的客户端程序。客户端开发 客户端程序开发其实和服务端开发思路基本相似多了一些配置而已。步骤①导入springboot admin对应的starter版本与当前使用的springboot版本保持一致并将其配置成web工程dependencygroupIdde.codecentric/groupIdartifactIdspring-boot-admin-starter-client/artifactIdversion2.5.4/version /dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId /dependency 上述过程也可以通过创建项目时使用勾选的形式完成不过一定要小心端口配置成不一样的否则会冲突。步骤②设置当前客户端将信息上传到哪个服务器上通过yml文件配置spring:boot:admin:client:url: http://localhost:8080 做到这里这个客户端就可以启动了。启动后再次访问服务端程序界面如下。 可以看到当前监控了1个程序点击进去查看详细信息。 由于当前没有设置开放哪些信息给监控服务器所以目前看不到什么有效的信息。下面需要做两组配置就可以看到信息了。开放指定信息给服务器看允许服务器以HTTP请求的方式获取对应的信息配置如下server:port: 80 spring:boot:admin:client:url: http://localhost:8080 management:endpoint:health:show-details: alwaysendpoints:web:exposure:include: * 上述配置对于初学者来说比较容易混淆。简单解释一下到下一节再做具体的讲解。springbootadmin的客户端默认开放了13组信息给服务器但是这些信息除了一个之外其他的信息都不让通过HTTP请求查看。所以你看到的信息基本上就没什么内容了只能看到一个内容就是下面的健康信息。 但是即便如此我们看到健康信息中也没什么内容原因在于健康信息中有一些信息描述了你当前应用使用了什么技术等信息如果无脑的对外暴露功能会有安全隐患。通过配置就可以开放所有的健康信息明细查看了。management:endpoint:health:show-details: always 健康明细信息如下 目前除了健康信息其他信息都查阅不了。原因在于其他12种信息是默认不提供给服务器通过HTTP请求查阅的所以需要开启查阅的内容项使用*表示查阅全部。记得带引号。endpoints:web:exposure:include: * 配置后再刷新服务器页面就可以看到所有的信息了。 以上界面中展示的信息量就非常大了包含了13组信息有性能指标监控加载的bean列表加载的系统属性日志的显示控制等等。配置多个客户端 可以通过配置客户端的方式在其他的springboot程序中添加客户端坐标这样当前服务器就可以监控多个客户端程序了。每个客户端展示不同的监控信息。 进入监控面板如果你加载的应用具有功能在监控面板中可以看到3组信息展示的与之前加载的空工程不一样。类加载面板中可以查阅到开发者自定义的类如左图映射中可以查阅到当前应用配置的所有请求性能指标中可以查阅当前应用独有的请求路径统计数据总结开发监控服务端需要导入坐标然后在引导类上添加注解EnableAdminServer并将其配置成web程序即可开发被监控的客户端需要导入坐标然后配置服务端服务器地址并做开放指标的设定即可在监控平台中可以查阅到各种各样被监控的指标前提是客户端开放了被监控的指标思考 之前说过服务端要想监控客户端需要主动的获取到对应信息并展示出来。但是目前我们并没有在客户端开发任何新的功能但是服务端确可以获取监控信息谁帮我们做的这些功能呢咱们下一节再讲。6-3.监控原理 通过查阅监控中的映射指标可以看到当前系统中可以运行的所有请求路径其中大部分路径以/actuator开头 首先这些请求路径不是开发者自己编写的其次这个路径代表什么含义呢既然这个路径可以访问就可以通过浏览器发送该请求看看究竟可以得到什么信息。 通过发送请求可以得到一组json信息如下{_links: {self: {href: http://localhost:81/actuator,templated: false},beans: {href: http://localhost:81/actuator/beans,templated: false},caches-cache: {href: http://localhost:81/actuator/caches/{cache},templated: true},caches: {href: http://localhost:81/actuator/caches,templated: false},health: {href: http://localhost:81/actuator/health,templated: false},health-path: {href: http://localhost:81/actuator/health/{*path},templated: true},info: {href: http://localhost:81/actuator/info,templated: false},conditions: {href: http://localhost:81/actuator/conditions,templated: false},shutdown: {href: http://localhost:81/actuator/shutdown,templated: false},configprops: {href: http://localhost:81/actuator/configprops,templated: false},configprops-prefix: {href: http://localhost:81/actuator/configprops/{prefix},templated: true},env: {href: http://localhost:81/actuator/env,templated: false},env-toMatch: {href: http://localhost:81/actuator/env/{toMatch},templated: true},loggers: {href: http://localhost:81/actuator/loggers,templated: false},loggers-name: {href: http://localhost:81/actuator/loggers/{name},templated: true},heapdump: {href: http://localhost:81/actuator/heapdump,templated: false},threaddump: {href: http://localhost:81/actuator/threaddump,templated: false},metrics-requiredMetricName: {href: http://localhost:81/actuator/metrics/{requiredMetricName},templated: true},metrics: {href: http://localhost:81/actuator/metrics,templated: false},scheduledtasks: {href: http://localhost:81/actuator/scheduledtasks,templated: false},mappings: {href: http://localhost:81/actuator/mappings,templated: false}} } 其中每一组数据都有一个请求路径而在这里请求路径中有之前看到过的health发送此请求又得到了一组信息{status: UP,components: {diskSpace: {status: UP,details: {total: 297042808832,free: 72284409856,threshold: 10485760,exists: true}},ping: {status: UP}} } 当前信息与监控面板中的数据存在着对应关系 原来监控中显示的信息实际上是通过发送请求后得到json数据然后展示出来。按照上述操作可以发送更多的以/actuator开头的链接地址获取更多的数据这些数据汇总到一起组成了监控平台显示的所有数据。 到这里我们得到了一个核心信息监控平台中显示的信息实际上是通过对被监控的应用发送请求得到的。那这些请求谁开发的呢打开被监控应用的pom文件其中导入了springboot admin的对应的client在这个资源中导入了一个名称叫做actuator的包。被监控的应用之所以可以对外提供上述请求路径就是因为添加了这个包。 这个actuator是什么呢这就是本节要讲的核心内容监控的端点。 Actuator可以称为端点描述了一组监控信息SpringBootAdmin提供了多个内置端点通过访问端点就可以获取对应的监控信息也可以根据需要自定义端点信息。通过发送请求路劲**/actuator可以访问应用所有端点信息如果端点中还有明细信息可以发送请求/actuator/端点名称**来获取详细信息。以下列出了所有端点信息说明ID描述默认启用auditevents暴露当前应用程序的审计事件信息。是beans显示应用程序中所有 Spring bean 的完整列表。是caches暴露可用的缓存。是conditions显示在配置和自动配置类上评估的条件以及它们匹配或不匹配的原因。是configprops显示所有 ConfigurationProperties 的校对清单。是env暴露 Spring ConfigurableEnvironment 中的属性。是flyway显示已应用的 Flyway 数据库迁移。是health显示应用程序健康信息是httptrace显示 HTTP 追踪信息默认情况下最后 100 个 HTTP 请求/响应交换。是info显示应用程序信息。是integrationgraph显示 Spring Integration 图。是loggers显示和修改应用程序中日志记录器的配置。是liquibase显示已应用的 Liquibase 数据库迁移。是metrics显示当前应用程序的指标度量信息。是mappings显示所有 RequestMapping 路径的整理清单。是scheduledtasks显示应用程序中的调度任务。是sessions允许从 Spring Session 支持的会话存储中检索和删除用户会话。当使用 Spring Session 的响应式 Web 应用程序支持时不可用。是shutdown正常关闭应用程序。否threaddump执行线程 dump。是heapdump返回一个 hprof 堆 dump 文件。是jolokia通过 HTTP 暴露 JMX bean当 Jolokia 在 classpath 上时不适用于 WebFlux。是logfile返回日志文件的内容如果已设置 logging.file 或 logging.path 属性。支持使用 HTTP Range 头来检索部分日志文件的内容。是prometheus以可以由 Prometheus 服务器抓取的格式暴露指标。是 上述端点每一项代表被监控的指标如果对外开放则监控平台可以查询到对应的端点信息如果未开放则无法查询对应的端点信息。通过配置可以设置端点是否对外开放功能。使用enable属性控制端点是否对外开放。其中health端点为默认端点不能关闭。management:endpoint:health: # 端点名称show-details: alwaysinfo: # 端点名称enabled: true # 是否开放 为了方便开发者快速配置端点springboot admin设置了13个较为常用的端点作为默认开放的端点如果需要控制默认开放的端点的开放状态可以通过配置设置如下management:endpoints:enabled-by-default: true # 是否开启默认端点默认值true 上述端点开启后就可以通过端点对应的路径查看对应的信息了。但是此时还不能通过HTTP请求查询此信息还需要开启通过HTTP请求查询的端点名称使用“*”可以简化配置成开放所有端点的WEB端HTTP请求权限。management:endpoints:web:exposure:include: * 整体上来说对于端点的配置有两组信息一组是endpoints开头的对所有端点进行配置一组是endpoint开头的对具体端点进行配置。management:endpoint: # 具体端点的配置health:show-details: alwaysinfo:enabled: trueendpoints: # 全部端点的配置web:exposure:include: *enabled-by-default: true总结被监控客户端通过添加actuator的坐标可以对外提供被访问的端点功能端点功能的开放与关闭可以通过配置进行控制web端默认无法获取所有端点信息通过配置开放端点功能6-4.自定义监控指标 端点描述了被监控的信息除了系统默认的指标还可以自行添加显示的指标下面就通过3种不同的端点的指标自定义方式来学习端点信息的二次开发。INFO端点 info端点描述了当前应用的基本信息可以通过两种形式快速配置info端点的信息配置形式在yml文件中通过设置info节点的信息就可以快速配置端点信息info:appName: project.artifactIdversion: project.versioncompany: 传智教育author: itheima配置完毕后对应信息显示在监控平台上也可以通过请求端点信息路径获取对应json信息编程形式通过配置的形式只能添加固定的数据如果需要动态数据还可以通过配置bean的方式为info端点添加信息此信息与配置信息共存Component public class InfoConfig implements InfoContributor {Overridepublic void contribute(Info.Builder builder) {builder.withDetail(runTime,System.currentTimeMillis()); //添加单个信息Map infoMap new HashMap(); infoMap.put(buildTime,2006);builder.withDetails(infoMap); //添加一组信息} }Health端点 health端点描述当前应用的运行健康指标即应用的运行是否成功。通过编程的形式可以扩展指标信息。Component public class HealthConfig extends AbstractHealthIndicator {Overrideprotected void doHealthCheck(Health.Builder builder) throws Exception {boolean condition true;if(condition) {builder.status(Status.UP); //设置运行状态为启动状态builder.withDetail(runTime, System.currentTimeMillis());Map infoMap new HashMap();infoMap.put(buildTime, 2006);builder.withDetails(infoMap);}else{builder.status(Status.OUT_OF_SERVICE); //设置运行状态为不在服务状态builder.withDetail(上线了吗,你做梦);}} } 当任意一个组件状态不为UP时整体应用对外服务状态为非UP状态。Metrics端点 metrics端点描述了性能指标除了系统自带的监控性能指标还可以自定义性能指标。Service public class BookServiceImpl extends ServiceImplBookDao, Book implements IBookService {Autowiredprivate BookDao bookDao;private Counter counter;public BookServiceImpl(MeterRegistry meterRegistry){counter meterRegistry.counter(用户付费操作次数);}Overridepublic boolean delete(Integer id) {//每次执行删除业务等同于执行了付费业务counter.increment();return bookDao.deleteById(id) 0;} } 在性能指标中就出现了自定义的性能指标监控项自定义端点 可以根据业务需要自定义端点方便业务监控Component Endpoint(idpay,enableByDefault true) public class PayEndpoint {ReadOperationpublic Object getPay(){Map payMap new HashMap();payMap.put(level 1,300);payMap.put(level 2,291);payMap.put(level 3,666);return payMap;} } 由于此端点数据spirng boot admin无法预知该如何展示所以通过界面无法看到此数据通过HTTP请求路径可以获取到当前端点的信息但是需要先开启当前端点对外功能或者设置当前端点为默认开发的端点。总结端点的指标可以自定义但是每种不同的指标根据其功能不同自定义方式不同info端点通过配置和编程的方式都可以添加端点指标health端点通过编程的方式添加端点指标需要注意要为对应指标添加启动状态的逻辑设定metrics指标通过在业务中添加监控操作设置指标可以自定义端点添加更多的指标
http://www.tj-hxxt.cn/news/136731.html

相关文章:

  • 网站站点地图网站特色栏目重要性
  • 如何建立自己公司的官方网站wordpress 头像 很慢
  • 免费com域名注册网站聊城专业网站制作公司
  • 深圳宝安p2p网站系统的建设天津制作企业网站报价
  • 安徽省和住房建设厅网站河北建设厅网站开通账号
  • 企业建设网站的目的是什么免费
  • 一个网站的构建TP5.1做的网站首页被挂马原因
  • 佛山企业网站建设中国建设银行网站查询
  • 卖磁铁的网站怎么做长沙优化科技有限公司地址
  • 微网站开发 在线商城打广告专用配图
  • a站网址是什么网店运营实训报告总结
  • 成都建设厅官方网站清苑住房和城乡建设局网站
  • 网站建设与维护实训总结网站数据库空间
  • 建设视频网站链接百度云盘伍佰亿网站系统
  • 建设公关型的企业网站有哪些百度热议怎么上首页
  • 网站宽度一般是多少深圳石岩做网站的公司
  • 做网站 怎么赚钱吗网站单页推广
  • 无锡h5网站建设十大软件免费下载安装手机版
  • 一个网站多个域名十堰电商网站建设
  • 企业网站系统功能分析与设计店铺推广方式有哪些
  • 保健品网站可以做网站资源网站平台建设方案
  • 中国建设银行网站包头分行乐清网络
  • 用360打开自己做的网站有广告seo 新老网站替换 域名不变
  • 做网站如何选择数据源百度网站下拉怎么做
  • 帮助中心网站怎么做seo是什么意思呢
  • 网站开发中的视图页面指的是什么广东手机版建站系统开发
  • 网站域名跳转怎么弄自己开发企业管理系统
  • 荥阳郑州网站建设开发公司截留占用住宅专项维修资金
  • 用织梦做领券网站软文兼职10元一篇
  • 房产信息网网站建设路街道办事处门户网站