最新要闻
- 锵锵三人行停播原因是什么?锵锵三人行女嘉宾名单大全
- 沙海吴邪的计划是什么?沙海吴邪的计划成功了吗?
- 什么边野草花什么口夕阳斜?什么边野草花什么口夕阳斜是什么诗?
- 九月青豆角产自什么村?九月青豆角怎么冻好吃?
- 天国的阶梯结局是什么?天国的阶梯演员表
- 天天看点:卡梅隆感染新冠 缺席洛杉矶首映式
- 想“白嫖”20万的私人飞机 推特被告了
- 【报资讯】短了5厘米照样强大 迷你SSD硬盘雄起 速度冲向5GB/s
- 全球聚焦:小米13、iPhone 14 Pro全角度对比:小米正面碾压式完胜 背面有争议
- 环球快消息!世界杯半决赛现场将播放两首中文歌:你肯定都听过
- 环球播报:硬刚小米13!moto X40核心配置官方全面揭晓
- 环球热头条丨男子帮摔倒大爷报警反被讹引热议:已和解 对方赔偿男子2000元误工费
- 环球即时:旗舰猛兽!小米万兆路由器明天首销:1799元
- 天天时讯:太强了!卢伟冰称小米13 Pro应该叫“小米13 Pro Ultra”
- 【速看料】《三体》动画爆火!两部真人版剧集明年开播:网飞、腾讯对标
- 环球热推荐:又一合资倒下?家喻户晓的斯柯达或将退出中国市场
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
焦点速看:深入理解Whitelabel Error Page底层源码
深入理解Whitelabel Error Page底层源码
(一)服务器请求处理错误则转发请求url
StandardHostValve的invoke()方法将根据请求的url选择正确的Context来进行处理。在发生错误的情况下,内部将调用status()或throwable()来进行处理。具体而言,当出现HttpStatus错误时,则将由status()进行处理。当抛出异常时,则将由throwable()进行处理。status()和throwable()的内部均是通过Context来查找对应的ErrorPage,并最终调用custom()来进行处理。custom()用于将请求转发到ErrorPage错误页面中。
(资料图片仅供参考)
在SpringBoot项目中,如果服务器处理请求失败,则会通过上述的过程将请求转发到/error中。
final class StandardHostValve extends ValveBase { private void status(Request request, Response response) { // ... Context context = request.getContext(); // ... // 从Context中查找ErrorPag ErrorPage errorPage = context.findErrorPage(statusCode); // ... // 调用custom() custom(request, response, errorPage); // ... } protected void throwable(Request request, Response response, Throwable throwable) { // ... // 从Context查找ErrorPage ErrorPage errorPage = context.findErrorPage(throwable); // ... // 调用custom() custom(request, response, errorPage); // ... } private boolean custom(Request request, Response response, ErrorPage errorPage) { // ... // 请求转发 rd.forward(request.getRequest(), response.getResponse()); // ... }}
(二)路径为/error的ErrorPage
为了能在Context中查找到ErrorPage,则必须先通过addErrorPage()来添加ErrorPage。在运行时,Context具体由StandardContext进行处理。
public class StandardContext extends ContainerBase implements Context, NotificationEmitter { private final ErrorPageSupport errorPageSupport = new ErrorPageSupport(); @Override public void addErrorPage(ErrorPage errorPage) { // Validate the input parameters if (errorPage == null) throw new IllegalArgumentException (sm.getString("standardContext.errorPage.required")); String location = errorPage.getLocation(); if ((location != null) && !location.startsWith("/")) { if (isServlet22()) { if(log.isDebugEnabled()) log.debug(sm.getString("standardContext.errorPage.warning", location)); errorPage.setLocation("/" + location); } else { throw new IllegalArgumentException (sm.getString("standardContext.errorPage.error", location)); } } errorPageSupport.add(errorPage); fireContainerEvent("addErrorPage", errorPage); }}
addErrorPage()具体由是由TomcatServletWebServerFactory的configureContext()方法来调用的。
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactoryimplements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { protected void configureContext(Context context, ServletContextInitializer[] initializers) { TomcatStarter starter = new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context; embeddedContext.setStarter(starter); embeddedContext.setFailCtxIfServletStartFails(true); } context.addServletContainerInitializer(starter, NO_CLASSES); for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) { context.addLifecycleListener(lifecycleListener); } for (Valve valve : this.contextValves) { context.getPipeline().addValve(valve); } for (ErrorPage errorPage : getErrorPages()) { org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage(); tomcatErrorPage.setLocation(errorPage.getPath()); tomcatErrorPage.setErrorCode(errorPage.getStatusCode()); tomcatErrorPage.setExceptionType(errorPage.getExceptionName()); context.addErrorPage(tomcatErrorPage); } for (MimeMappings.Mapping mapping : getMimeMappings()) { context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); } configureSession(context); new DisableReferenceClearingContextCustomizer().customize(context); for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) { customizer.customize(context); } }}
先调用getErrorPages()获取所有错误页面,然后再调用Context的addErrorPage()来添加ErrorPage错误页面。
getErrorPages()中的错误页面是通过AbstractConfigurableWebServerFactory的addErrorPages()来添加的。
public abstract class AbstractConfigurableWebServerFactory implements ConfigurableWebServerFactory { @Override public void addErrorPages(ErrorPage... errorPages) { Assert.notNull(errorPages, "ErrorPages must not be null"); this.errorPages.addAll(Arrays.asList(errorPages)); }}
addErrorPages()实际上是由ErrorMvcAutoConfiguration的ErrorPageCustomizer的registerErrorPages()调用的。
static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { private final ServerProperties properties; private final DispatcherServletPath dispatcherServletPath; protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) { this.properties = properties; this.dispatcherServletPath = dispatcherServletPath; } @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage( this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())); errorPageRegistry.addErrorPages(errorPage); } @Override public int getOrder() { return 0; }}
在registerErrorPages()中,先从ServerProperties中获取ErrorProperties,又从ErrorProperties中获取path,而path默认为/error。可通过在配置文件中设置server.error.path来进行配置。
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)public class ServerProperties { public class ErrorProperties { // ... @Value("${error.path:/error}") private String path = "/error"; // ... }}
然后调用DispatcherServletPath的getRelativePath()来构建错误页面的完整路径。getRelativePath()调用getPrefix()用于获取路径前缀,getPrefix()又调用getPath()来获取路径。
@FunctionalInterfacepublic interface DispatcherServletPath {default String getRelativePath(String path) {String prefix = getPrefix();if (!path.startsWith("/")) {path = "/" + path;}return prefix + path;} default String getPrefix() {String result = getPath();int index = result.indexOf("*");if (index != -1) {result = result.substring(0, index);}if (result.endsWith("/")) {result = result.substring(0, result.length() - 1);}return result;}}
DispatcherServletPath实际上是由DispatcherServletRegistrationBean进行处理的。而DispatcherServletRegistrationBean的path字段值由构造函数给出。
public class DispatcherServletRegistrationBean extends ServletRegistrationBeanimplements DispatcherServletPath {private final String path;public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {super(servlet);Assert.notNull(path, "Path must not be null");this.path = path;super.addUrlMappings(getServletUrlMapping());}}
而DispatcherServletRegistrationBean实际上是在DispatcherServletAutoConfiguration中的DispatcherServletRegistrationConfiguration创建的。
@Configuration(proxyBeanMethods = false)@Conditional(DispatcherServletRegistrationCondition.class)@ConditionalOnClass(ServletRegistration.class)@EnableConfigurationProperties(WebMvcProperties.class)@Import(DispatcherServletConfiguration.class)protected static class DispatcherServletRegistrationConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; }}
因此创建DispatcherServletRegistrationBean时,将从WebMvcProperties中获取path。默认值为/,可在配置文件中设置spring.mvc.servlet.path来配置。也就是说getPrefix()返回值就是/。
@ConfigurationProperties(prefix = "spring.mvc")public class WebMvcProperties { // ... private final Servlet servlet = new Servlet(); // ...public static class Servlet { // ... private String path = "/"; } // ...}
最终在ErrorMvcAutoConfiguration的ErrorPageCustomizer的registerErrorPages()中注册的错误页面路径为将由两个部分构成,前缀为spring.mvc.servlet.path,而后缀为server.error.path。前者默认值为/,后者默认值为/error。因此,经过处理后最终返回的ErrorPath的路径为/error。
SpringBoot会通过上述的过程在StandardContext中添加一个路径为/error的ErrorPath。当服务器发送错误时,则从StandardContext中获取到路径为/error的ErrorPath,然后将请求转发到/error中,然后由SpringBoot自动配置的默认Controller进行处理,返回一个Whitelabel Error Page页面。
(三)Whitelabel Error Page视图
SpringBoot自动配置ErrorMvcAutoConfiguration。并在@ConditionalOnMissingBean的条件下创建DefaultErrorAttributes、DefaultErrorViewResolver、BasicErrorController和View(名称name为error)的Bean组件。
@Configuration(proxyBeanMethods = false)@ConditionalOnWebApplication(type = Type.SERVLET)@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })@AutoConfigureBefore(WebMvcAutoConfiguration.class)@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })public class ErrorMvcAutoConfiguration { @Bean@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)public DefaultErrorAttributes errorAttributes() {return new DefaultErrorAttributes();} @Bean@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,ObjectProvider errorViewResolvers) {return new BasicErrorController(errorAttributes, this.serverProperties.getError(),errorViewResolvers.orderedStream().collect(Collectors.toList()));}@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); }@Configuration(proxyBeanMethods = false)@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)@Conditional(ErrorTemplateMissingCondition.class)protected static class WhitelabelErrorViewConfiguration {private final StaticView defaultErrorView = new StaticView();@Bean(name = "error")@ConditionalOnMissingBean(name = "error")public View defaultErrorView() {return this.defaultErrorView;}}}
BasicErrorController是一个控制器组件,映射值为${server.error.path:${error.path:/error}},与在StandardContext中注册的ErrorPage的路径一致。BasicErrorController提供两个请求映射的处理方法errorHtml()和error()。errorHtml()用于处理浏览器访问时返回的HTML页面。方法内部调用getErrorAttributes()和resolveErrorView()。当无法从resolveErrorView()中获取任何ModelAndView时,将默认返回一个名称为error的ModelAndView。error()用于处理ajax请求时返回的响应体数据。方法内部调用getErrorAttributes()并将返回值作为响应体返回到客户端中。
@Controller@RequestMapping("${server.error.path:${error.path:/error}}")public class BasicErrorController extends AbstractErrorController {@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {HttpStatus status = getStatus(request);Map model = Collections.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));response.setStatus(status.value());ModelAndView modelAndView = resolveErrorView(request, response, status, model);return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);}@RequestMappingpublic ResponseEntity
在BasicErrorController的errorHtml()中返回的是名称为error的ModelAndView,因此Whitelabel Error Page页面就是由于名称为error的View提供的。在ErrorMvcAutoConfiguration已经自动配置一个名称为error的View,具体为ErrorMvcAutoConfiguration.StaticView,它的render()方法输出的就是Whitelabel Error Page页面。
private static class StaticView implements View { private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8); private static final Log logger = LogFactory.getLog(StaticView.class); @Override public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (response.isCommitted()) { String message = getMessage(model); logger.error(message); return; } response.setContentType(TEXT_HTML_UTF8.toString()); StringBuilder builder = new StringBuilder(); Object timestamp = model.get("timestamp"); Object message = model.get("message"); Object trace = model.get("trace"); if (response.getContentType() == null) { response.setContentType(getContentType()); } builder.append("Whitelabel Error Page
").append( "This application has no explicit mapping for /error, so you are seeing this as a fallback.
") .append("").append(timestamp).append("") .append("There was an unexpected error (type=").append(htmlEscape(model.get("error"))) .append(", status=").append(htmlEscape(model.get("status"))).append(")."); if (message != null) { builder.append("").append(htmlEscape(message)).append(""); } if (trace != null) { builder.append("").append(htmlEscape(trace)).append(""); } builder.append(""); response.getWriter().append(builder.toString()); }}
SpringBoot会通过上述的过程在Context中添加一个路径为/error的ErrorPath。当服务器发送错误时,则从Context中获取到路径为/error的ErrorPath,然后将请求转发到/error中,然后由SpringBoot自动配置的BasicErrorController进行处理,返回一个Whitelabel Error Page页面,并且在页面中通常还包含timestamp、error、status、message、trace字段信息。
(四)Whitelabel Error Page字段
在BasicErrorController的errorHtml()和error()中,内部均调用了AbstractErrorController的ErrorAttributes字段的getErrorAttributes()。
public abstract class AbstractErrorController implements ErrorController { private final ErrorAttributes errorAttributes; protected Map getErrorAttributes(HttpServletRequest request, ErrorAttributeOptions options) {WebRequest webRequest = new ServletWebRequest(request);return this.errorAttributes.getErrorAttributes(webRequest, options);}}
在ErrorMvcAutoConfiguration中自动配置了ErrorAttributes的Bean,即DefaultErrorAttributes。在DefaultErrorAttributes中通过getErrorAttributes()来获取所有响应字段。getErrorAttributes()先添加timestamp字段,然后又调用addStatus()、addErrorDetails()、addPath()来添加其他字段。
@Order(Ordered.HIGHEST_PRECEDENCE)public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered { @Overridepublic Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {Map errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));if (Boolean.TRUE.equals(this.includeException)) {options = options.including(Include.EXCEPTION);}if (!options.isIncluded(Include.EXCEPTION)) {errorAttributes.remove("exception");}if (!options.isIncluded(Include.STACK_TRACE)) {errorAttributes.remove("trace");}if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {errorAttributes.put("message", "");}if (!options.isIncluded(Include.BINDING_ERRORS)) {errorAttributes.remove("errors");}return errorAttributes;}@Override@Deprecatedpublic Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {Map errorAttributes = new LinkedHashMap<>();errorAttributes.put("timestamp", new Date());addStatus(errorAttributes, webRequest);addErrorDetails(errorAttributes, webRequest, includeStackTrace);addPath(errorAttributes, webRequest);return errorAttributes;} private void addStatus(Map errorAttributes, RequestAttributes requestAttributes) {Integer status = getAttribute(requestAttributes, RequestDispatcher.ERROR_STATUS_CODE);if (status == null) {errorAttributes.put("status", 999);errorAttributes.put("error", "None");return;}errorAttributes.put("status", status);try {errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());}catch (Exception ex) {// Unable to obtain a reasonerrorAttributes.put("error", "Http Status " + status);}}private void addErrorDetails(Map errorAttributes, WebRequest webRequest,boolean includeStackTrace) {Throwable error = getError(webRequest);if (error != null) {while (error instanceof ServletException && error.getCause() != null) {error = error.getCause();}errorAttributes.put("exception", error.getClass().getName());if (includeStackTrace) {addStackTrace(errorAttributes, error);}}addErrorMessage(errorAttributes, webRequest, error);} private void addPath(Map errorAttributes, RequestAttributes requestAttributes) {String path = getAttribute(requestAttributes, RequestDispatcher.ERROR_REQUEST_URI);if (path != null) {errorAttributes.put("path", path);}}}
因此SpringBoot会通过上述过程,向BasicErrorController注入DefaultErrorAttributes的Bean,然后调用其getErrorAttributes()来获取所有的字段信息,最后通过StaticView的render()将字段信息输出到Whitelablel Error Page页面中,这就是为什么Whitelabel Error Page会出现timestamp、error、status、message、trace字段信息的原因。
(五)底层源码核心流程
底层源码核心流程
- SpringBoot通过ErrorMvcAutoConfiguration的ErrorPageCustomizer的registerErrorPages()向StandardContext中添加一个路径为/error为ErrorPage。
- 当服务器处理请求失败(HttpStatus错误、抛出异常)时,将通过StandardHostValve的custom()将请求转发到路径为/error的ErrorPage中。
- /error请求由BasicErrorController进行处理,通过errorHtml()返回一个StaticView,即Whitelabel Error Page。
向StandardContext添加的ErrorPage路径和BasicErrorController处理的请求路径均是从配置文件server.error.path中读取的。
(六)自定义拓展
- 修改server.error.path来实现自定义的错误转发路径。
server.error.path用于配置请求处理错误时转发的路径,默认值为/error。因此我们可以修改server.error.path的值来自定义错误转发路径,然后再通过自定义的Controller来对错误转发路径进行处理。
- 继承DefaultErrorAttributes并重写getErrorAttributes()来实现自定义异常属性。
在ErrorMvcAutoConfiguration中创建ErrorAttributes的Bean时使用了的@ConditionalOnMissBean注解,因此我们可以自定义一个ErrorAttributes的Bean来覆盖默认的DefaultErrorAttributes。通常的做法是继承DefaultErrorAttributes并重写getErrorAttributes()来实现自定义异常属性。
由于BasicErrorController的errorHtml()和error()内部均会调用ErrorAttributes的getErrorAttributes(),因此BasicErrorController将会调用我们自定义的ErrorAttributes的Bean的getErrorAttributes()来获取错误属性字段。
- 继承DefaultErrorViewResolver并重写resolveErrorView()来实现自定义异常视图。
BasicErrorController会调用ErrorViewResolver的resolveErrorView()来寻找合适的错误视图。DefaultErrorViewResolver默认会从resources目录中查找4xx.html、5xx.html页面。当无法找到合适的错误视图时,将自动返回一个名称为error的视图,此视图由StaticView解析,也就是Whitelabel Error Page。
在ErrorMvcAutoConfiguration中创建ErrorViewResolver的Bean时使用了@ConditionalOnMissBean注解,因此我们可以自定义一个ErrorViewResolver来覆盖默认的DefaultErrorViewResolver。通常的做法是继承DefaultErrorViewResolver并重写resolveErrorView()来实现自定义异常视图。
- 实现ErrorController接口来自定义错误映射处理。不推荐直接继承BasicErrorController。
在ErrorMvcAutoConfiguration中创建ErrorController的Bean时使用了@ConditionalOnMissBean注解,因此我们可以自定义一个ErrorController来覆盖默认的BasicErrorController。通常的做法是实现ErrorController接口来自定义错误映射处理。具体实现时可参考AbstractErrorController和BasicErrorController。
当服务器处理请求失败后,底层会将请求默认转发到/error映射中,因此我们必须提供一个处理/error请求映射的方法来保证对错误的处理。
在前后端分离项目中,前端与后端的交互通常是通过json字符串进行的。当服务器请求处理异常时,我们不能返回一个Whitelabel Error Page的HTML页面,而是返回一个友好的、统一的json字符串。为了实现这个目的,我们必须覆盖BasicErrorController来实现在错误时的自定义数据返回。
// 统一响应类@AllArgsConstructor@Datapublic static class Response { private Integer code; private String message; private T data;}
// 自定义的ErrorController参考BasicErrorController、AbstractErrorController实现@RestController@RequestMapping("${server.error.path:${error.path:/error}}")@RequiredArgsConstructor@Slf4jpublic static class MyErrorController implements ErrorController { private final DefaultErrorAttributes defaultErrorAttributes; @Override public String getErrorPath() { // 忽略 return null; } @GetMapping public Response error(HttpServletRequest httpServletRequest) { // 获取默认的错误信息并打印异常日志 log.warn(String.valueOf(errorAttributes(httpServletRequest))); // 返回统一响应类 return new Response<>(-1, "error", null); } private Map errorAttributes(HttpServletRequest httpServletRequest) { return defaultErrorAttributes.getErrorAttributes( new ServletWebRequest(httpServletRequest), ErrorAttributeOptions.of( ErrorAttributeOptions.Include.EXCEPTION, ErrorAttributeOptions.Include.STACK_TRACE, ErrorAttributeOptions.Include.MESSAGE, ErrorAttributeOptions.Include.BINDING_ERRORS) ); }}
-
焦点速看:深入理解Whitelabel Error Page底层源码
本文对WhitelabelErrorPage进行源码分析,详细说明出现WhitelabelErrorPage页面的核心流程,并给出了自定义拓展的方案。
来源: 焦点速看:深入理解Whitelabel Error Page底层源码
短讯!字符串函数
【脚本项目源码】Python实现鲁迅名言查询系统
短讯!NGINX常见的变量说明
程序员也可以很浪漫,精选10个圣诞节特效及源码
天天资讯:使用Cpolar搭建一个图片网站2 (Piwigo网站环境准备及安装)
苹果笔记本是什么系统?苹果笔记本怎么安装win10系统?
关闭笔记本触摸板的方法有哪些?关闭笔记本触摸板的四种方法
柳叶刀是哪个国家的杂志?柳叶刀杂志是什么水平?
2023年元旦股市放假几天?2023年元旦股市休市时间表
消防车也要去加油站加油吗?消防车多少钱一台?
win10可以免费升级吗?免费升级win10的条件有哪些?
wlan和wifi哪个网速更快?wlan和wifi的区别是什么?
内存卡读不出来是什么原因?内存卡读不出来怎么修复?
一加8t和一加8pro哪个好?一加8t参数配置
环球微动态丨下载selenium及其适配谷歌浏览器插件chromedriver(含chrome各版本及下载地址)
19.13备库duplicate恢复新主库(二)
iPhone6多少钱?iphone6系统最高可以升级到多少?
锵锵三人行停播原因是什么?锵锵三人行女嘉宾名单大全
沙海吴邪的计划是什么?沙海吴邪的计划成功了吗?
什么边野草花什么口夕阳斜?什么边野草花什么口夕阳斜是什么诗?
九月青豆角产自什么村?九月青豆角怎么冻好吃?
天国的阶梯结局是什么?天国的阶梯演员表
天天热点!什么是Docker容器?(全面了解使用)
环球新资讯:【collection】4.java容器之LinkedList,Stack,CopyOnWriteArrayList
教你用JavaScript实现实时字符计数器
杭州联合银行 x 袋鼠云:打造智能标签体系,助力银行大零售业务转型
天天看点:卡梅隆感染新冠 缺席洛杉矶首映式
想“白嫖”20万的私人飞机 推特被告了
【报资讯】短了5厘米照样强大 迷你SSD硬盘雄起 速度冲向5GB/s
全球聚焦:小米13、iPhone 14 Pro全角度对比:小米正面碾压式完胜 背面有争议
环球快消息!世界杯半决赛现场将播放两首中文歌:你肯定都听过
当前热讯:CSS实现打字机动画效果
环球热消息:行为管理(锐捷交换篇)
环球播报:硬刚小米13!moto X40核心配置官方全面揭晓
环球热头条丨男子帮摔倒大爷报警反被讹引热议:已和解 对方赔偿男子2000元误工费
环球即时:旗舰猛兽!小米万兆路由器明天首销:1799元
天天时讯:太强了!卢伟冰称小米13 Pro应该叫“小米13 Pro Ultra”
【速看料】《三体》动画爆火!两部真人版剧集明年开播:网飞、腾讯对标
环球热推荐:又一合资倒下?家喻户晓的斯柯达或将退出中国市场
环球聚焦:历史首次!英国女孩通过基因编辑治好白血病对抗癌症:生物工程壮举
全球快资讯:框架第三课---作业讲解(数据增删改查),django请求生命周期流程图,django路由层,反向解析
天天热点!第一百一十二篇: JS数组Array(一)数组基本用法
焦点快播:SpringBoot+VUE
Dockerfile指令与Docker-compose容器编排-搭建docker私有仓库
【环球报资讯】伴娘穿露肩礼服肌肉健硕画面太美不敢看 网友:真金刚芭比
IGN给RX 7900 XTX显卡打7分:AMD性价比绝对YES
一加显示器X27发布:2K 165Hz屏、65W PD输出
观天下!小米13深度体验:补上最后一块短板!冲击高端真的要成了?
拒绝向SSD认输!机械硬盘每GB单价已暴跌87%:将越来越便宜
环球微动态丨python中的高阶函数
今日讯!雷军宣布小米13系列将很快登陆全球市场:国外用户激动坏了
【世界新要闻】《三体》动画播放量破1.3亿:豆瓣出现N多1星剧评
每日关注!奇葩!男子恋爱转账11万分手能向女方要回不 法院判决
动态焦点:AMD悄悄把RX 7900 XT的功耗提高了:游戏性能实测公布
世界热点评!FreeSWITCH学习笔记:模块
天天快播:服!敬业新郎一边结婚一边拉业务:39元套餐送1200分钟通话、90G流量和宽带
世界要闻:三亚游客乘观光直升机突遇意外迫降海面:发动机断油失效
视点!秒懂:JCTool 的 Mpsc 超高性能无锁队列 (史上最全+10W字长文)
全球要闻:免费可商用!荣耀HONOR Sans字体来了 附下载
0.89元/片:KN95口罩30片26.9元大促
全球热文:全球首款!大上科技墨水屏显示器上架:25.3寸超大屏幕
天天观速讯丨我们为什么使用Docker
天天滚动:win11上同时安装多个版本的python
当前信息:999元 小米Sound Pro太顶了:用户体验后感觉地板都在共振
天天看点:裸奔还是不行 Win10/11系统依然需要安全软件:免费的就行
每日速讯:顺丰突然又崩了!官方回应:系统异常 已恢复
系列最轻巧流畅系统!MIUI 14开发版首批推送来了
每日讯息!第一章作业
【全球报资讯】架构到底是指什么?
实验七-缓冲区溢出
全球看点:分析师称马斯克暴砍80%服务器订单:供应商Intel很受伤
新能源车换电池多少钱?比亚迪汉8万多 特斯拉13万
世界即时看!山东女生送闺蜜化妆品到内蒙被冻炸 当事人:心里很难过
全球首款真Hi-Fi无线耳机!vivo TWS 3 Pro图赏
2022最强电影!《阿凡达2》北京首映礼今晚举办:提前感受科幻巨作
PopClip使用教程图文详解 2022.12亲测有效
焦点快报!Html+CSS小案例项目:CSS开发小米商城电商产品展示效果
通过命令上传到GitHub
当前播报:对于async和await的使用方式、作用效果不怎么理解 ?没关系,初步看这篇就够了。
记录--记一次前端CSS升级
世界观焦点:中国生物最新研究!灭活疫苗对新冠康复者同样具有保护效力
环球热点评!女子住1楼质问物业为啥要交电梯费 网友力挺
OPPO Find N2 Flip亮相:副屏太惊艳
环球新资讯:日本公布2022年度热门汉字:“战”二次当选 理由奇葩
李诞入手小米13和13 Pro:大玩谐音梗
每日快报!【脚本项目源码】Python制作多功能音乐播放器,打造专属你的音乐播放器
每日关注!雷军分享小米13/MIUI 14内置壁纸原图:大批iPhone用户感谢
世界观天下!不用耳机也有立体听感!moto X40新增空间音频功能
电动自行车新国标充电插头曝光:三脚接口无了 不配套不上电
中国汽车工业见证者上新!全新BJ212开售:9.99万起
当前观点:顺丰又崩了!小程序查件、寄件都无法操作:范围极大
Python工具箱系列(十九)
Linux发布6.1稳定版:进一步提升国产LoongArch架构CPU支持
婆婆给刚出生9天宝宝喂米糊引热议 医生称太离谱:回击这是习俗 科学靠边
世界杯决赛用球亮相:取名“梦想”、内藏高科技
续航轻松破1000km 哈弗H6插电混动亮相泰国:老外都叹服!
环球观热点:马斯克反悔:毫米波雷达重新上车
每日看点!建立自己的kindle书库
国产麒麟系统下基于卫星的NTP网络时间服务器方案