深入理解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()方法来调用的。
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()来获取路径。
@FunctionalInterface
public 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 ServletRegistrationBean<DispatcherServlet>
implements 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<MultipartConfigElement> 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<ErrorViewResolver> 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<String, Object> 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);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
}
在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<String, ?> 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("<html><body><h1>Whitelabel Error Page</h1>").append(
"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>").append(timestamp).append("</div>")
.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
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<String, Object> 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 {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> 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
@Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
private void addStatus(Map<String, Object> 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 reason
errorAttributes.put("error", "Http Status " + status);
}
}
private void addErrorDetails(Map<String, Object> 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<String, Object> 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
@Data
public static class Response<T> {
private Integer code;
private String message;
private T data;
}
// 自定义的ErrorController参考BasicErrorController、AbstractErrorController实现
@RestController
@RequestMapping("${server.error.path:${error.path:/error}}")
@RequiredArgsConstructor
@Slf4j
public static class MyErrorController implements ErrorController {
private final DefaultErrorAttributes defaultErrorAttributes;
@Override
public String getErrorPath() {
// 忽略
return null;
}
@GetMapping
public Response<Void> error(HttpServletRequest httpServletRequest) {
// 获取默认的错误信息并打印异常日志
log.warn(String.valueOf(errorAttributes(httpServletRequest)));
// 返回统一响应类
return new Response<>(-1, "error", null);
}
private Map<String, Object> 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底层源码的更多相关文章
- SpringBoot Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.
使用SpringBoot写HelloWorld,当配置好启动类后,再创建新的controller或其它类,启动项目后访问对应的映射名,页面显示: Whitelabel Error Page This ...
- 《Springboot极简教程》问题解决:Springboot启动报错 Whitelabel Error Page: This application has no explicit mapping for(转)
13.2 Spring Boot启动报错:Whitelabel Error Page 13.2 Spring Boot启动报错:Whitelabel Error Page 问题描述 Whitelabe ...
- 关于Springboot启动报错 Whitelabel Error Page: This application has no explicit mapping
Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as ...
- Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Whitelabel Error Page 专题
Spring boot为错误视图提供了如下错误属性:timestamp:错误发生的时间status:HTTP状态码error:错误原因exception:异常的类名message:异常消息(如果这个错 ...
- 深入理解koa中的co源码
阅读目录 一:理解Generator 二:理解js函数柯里化 三:理解Thunk函数 四:理解CO源码 回到顶部 一:理解Generator 在看co源码之前,我们先来理解下Generator函数.G ...
- 从底层源码浅析Mybatis的SqlSessionFactory初始化过程
目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...
- Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap
声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...
- Whitelabel Error Page异常
做了个简单的springboot项目,一开始所有运行正常,之后写了一天的代码,再运行相同的代码,就一直报以下异常: Whitelabel Error Page This application has ...
- BAT资深工程师 由浅入深分析 Tp5&Tp6底层源码 - 分享
BAT资深工程师由浅入深分析Tp5&Tp6底层源码 第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的 ...
随机推荐
- host主机监控规则
1.先在 Prometheus 主程序目录下创建rules目录,然后在该目录下创建 host.yml文件,内容如下: 内容很多,可以根据实际情况进行调整. 规则参考网址:https://awesome ...
- ERP 软件为什么很贵?
ERP软件的贵体现在两个方面,一个是软件系统贵,一个是部署实施贵,究其原因,就是ERP专业性太高.内部业务逻辑忒复杂,而面向形形色色的实体企业时个性化需求又加剧了它整体的复杂性,实施部署客制化无法避免 ...
- HDU1561 The more, The Better(树形背包)
通过这道题对树形背包理解更深一步...... 有几个地方需要注意: 1.本题数据结构为森林,需增加一个超根作为根节点,M+=1(后面解释). 2.本题有拓扑序的限制,通过vector建成的一棵树中,必 ...
- HDU2041 超级楼梯 (线性DP)
fn[i]表示走上第i级台阶的所有走法. 方程:fn[i]=fn[i-1]+fn[i-2]; 1 #include<cstdio> 2 #define MAXN 40 3 using na ...
- 洛谷P2627 [USACO11OPEN]Mowing the Lawn G (单调队列优化DP)
一道单调队列优化DP的入门题. f[i]表示到第i头牛时获得的最大效率. 状态转移方程:f[i]=max(f[j-1]-sum[j])+sum[i] ,i-k<=j<=i.j的意义表示断点 ...
- mac通过docker一键部署MySQL8
目录 mac通过docker一键部署MySQL8 一.前言 二.系统配置 三.安装步骤 Dockerhub查看镜像地址 1.一键安装 1.1.克隆脚本 1.2.安装程序 1.2.1.安装程序详情 1. ...
- linux安装达梦数据库8
PS.本次测试只是为了项目需要,但是在部署和启动程序的时候发生了一系列的报错,由此记录下来为日后作参考 安装达梦数据库 1. 达梦数据库(DM8)简介 达梦数据库管理系统是武汉达梦公司推出的具有完全自 ...
- Autobus 方法记录
原题链接 [COCI2021-2022#4] Autobus 题目描述 在一个国家里有 \(n\) 座城市.这些城市由 \(m\) 条公交线路连接,其中第 \(i\) 条线路从城市 \(a_i\) 出 ...
- 超好用的截图软件Snipaste(包含安装包)、如何设置Snipaste开机自启
文章目录 1.设置开机自动启动 2.基本使用 链接: https://pan.baidu.com/s/1oR2qkOZl5-etPxr7kWip7Q 提取码:8888 1.设置开机自动启动 2.基本使 ...
- 八、指定节点pod运行在固定节点
指定pod运行在固定节点 一.指定固定节点:Pod.spec.nodeName Pod.spec.nodeName 将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策 ...