新手学习做网站/如何在百度推广自己的产品
前言
在日常工作中,随着业务日渐庞大,不可避免的涉及到调用远程服务,但是远程服务的健壮性和网络稳定性都是不可控因素,因此,我们需要考虑合适的重试机制去处理这些问题,最基础的方式就是手动重试,侵入业务代码去处理,再高端一点的通过切面去处理,较为优雅的实现重试,下面,介绍两个重试框架,只需要配置好重启策略及重试任务,即可使用。
重试任务
这里只是模拟传参、相应及异常,具体任务需对应业务
package com.example.test.MessageRetry;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;@Slf4j
public class RetryTask {/*** 重试方法* @param param* @return*/public static boolean retryTask(String param){log.info("请求参数:{}",param);int i = RandomUtils.nextInt(0,15);log.info("随机数:{}",i);if(i == 0){log.error("参数异常");throw new IllegalArgumentException("参数异常");}else if(i > 10){log.error("访问异常");throw new RemoteAccessException("访问异常");}else if(i % 2 == 0){log.info("成功");return true;}else{log.info("失败");return false;}}
}
Spring-Retry
简介
Spirng-Retry是Spring提供的一个重试框架,为spring程序提供声明式重试支持,主要针对可能抛出异常的调用操作,进行有策略的重试。可以通过代码方式和注解方式实现,主要由重试执行者和两个策略构成:
- RetryTemplet:重试执行者,可以设置重试策略(设置重试上线、如何重试)和回退策略(立即重试还是等待一段时间后重试,默认立即重试,如果需要配置等待一段时间后重试则需要指定回退策略),通过Execute提交执行操作,只有在调用时抛出指定配置的异常,才会执行重试
- 重试策略:
策略 | 方式 |
---|---|
NeverRetryPolicy | 只允许调用RetryCallback一次,不允许重试 |
AlwaysRetryPolicy | 允许无限重试,直到成功,此方式逻辑不当会导致死循环 |
SimpleRetryPolicy | 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略 |
TimeoutRetryPolicy | 超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试 |
ExceptionClassifierRetryPolicy | 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试 |
CircuitBreakerRetryPolicy | 有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate |
CompositeRetryPolicy | 组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,悲观组合重试策略是指只要有一个策略不允许即可以重试,但不管哪种组合方式,组合中的每一个策略都会执行 |
- 重试回退策略:
回退策略 | 方式 |
---|---|
NoBackOffPolicy | 无退避算法策略,每次重试时立即重试 |
FixedBackOffPolicy | 固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒 |
UniformRandomBackOffPolicy | 随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒 |
ExponentialBackOffPolicy | 指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier |
ExponentialRandomBackOffPolicy | 随机指数退避策略,引入随机乘数可以实现随机乘数回退 |
- 此外,还需要配置重试时间间隔、最大重试次数以及可重试异常
实现
代码方式
RetryTemplate通过execute提交执行操作,需要准备RetryCallback和RecoveryCallback两个类实例,前者对应的就是重试回调逻辑实例,包装正常的功能操作,RecoveryCallback实现的是整个执行操作结束的恢复操作实例,只有在调用的时候抛出了异常,并且异常是在exceptionMap中配置的异常,才会执行重试操作,否则就调用到excute方法的第二个执行方法RecoveryCallback中
package com.example.test.MessageRetry;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
@Slf4j
public class SpringRetryService {/*** 重试时间间隔ms,默认1000ms*/private long retryPeriodTime = 5000L;/*** 最大重试次数*/private int maxRetryNum = 3;/*** 哪些结果和异常要重试,key表示异常类型,value表示是否需要重试*/private Map<Class<? extends Throwable>,Boolean> retryMap = new HashMap<>();@Testpublic void test(){retryMap.put(IllegalArgumentException.class,true);retryMap.put(RemoteAccessException.class,true);//构建重试模板RetryTemplate retryTemplate = new RetryTemplate();//设置重试回退操作策略,主要设置重试时间间隔FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();backOffPolicy.setBackOffPeriod(retryPeriodTime);//设置重试策略,主要设置重试次数SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryNum,retryMap);retryTemplate.setRetryPolicy(retryPolicy);retryTemplate.setBackOffPolicy(backOffPolicy);Boolean execute = retryTemplate.execute(//RetryCallbackretryContext -> {boolean b = RetryTask.retryTask("aaa");log.info("调用结果:{}",b);return b;},retryContext -> {//RecoveryCallbacklog.info("到达最多尝试次数");return false;});log.info("执行结果:{}",execute);}
}
注解方式
上面我们说到Spring-Retry是Spring提供的,那么,它就支持依赖整合
<!--spring-retry--><dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId><version>1.2.2.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.1</version></dependency>
然后,在启动类上添加开启注解
//表示是否开启重试,属性proxyTargetClass,boolean类型,是否创建基于子类(CGLIB)的代理,而不是标准的基于接口的代理,默认false
@EnableRetry
package com.example.test.MessageRetry;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;@Slf4j
public class RetryTask {/*** 重试方法* @param param* @return*/public static boolean retryTask(String param){log.info("请求参数:{}",param);int i = RandomUtils.nextInt(0,15);log.info("随机数:{}",i);if(i >-1){log.error("参数异常");throw new IllegalArgumentException("参数异常");
// }else if(i > 10){
// log.error("访问异常");
// throw new RemoteAccessException("访问异常");
// }else if(i % 2 == 0){
// log.info("成功");
// return true;}else{log.info("失败");return false;}}
}
/*** 重试调用方法* @param param* @return* @Retryable注解:**/@Retryable(value = {RemoteAccessException.class,IllegalArgumentException.class},maxAttempts = 3,backoff = @Backoff(delay = 5000L,multiplier = 2))public void call(String param){RetryTask.retryTask(param);}/*** 达到最大重试次数,或抛出了没有指定的异常* @param e* @param param* @return*/@Recoverpublic void recover(Exception e,String param){log.error("达到最大重试次数!!!!!");}
@Testpublic void retry(){springRetryService.call("aaa");}
- @Retryable注解说明
属性 | 说明 |
---|---|
value | 指定重试的异常类型,默认为空 |
maxAttempts | 最大尝试次数,默认3次 |
include | 和value一样,默认为空,当exclude也为空时,默认所有异常 |
exclude | 指定不处理的异常 |
Backoff | 重试策略 |
- @Backoff注解说明:设定重试倍数,每次重试时间是上次的n倍
属性 | 说明 |
---|---|
delay | 重试之间的等待时间(以毫秒为单位),默认0 |
maxDelay | 重试之间的最大等待时间(以毫秒为单位),默认0 |
multiplier | 延迟的倍数,默认0.0 |
delayExpression | 重试之间的等待时间表达式,默认空 |
maxDelayExpression | 重试之间的最大等待时间表达式,默认空 |
multiplierExpression | 指定延迟的倍数表达式,默认空 |
random | 随机指定延迟时间,默认false |
-
@Recover注解说明:当重试到达指定次数时,将要回调的方法
-
@Retryable和@Recover修饰的方法要在同一个类中,且被@Retryable和@Recover标记的方法不能有返回值,这样Recover方法才会生效。由于@Retryable注解是通过切面实现的,因此我们要避免@Retryable 注解的方法的调用方和被调用方处于同一个类中,因为这样会使@Retryable 注解失效。
我们可以看到,Spring-Retry只能针对指定异常重试,不能根据执行结果返回值重试,整体使用也比较死板,下面,看下更加灵活的Guava-Retry。
Guava-Retry
简介
Guava-Retry是谷歌的Guava库的一个小扩展,允许为任意函数调用创建可配置的重试策略,我们可以通过构建重试实例RetryBuilder,来设置重试源、配置重试次数、重试超时时间、等待时间间隔等,实现优雅的重试机制。
- 主要属性
属性 | 说明 |
---|---|
attemptTimeLimiter | 时间限制策略,单次任务执行时间限制,超时终止 |
stopStrategy | 停止重试策略 |
waitStrategy | 等待策略 |
blockStrategy | 任务阻塞策略,即当前任务执行完,下次任务执行前做什么,仅有线程阻塞threadSleepStrategy |
retryException | 重试异常(重试策略) |
listeners | 自定义重试监听器,可用于记录日志等 |
- 时间限制策略
策略 | 说明 |
---|---|
NoAttemptTimeLimit | 对代理方法不添加时间限制,默认 |
FixedAttemptTimeLimit | 对代理方法的尝试添加固定时间限制 |
- 重试策略(重试异常)
策略 | 说明 |
---|---|
retryIfException | 抛出 runtime 异常、checked 异常时都会重试,但是抛出 error 不会重试 |
retryIfRuntimeException | 只会在抛 runtime 异常的时候才重试,checked 异常和error 都不重试 |
retryIfExceptionOfType | 允许我们只在发生特定异常的时候才重试,比如NullPointerException 和 IllegalStateException 都属于 runtime 异常,也包括自定义的error |
retryIfResult | 可以指定你的 Callable 方法在返回值的时候进行重试 |
- 停止策略
策略 | 说明 |
---|---|
StopAfterDelayStrategy | 设定最长执行时间,无论任务执行几次,一旦超时,任务终止,返回RetryException |
StopAfterAttemptStrategy | 设定最大尝试次数,一旦超过,返回重试异常 |
NeverStopStrategy | 一直轮询直到获取期望结果 |
- 等待策略
策略 | 说明 |
---|---|
ExceptionWaitStrategy | 异常时长等待,如果抛出的是指定异常,则从传入的方法中取得等待时间并返回;如果异常不匹配,则返回等待时间为0L |
CompositeWaitStrategy | 复合时长等待,在获取等待时间时会获取多种等待策略各自的等待时间,然后累加这些等待时间 |
FibonacciWaitStrategy | 斐波那契等待策略 |
ExponentialWaitStrategy | 指数等待时长,指数增长,若设置了最大时间,则停止,否则到Long.MAX_VALUE |
IncrementingWaitStrategy | 递增等待,提供一个初始时长和步长,随次数叠加 |
RandomWaitStrategy | 随机等待时长,可以提供一个最大和最小时间,从范围内随机 |
FixedWaitStrategy | 固定等待时长 |
代码
<!--guava-retryer--><dependency><groupId>com.github.rholder</groupId><artifactId>guava-retrying</artifactId><version>2.0.0</version></dependency>
package com.example.test.MessageRetry;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;@Slf4j
public class RetryTask {/*** 重试方法* @param param* @return*/public static boolean retryTask(String param){log.info("请求参数:{}",param);int i = RandomUtils.nextInt(0,15);log.info("随机数:{}",i);if(i < 3){log.error("参数异常");throw new IllegalArgumentException("参数异常");}else if(i > 10){log.error("访问异常");throw new RemoteAccessException("访问异常");}else if(i % 2 == 0){log.info("成功");return true;}else{log.info("失败");return false;}}
}
package com.example.test.MessageRetry;import com.github.rholder.retry.*;
import com.google.common.base.Predicates;
import lombok.extern.slf4j.Slf4j;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
@Slf4j
public class GuavaRetryService {public void guavaRetry(){//构建重试实例RetryBuilder,可以设置重试源,可以配置重试次数、重试超时时间、等待时间间隔Retryer<Boolean>retryer = RetryerBuilder.<Boolean>newBuilder()//设置异常重试源.retryIfExceptionOfType(RemoteAccessException.class).retryIfExceptionOfType(IllegalArgumentException.class)//设置根据结果重试 res->res==false Predicates.containsPattern("_error$").retryIfResult(Predicates.equalTo(false))//设置等待时间.withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))//设置最大重试次数.withStopStrategy(StopStrategies.stopAfterAttempt(3))//设置重试监听,可用作重试时的额外动作.withRetryListener(new RetryListener() {@Overridepublic <V> void onRetry(Attempt<V> attempt) {log.error("第【{}】次调用失败",attempt.getAttemptNumber());}})//设置阻塞策略.withBlockStrategy(BlockStrategies.threadSleepStrategy())//设置时间限制.withAttemptTimeLimiter(AttemptTimeLimiters.noTimeLimit()).build();try{retryer.call(()->RetryTask.retryTask("aaa"));}catch (Exception e){e.printStackTrace();}}
}
可以看到,我们设置了重试三次,超过这个限制没有执行成功,抛出了重试异常,而且也可以根据我们的返回结果来判断。
总结
Spring-Retry和Guava-Retry都是线程安全的重试框架,能够保证并发业务下重试逻辑的正确性。两者都很好的将正常方法和重试方法进行了解耦,可以设置超时时间、重试次数、间隔时间、监听结果等,相比来说,Guava-Retry比Spring-Retry更加灵活,并且可以通过返回值来进行重试,两者都是非常好的重试框架,具体的选用看相关的业务场景即可。
相关文章:

重试框架入门:Spring-RetryGuava-Retry
前言 在日常工作中,随着业务日渐庞大,不可避免的涉及到调用远程服务,但是远程服务的健壮性和网络稳定性都是不可控因素,因此,我们需要考虑合适的重试机制去处理这些问题,最基础的方式就是手动重试…...

[QCM6125][Android13] 修复PRODUCT_COPY_FILES无法拷贝so
文章目录 开发平台基本信息问题描述解决方法 开发平台基本信息 芯片: QCM6125 版本: Android 13 kernel: msm-4.14 问题描述 在进行系统移植时,经常会把一些自己开发的c或者c程序编译成so库,然后在系统服务中去调用这些库。所以在进行新代码开发时&am…...

微服务Eureka注册中心
目录 一、Eureka的结构和作用 二、搭建eureka-server 三、服务注册 四、服务发现 假如我们的服务提供者user-service部署了多个实例,如图: 存在的问题: order-service在发起远程调用的时候,该如何得知user-service实例的ip地址…...

Java:企业级java后端开发,需要掌握哪些内容
一、什么是后端开发 后端开发是指开发基于服务器端的软件应用程序,也称为系统的后台或服务器端编程。 后端程序员负责处理网站或应用程序后台的逻辑和功能,包括数据库管理、服务器端脚本编写、API设计、数据安全性、网站性能优化等。 后端开发技术通常包…...

使用Go语言生成Excel任务表依赖图(Markdown文件mermaid图)
一、前言 在游戏中,任务是非常常见的玩法,可能会有主线任务,支线任务以及其它一些类型的任务,各任务可能还会有前置任务,即需要完成某个任务之后,才能做当前任务。在游戏开发中,配置表可以使用…...

C语言和C++的区别在哪?如何自学C++?
C语言和C是两种不同的编程语言,它们在语法、特性和用途上有一些区别。以下是C语言和C的一些主要区别: 面向对象编程:C是一种支持面向对象编程的语言,它在C语言的基础上添加了类、对象、继承、多态等面向对象的特性。而C语言是一种…...

功能强大的开源数据中台系统 DataCap 1.13.0 发布
推荐一套基于 SpringBoot 开发的简单、易用的开源权限管理平台,建议下载使用: https://github.com/devlive-community/authx 推荐一套为 Java 开发人员提供方便易用的 SDK 来与 OpenAI 的 API 进行交互组件:https://github.com/devlive-community/openai…...

JTS Self-intersection异常TopologyException: side location conflict解决办法
JTS Self-intersection异常TopologyException: side location conflict解决办法 举例:问题围栏 MULTIPOLYGON (((114.0905685 32.1120567, 114.0905685 32.112957, 114.0905685 32.1138535, 114.0905685 32.1147537, 114.0905685 32.115654, 114.0905685 32.11655…...

Maven: No compiler is provided in this environment.
在Eclipse中运行Maven项目,报错: No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK? 解决方法: Windows > Preferences > Java > Installed JREs > Add > Standard VM,…...

.NET-10. 其他-VSTO+VBA
VSTOVBA 前言VSTO 外接程序介绍:VSTO参考链接:VSTO 例子: VBA:参考链接: 前言 主要用于Excel插件。 VSTO 外接程序介绍: Excel、Word、PowerPoint、Project、Visio等等Office应用程序 相对简单 VSTO参考链接&#x…...

相机传感器格式与镜头光圈参数
相机靶面大小 CCD/CMOS图像传感器尺寸(sensor format)1/2’‘、1/3’‘、1/4’实际是多大 1英寸——靶面尺寸为宽12.7mm*高9.6mm,对角线16mm。 2/3英寸——靶面尺寸为宽8.8mm*高6.6mm,对角线11mm。 1/2英寸——靶面尺寸为宽6.…...

Android 设置头像(拍照获取、相册获取、裁剪照片)
在Android原生态开发过程中,往往会设计到用户头像的设置问题,一般来讲设置头像需要用到拍照、获取照片、存储照片、裁剪照片、显示照片等问题,本文将一步一步的进行说明讲解。 首先需要强调几点我在开发过程中遇到的问题。 权限问题…...

android开发之Android 自定义滑动解锁View
自定义滑动解锁View 需求如下: 近期需要做一个类似屏幕滑动解锁的功能,右划开始,左划暂停。 需求效果图如下 实现效果展示 自定义view如下 /** Desc 自定义滑动解锁View Author ZY Mail sunnyfor98gmail.com Date 2021/5/17 11:52 *…...

CAD绘制法兰、添加光源、材质并渲染
首先绘制两个圆柱体,相互嵌套 在顶部继续绘制圆柱体,这是之后要挖掉的部分 在中央位置绘制正方形 用圆角工具: 将矩形的四个角分别处理,效果: 用拉伸工具 向上拉伸到和之前绘制的圆柱体高度齐平 绘制一个圆柱体&#…...

ChatGPT访问流量下降的原因分析
自从OpenAI的ChatGPT于11月问世以来,这款聪明的人工智能聊天机器人就席卷了全世界,人们在试用该工具的同时也好奇该技术到底将如何改变我们的工作和生活。 但近期Similarweb表示,自去ChatGPT上线以来,该网站的访问量首次出现下…...

干货 | 详述 Elasticsearch 向量检索发展史
1. 引言 向量检索已经成为现代搜索和推荐系统的核心组件。 通过将复杂的对象(例如文本、图像或声音)转换为数值向量,并在多维空间中进行相似性搜索,它能够实现高效的查询匹配和推荐。 图片来自:向量数据库技术鉴赏【上…...

mysql常见面试题,高频题目放送
互联网的产品架构是包含这接入层,逻辑处理以及储存层的,其中储存层承载着较多的数据以及持久化的任务,而说到储存层,避免不了说到数据库,在我们面试的时候,数据库的知识题目占比是非常多的: 1.…...

使用 PowerShell 将 Excel 中的每个工作表单独另存为独立的文件
导语:在日常工作中,我们经常需要处理 Excel 文件。本文介绍了如何使用 PowerShell 脚本将一个 Excel 文件中的每个工作表单独另存为独立的 Excel 文件,以提高工作效率。 1. 准备工作 在开始之前,请确保已经安装了 Microsoft Exc…...

python提取pdf图片
import fitz import re import osdef save_pdf_img(path, save_path):path: pdf的路径save_path : 图片存储的路径# 使用正则表达式来查找图片checkXO r"/Type(? */XObject)"checkIM r"/Subtype(? */Image)"# 打开pdfdoc fitz.open(path)# 图片计数im…...

Vue3 表单输入绑定简单应用
去官网学习→表单输入绑定 | Vue.js 运行示例: 代码:HelloWorld.vue <template><div class"hello"><h1>Vue 表单输入绑定</h1><input type"text" placeholder"输入框" v-model"msg"…...

如何解决 Elasticsearch 查询缓慢的问题以获得更好的用户体验
作者:Philipp Kahr Elasticsearch Service 用户的重要注意事项:目前,本文中描述的 Kibana 设置更改仅限于 Cloud 控制台,如果没有我们支持团队的手动干预,则无法进行配置。 我们的工程团队正在努力消除对这些设置的限制…...

近期学习练习
练习: 或: //sqrt是数学库函数,开平方 //头文件为math.h 或...

平台安全之中间件安全
理解中间件 一次web访问的顺序,web浏览器->web服务器(狭义)->web容器->应用服务器->数据库服务器 web服务器 广义:提供广义web服务的软件或主机 狭义:提供w3服务的软件或主机,即Web服务器软件…...

芒果 TV 基于 Flink 的实时数仓建设实践
公司简介:芒果 TV 作为湖南广电旗下互联网视频平台,在“一云多屏,多元一体”的战略指导下,通过内容自制,培植核心竞争力,从独播、独特走向独创,并通过市场化运作完成 A 轮、B 轮融资,…...

尚硅谷大数据项目《在线教育之采集系统》笔记004
视频地址:尚硅谷大数据项目《在线教育之采集系统》_哔哩哔哩_bilibili 目录 P047 P048 P049 P050 P051 P052 P053 P054 P055 P056 P047 /opt/module/datax/job/base_province.json [atguigunode001 ~]$ hadoop fs -mkdir /base_province/2022-02-22 [atgu…...

R语言4_安装BayesSpace
环境Ubuntu22/20, R4.1 你可能会报错说你的R语言版本没有这个库,但其实不然。这是一个在Bioconductor上的库。 同时我也碰到了这个问题,ERROR: configuration failed for package systemfonts’等诸多类似问题,下面的方法可以一并解决。 第…...

TSINGSEE青犀视频安防监控视频平台EasyCVR设备在线,视频无法播放的原因排查
可支持国标GB28181、RTMP、RTSP/Onvif、海康Ehome、海康SDK、大华SDK、宇视SDK等多种协议接入的安防监控视频平台EasyCVR基于云边端一体化架构,具有强大的数据接入、处理及分发能力,可在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、…...

【算法篇C++实现】算法的时间、空间复杂度
文章目录 🚀一、算法的概念🚀二、算法的特征1.可行性2.确定性3.有穷性4.输入5.输出 🚀三、算法的评价1.正确性2.可读性3.健壮性 🚀四、算法的复杂度⛳(一)时间复杂度1、时间复杂度的概念2、大O的渐进表示法…...

On Evaluation of Embodied Navigation Agents 论文阅读
论文信息 题目:On Evaluation of Embodied Navigation Agents 作者:Peter Anderson,Angel Chang 来源:arXiv 时间:2018 Abstract 过去两年,导航方面的创造性工作激增。这种创造性的输出产生了大量有时不…...

【CSS 布局】水平垂直方向居中
【CSS 布局】水平垂直方向居中 单行元素 <div class"container"><div class"item"></div> </div>方式一:relative 和 absolute .container {position: relative;height: 400px;border: 1px solid #ccc;.item {posit…...