前言

在 SpringBoot 项目中,默认情况下,使用浏览器访问一个不存在的地址会返回如下错误页面:

而当客户端未非浏览器时,错误信息则会以 json 数据返回,如下:

会出现如上效果的原因是 SpringBoot 针对错误消息做了自动配置,对应自动配置类为 org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration 。

自定义错误页

查看错误自动配置类会发现在该类中注册了如下组件:

ErrorPageCustomizer

@Bean
public ErrorPageCustomizer errorPageCustomizer() {
    return new ErrorPageCustomizer(this.serverProperties);
}

查看该组件类:

     private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {

         private final ServerProperties properties;

         protected ErrorPageCustomizer(ServerProperties properties) {
             this.properties = properties;
         }

         @Override
         public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
             ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
                     + this.properties.getError().getPath());
             errorPageRegistry.addErrorPages(errorPage);
         }

         @Override
         public int getOrder() {
             ;
         }

     }

org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration.ErrorPageCustomizer

在第 10 行的 registerErrorPages 方法中,注册了一个错误页,错误页路径为 this.properties.getError().getPath() ,该值为

@Value("${error.path:/error}")
private String path = "/error";

即,一旦出现了 4xx 或 5xx 错误,该组件就会生效,可用其定制系统发生错误时的转发路径,默认情况下当前请求会转发到  /error 路径。

BasicErrorController

@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
            this.errorViewResolvers);
}

查看该组件类:

 package org.springframework.boot.autoconfigure.web;

 import java.util.Collections;
 import java.util.List;
 import java.util.Map;

 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;

 import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
 import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
 import org.springframework.util.Assert;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.servlet.ModelAndView;

 @Controller
 @RequestMapping("${server.error.path:${error.path:/error}}")
 public class BasicErrorController extends AbstractErrorController {

     private final ErrorProperties errorProperties;

     public BasicErrorController(ErrorAttributes errorAttributes,
             ErrorProperties errorProperties) {
         this(errorAttributes, errorProperties,
                 Collections.<ErrorViewResolver>emptyList());
     }

     public BasicErrorController(ErrorAttributes errorAttributes,
             ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
         super(errorAttributes, errorViewResolvers);
         Assert.notNull(errorProperties, "ErrorProperties must not be null");
         this.errorProperties = errorProperties;
     }

     @Override
     public String getErrorPath() {
         return this.errorProperties.getPath();
     }

     @RequestMapping(produces = "text/html")
     public ModelAndView errorHtml(HttpServletRequest request,
             HttpServletResponse response) {
         HttpStatus status = getStatus(request);
         Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                 request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
         response.setStatus(status.value());
         ModelAndView modelAndView = resolveErrorView(request, response, status, model);
         return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
     }

     @RequestMapping
     @ResponseBody
     public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
         Map<String, Object> body = getErrorAttributes(request,
                 isIncludeStackTrace(request, MediaType.ALL));
         HttpStatus status = getStatus(request);
         return new ResponseEntity<Map<String, Object>>(body, status);
     }

     protected boolean isIncludeStackTrace(HttpServletRequest request,
             MediaType produces) {
         IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
         if (include == IncludeStacktrace.ALWAYS) {
             return true;
         }
         if (include == IncludeStacktrace.ON_TRACE_PARAM) {
             return getTraceParameter(request);
         }
         return false;
     }

     protected ErrorProperties getErrorProperties() {
         return this.errorProperties;
     }

 }

org.springframework.boot.autoconfigure.web.BasicErrorController

可以看到该组件实际上是一个控制器,用来处理路径为配置中定义的 "${server.error.path:${error.path:/error}}" 请求,如果 server.error 和 error.path 都没有配置,则默认处理路径为 /error 的请求。

控制器中有两个响应方法,分别为第 46 行的 errorHtml 方法和第 58 行的 error 方法,它们都是用来处理路径为 /error 的请求,但 errorHtml 方法返回的错误消息是一个 html 页面,而 error 方法是返回的错误消息是一个 json 数据。通过 @RequestMapping 注解中的 produces 属性来区分客户端需要的错误消息类型,即根据客户端的 accept 请求头区分。具体以哪个页面作为错误页则可看到在第 52 行的 resolveErrorView 方法:

 protected ModelAndView resolveErrorView(HttpServletRequest request,
         HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
     for (ErrorViewResolver resolver : this.errorViewResolvers) {
         ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
         if (modelAndView != null) {
             return modelAndView;
         }
     }
     return null;
 }

org.springframework.boot.autoconfigure.web.AbstractErrorController#resolveErrorView

可以看到,该方法时遍历容器中所有的错误视图解析器,如果解析器解析当前请求返回的 modelAndView 不为空,则以该 modelAndView 作为错误页的响应。即:以哪个页面作为错误页是由错误视图解析器的 resolveErrorView 方法的返回值决定。

DefaultErrorViewResolver

@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
    return new DefaultErrorViewResolver(this.applicationContext,
            this.resourceProperties);
}

这是默认配置的错误视图解析器,查看它的 resolveErrorView 方法:

 @Override
 public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
         Map<String, Object> model) {
     // 传入字符串形式的状态码
     ModelAndView modelAndView = resolve(String.valueOf(status), model);
     if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
         modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
     }
     return modelAndView;
 }

 private ModelAndView resolve(String viewName, Map<String, Object> model) {
     String errorViewName = "error/" + viewName;  // 如:error/404
     TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
             .getProvider(errorViewName, this.applicationContext);
     if (provider != null) { // 如果模板引擎解析器可解析则返回模板视图
         return new ModelAndView(errorViewName, model);
     }
     // 模板引擎不可解析时
     return resolveResource(errorViewName, model);
 }

 private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
     for (String location : this.resourceProperties.getStaticLocations()) { // 遍历静态资源文件夹
         try {
             Resource resource = this.applicationContext.getResource(location); // 获取静态资源
             resource = resource.createRelative(viewName + ".html");  // 如:error/404.html
             if (resource.exists()) { // 判断对应资源是否存在
                 return new ModelAndView(new HtmlResourceView(resource), model);  // 如果存在则返回对应 html 视图
             }
         }
         catch (Exception ex) {
         }
     }
     return null;
 }

org.springframework.boot.autoconfigure.web.DefaultErrorViewResolver#resolveErrorView

通过上述代码可以看到,当请求出现错误时,错误视图解析器会在模板路径及静态文件夹路径下寻找以该错误对应状态码命名的 html 页面作为错误响应视图。比如错误代码为 404,那么默认情况下将会寻找在 templates 和 static 等静态资源文件夹下的 error/404.html 页面作为响应页。我们还可以通过使用 4xx.html 和 5xx.html 作为模板页或静态页分别来匹配以 4 开头和 5 开头的错误让其作为该错误的响应页。从 org.springframework.boot.autoconfigure.web.BasicErrorController#errorHtml 方法的返回值可以看到,如果在模板文件夹和静态文件夹下都没有找到对应的错误页,那么将会返回 new ModelAndView("error", model) 对象,而这个 error 视图在错误自动配置类中中已经配置好了:

 @Configuration
 @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
 @Conditional(ErrorTemplateMissingCondition.class)
 protected static class WhitelabelErrorViewConfiguration {

     private final SpelView defaultErrorView = new SpelView(
             "<html><body><h1>Whitelabel Error Page</h1>"
                     + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
                     + "<div id='created'>${timestamp}</div>"
                     + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
                     + "<div>${message}</div></body></html>");

     @Bean(name = "error")
     @ConditionalOnMissingBean(name = "error")
     public View defaultErrorView() {
         return this.defaultErrorView;
     }

     @Bean
     @ConditionalOnMissingBean(BeanNameViewResolver.class)
     public BeanNameViewResolver beanNameViewResolver() {
         BeanNameViewResolver resolver = new BeanNameViewResolver();
         resolver.setOrder(Ordered.LOWEST_PRECEDENCE - );
         return resolver;
     }
 }

org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration.WhitelabelErrorViewConfiguration

DefaultErrorAttributes

@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes();
}

如上我们只说明了错误页的显示规则,那错误页的消息又是从何而来呢?回头看到 org.springframework.boot.autoconfigure.web.BasicErrorController#errorHtml 方法:

 @RequestMapping(produces = "text/html")
 public ModelAndView errorHtml(HttpServletRequest request,
         HttpServletResponse response) {
     HttpStatus status = getStatus(request);
     Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
             request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
     response.setStatus(status.value());
     ModelAndView modelAndView = resolveErrorView(request, response, status, model);
     return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
 }

org.springframework.boot.autoconfigure.web.BasicErrorController#errorHtml

可以看到返回的 model 的数据为 getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)) 方法的返回值,查看该方法:

 protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
         boolean includeStackTrace) {
     RequestAttributes requestAttributes = new ServletRequestAttributes(request);
     return this.errorAttributes.getErrorAttributes(requestAttributes,
             includeStackTrace);
 }

org.springframework.boot.autoconfigure.web.AbstractErrorController#getErrorAttributes

继续查看 this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace) 方法:

 @Override
 public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
         boolean includeStackTrace) {
     Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
     errorAttributes.put("timestamp", new Date());
     addStatus(errorAttributes, requestAttributes);
     addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
     addPath(errorAttributes, requestAttributes);
     return errorAttributes;
 }

 private void addStatus(Map<String, Object> errorAttributes,
         RequestAttributes requestAttributes) {
     Integer status = getAttribute(requestAttributes,
             "javax.servlet.error.status_code");
     if (status == null) {
         errorAttributes.put();
         errorAttributes.put("error", "None");
         return;
     }
     errorAttributes.put("status", status);
     try {
         errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
     }
     catch (Exception ex) {
         errorAttributes.put("error", "Http Status " + status);
     }
 }

 private void addErrorDetails(Map<String, Object> errorAttributes,
         RequestAttributes requestAttributes, boolean includeStackTrace) {
     Throwable error = getError(requestAttributes);
     if (error != null) {
         while (error instanceof ServletException && error.getCause() != null) {
             error = ((ServletException) error).getCause();
         }
         errorAttributes.put("exception", error.getClass().getName());
         addErrorMessage(errorAttributes, error);
         if (includeStackTrace) {
             addStackTrace(errorAttributes, error);
         }
     }
     Object message = getAttribute(requestAttributes, "javax.servlet.error.message");
     if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
             && !(error instanceof BindingResult)) {
         errorAttributes.put("message",
                 StringUtils.isEmpty(message) ? "No message available" : message);
     }
 }

 private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
     BindingResult result = extractBindingResult(error);
     if (result == null) {
         errorAttributes.put("message", error.getMessage());
         return;
     }
     ) {
         errorAttributes.put("errors", result.getAllErrors());
         errorAttributes.put("message",
                 "Validation failed for object='" + result.getObjectName()
                         + "'. Error count: " + result.getErrorCount());
     }
     else {
         errorAttributes.put("message", "No errors");
     }
 }

 private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
     StringWriter stackTrace = new StringWriter();
     error.printStackTrace(new PrintWriter(stackTrace));
     stackTrace.flush();
     errorAttributes.put("trace", stackTrace.toString());
 }

 private void addPath(Map<String, Object> errorAttributes,
         RequestAttributes requestAttributes) {
     String path = getAttribute(requestAttributes, "javax.servlet.error.request_uri");
     if (path != null) {
         errorAttributes.put("path", path);
     }

org.springframework.boot.autoconfigure.web.DefaultErrorAttributes#getErrorAttributes

通过上述代码我们可以知道在错误页中我们可以使用如下错误信息:

timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常信息
errors:JSR303 数据校验错误信息

自定义错误信息

上述已经描述了我们如何使用自定义的错误页,但是使用的错误信息还依旧是 SpringBoot 默认配置的,如果我们想要自己定制错误信息,则可通过如下方式。

方便下面测试先编写如下异常类及控制器:

package com.springboot.webdev2.ex;

public class MyException extends RuntimeException {
    public MyException() {
        super("运行期间出异常了");
    }
}

com.springboot.webdev2.ex.MyException

package com.springboot.webdev2.controller;

import com.springboot.webdev2.ex.MyException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {

    @RequestMapping("test")
    public void test1(){
        throw new MyException();
    }
}

com.springboot.webdev2.controller.TestController

方式一:自定义异常处理器

 package com.springboot.webdev2.component;

 import com.springboot.webdev2.ex.MyException;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseBody;

 import java.util.HashMap;
 import java.util.Map;

 @ControllerAdvice
 public class MyExceptionHandler {

     @ResponseBody
     @ExceptionHandler(MyException.class)
     public Map<String,Object> handleException(Exception e){
         Map<String, Object> map = new HashMap<>();
         map.put("code", "myCode");
         map.put("msg", "自定义的异常");
         return map;
     }
 }

com.springboot.webdev2.component.MyExceptionHandler

该方式是 SpringMVC 提供的异常处理方式,缺点:使用该方式失去了 SpringBoot 本身的根据客户端的不同自适应响应数据类型的功能。

方式二:转发到错误处理路径

我们已经知道默认情况下出现异常 SpringBoot 会将请求转发到 /error ,那么如果我们通过异常处理器手动转发到该路径,并可手动将我们需要的错误信息放入请求域,我们就可以解决方式一的缺点并且可以在错误页使用我们自己的错误信息了。

package com.springboot.webdev2.component;

import com.springboot.webdev2.ex.MyException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(MyException.class)
    public String handleException(Exception e, HttpServletRequest request){
        // SpringBoot 默认使用的状态码就是请求域中的 javax.servlet.error.status_code
        request.setAttribute();
        Map<String, Object> map = new HashMap<>();
        map.put("code", "myCode");
        map.put("msg", "自定义的异常");
        request.setAttribute("ext", map);
        return "forward:/error";
    }
}

com.springboot.webdev2.component.MyExceptionHandler

<!DOCTYPE html>
<html lang="cn">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>[[${status}]]</h1>
    <h2>[[${message}]]</h2>
    <!--请求域中取错误信息-->
    <h2>[[${ext.msg}]]</h2>
</body>
</html>

templates/error/4xx.html

可以发现,该方式依旧有一个缺点:放入请求域中的数据未被序列化,所以只可在转发到的模板页中取到,而在客户端是非浏览器时是拿不到自定义的错误信息的。

方式三:自定义错误处理控制器

出现异常时 SpringBoot 会将请求转发到 /error ,而处理该请求的控制器为 BaseErrorController  ,查看该控制器注册信息我们也可以知道,当我们自己定义一个 org.springframework.boot.autoconfigure.web.ErrorController 组件注册到容器中时,那么默认的 BasicErrorController 就不生效了,所以我们可以在自定义的错误处理控制器中根据我们的需要取到我们合适的信息返回。该方式比较复杂,明显不合适,了解即可,略过。

方式四:自定义ErrorAttributes

通过查看 org.springframework.boot.autoconfigure.web.BasicErrorController 我们已经知道,不管是响应 html 还是 json 错误信息,它们的错误信息都是通过 this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace) 方法取到,而 this.errorAttributes 对应的组件实际上在错误自动配置类中已经注册,即 DefaultErrorAttributes ,所以我们可以自定义一个的 org.springframework.boot.autoconfigure.web.ErrorAttributes 组件注册到容器中,重写它的 getErrorAttributes 方法,通过手动取得自定义的错误信息返回即可。

package com.springboot.webdev2.component;

import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;

import java.util.Map;

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
        errorAttributes.put("ext", requestAttributes.getAttribute("ext", RequestAttributes.SCOPE_REQUEST));
        return errorAttributes;
    }
}

com.springboot.webdev2.component.MyErrorAttributes

package com.springboot.webdev2.component;

import com.springboot.webdev2.ex.MyException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(MyException.class)
    public String handleException(Exception e, HttpServletRequest request){
        // SpringBoot 默认使用的状态码就是请求域中的 javax.servlet.error.status_code
        request.setAttribute("javax.servlet.error.status_code", 400);
        Map<String, Object> map = new HashMap<>();
        map.put("code", "myCode");
        map.put("msg", "自定义的异常");
        request.setAttribute("ext", map);
        return "forward:/error";
    }
}

com.springboot.webdev2.component.MyExceptionHandler

<!DOCTYPE html>
<html lang="cn">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>[[${status}]]</h1>
    <h2>[[${message}]]</h2>
    <!--请求域中取错误信息-->
    <h2>[[${ext.msg}]]</h2>
</body>
</html>

templates/error/4xx.html

java框架之SpringBoot(7)-异常处理的更多相关文章

  1. java框架之SpringBoot(1)-入门

    简介 Spring Boot 用来简化 Spring 应用开发,约定大于配置,去繁从简,just run 就能创建一个独立的.产品级别的应用. 背景: J2EE 笨重的开发.繁多的配置.低下的开发效率 ...

  2. java框架之SpringBoot(3)-日志

    市面上的日志框架 日志抽象层 日志实现 JCL(Jakarta Commons Logging).SLF4J(Simple Logging Facade For Java).JBoss-Logging ...

  3. java框架之SpringBoot(4)-资源映射&thymeleaf

    资源映射 静态资源映射 查看 SpringMVC 的自动配置类,里面有一个配置静态资源映射的方法: @Override public void addResourceHandlers(Resource ...

  4. java框架之SpringBoot(5)-SpringMVC的自动配置

    本篇文章内容详细可参考官方文档第 29 节. SpringMVC介绍 SpringBoot 非常适合 Web 应用程序开发.可以使用嵌入式 Tomcat,Jetty,Undertow 或 Netty ...

  5. java框架之SpringBoot(14)-任务

    使用 maven 创建 SpringBoot 项目,引入 Web 场景启动器. 异步任务 1.编写异步服务类,注册到 IoC 容器: package zze.springboot.task.servi ...

  6. java框架之SpringBoot(15)-安全及整合SpringSecurity

    SpringSecurity介绍 Spring Security 是针对 Spring 项目的安全框架,也是 Spring Boot 底层安全模块默认的技术选型.它可以实现强大的 Web 安全控制.对 ...

  7. java框架之SpringBoot(16)-分布式及整合Dubbo

    前言 分布式应用 在分布式系统中,国内常用 Zookeeper + Dubbo 组合,而 SpringBoot 推荐使用 Spring 提供的分布式一站式解决方案 Spring + SpringBoo ...

  8. 【java框架】SpringBoot(5)--SpringBoot整合分布式Dubbo+Zookeeper

    1.理论概述 1.1.分布式 分布式系统是若干独立计算机的集合,这些计算机对于用户来讲就像单个系统. 由多个系统集成成一个整体,提供多个功能,组合成一个板块,用户在使用上看起来是一个服务.(比如淘宝网 ...

  9. java框架之SpringBoot(2)-配置

    规范 SpringBoot 使用一个全局的配置文件,配置文件名固定为 application.properties 或 application.yml .比如我们要配置程序启动使用的端口号,如下: s ...

随机推荐

  1. Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk

    查下是不是有在环境变量中定义NDK_PROJECT_PATH 这个变量,如果有,删除试试

  2. Android recovery支持adb shell

    Android recovery支持adb shell 近期开发过程注意到recovery不支持adb shell.为了便于调试方便,决定添加此功能. 刚開始我们採用的是user版本号系统,进入rec ...

  3. 【原创 深度学习与TensorFlow 动手实践系列 - 3】第三课:卷积神经网络 - 基础篇

    [原创 深度学习与TensorFlow 动手实践系列 - 3]第三课:卷积神经网络 - 基础篇 提纲: 1. 链式反向梯度传到 2. 卷积神经网络 - 卷积层 3. 卷积神经网络 - 功能层 4. 实 ...

  4. .net core 2.0 虚拟目录下载 Android Apk 等文件

    当Android 文件 Apk 放在Asp.net core wwwroot 虚拟目录下面.访问是 404,设置Content-Type类型 app.UseStaticFiles(); //设置实际目 ...

  5. CAS单点登陆,URL多出个参数jsessionid导致登陆失败问题

    目录: 1.定位问题 2.问题产生的原因 3.解决问题 一 定位问题 首先,如下图所示:输入到地址栏的地址被302重定向到单点登录地址,地址由Response Headers中的参数Location所 ...

  6. R语言 启动报错 *** glibc detected *** /usr/lib64/R/bin/exec/R: free(): invalid next size (fast): 0x000000000263a420 *** 错误 解决方案

    *** glibc detected *** /usr/lib64/R/bin/exec/R: free(): invalid next size (fast): 0x000000000263a420 ...

  7. NPS - 数字化营销 - 净推荐值

    在获客成本高涨的时代,拥有一批超级用户,让企业更有本钱专注在提升产品及体验,创造更多的超级用户,形成良性循环.超级用户究竟要如何创造?超级用户可以定义成“忠诚用户当中最忠诚的一群人”,因此创造超级用户 ...

  8. 第四百零五节,centos7下搭建sentry错误日志服务器,接收python以及Django错误,

    第四百零五节,centos7下搭建sentry错误日志服务器,接收python以及Django错误, 注意:版本,不然会报错 Docker >=1.11Compose >1.6.0 通过d ...

  9. laravel5.8笔记七:语言包

    语言包控制config/app.php 'locale' => 'en', 语言包位置:resources/lang/cn/ 建立resources/lang/cn/common.php < ...

  10. Mybatis常考面试题汇总(附答案)

    1.#{}和${}的区别是什么? #{}和${}的区别是什么? 在Mybatis中,有两种占位符 #{}解析传递进来的参数数据 ${}对传递进来的参数原样拼接在SQL中 #{}是预编译处理,${}是字 ...