SpringBoot学习04-[定制SpringMVC]

定制SpringMVC

  • 定制SpringMvc的自动配置
    • 定制springmvc-configurePathMatch配置
    • 定制SpringMVC-拦截器Interceptor
    • 定制SpringMVC-CORS配置
      • 全局cors配置
      • 针对某个方法加跨域解决
  • WebMvcConfigurer原理
  • 定制SpringMVC-JSON
    • JSON开发
      • jackson的使用
      • 定制化json序列化和反序列化
    • JSON国际化
      • 添加国际化资源文件
      • 配置messageResource设置国际化资源文件
        • @Conditional
        • 源码解读
        • 配置国际化资源环境路径让MessageSourceAutoConfiguration 配置类生效
      • 需要去解析请求头中的Accept-Language或者获取url参数中的?local=,来判断语言
        • 源码解读
        • 随意切换本地语言,进行缓存
      • 通过messageResource获取国际化信息
        • 在handler方法参数中加入Local参数,注入MessageSource 对象
        • 使用自定义工具类
  • 统一异常处理
    • SpringBoot统一异常处理
    • 源码解读-ErrorMvcAutoConfiguration配置类
      • BasicErrorController
        • errorHtml:怎么去定制它的返回页面?
          • resolveErrorView源码解读
          • 定制化页面
        • error:看它是怎么返回json数据的,从而要定制自己的json数据
    • SpringBoot定制浏览器请求和ajax请求,返回异常处理
      • 自定义异常处理类(覆盖BasicErrorController)

定制SpringMvc的自动配置

SpringMvc的自动配置类:WebMvcAutoConfiguration
1、在大多数情况下,SpringBoot在自动配置中标记了很多@ConditionalOnMissingBean,我们只需要在自己的配置类中配置对应的bean,就可以覆盖原有的bean

定制springmvc-configurePathMatch配置

配置configurePathMatch的setUseTrailingSlashMatch

setUseTrailingSlashMatch:是否与 URL 匹配,而不考虑是否存在尾部斜杠。如果启用,映射到“users”的方法也与“users”匹配。默认值为 true。

  • 自定义配置
@Configuration
public class CustomWebMvcConfig implements WebMvcConfigurer {

    /**
     *
     * @param configurer
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseTrailingSlashMatch(false);
    }
}
  • 测试
    此时访问:http://localhost:8080/person/14/,如果最后加上/ 则找不到
    在这里插入图片描述
    改为true,则可以找到
    在这里插入图片描述

定制SpringMVC-拦截器Interceptor

自定义一个拦截器用来计算用户请求执行时间
在这里插入图片描述

preHandle:请求执行前,返回值设置为true,不然请求无法走下去
postHandle:请求执行后,执行的方法

  • 自定义拦截器
public class CustormInterceptor implements HandlerInterceptor {
    LocalDateTime beginTime;

    Logger log=LoggerFactory.getLogger(CustormInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //开始时间
        beginTime = LocalDateTime.now();
        log.info("当前请求,{}+用户请求开始时间:{}",request.getRequestURI(),beginTime);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //结束时间
        LocalDateTime endTime = LocalDateTime.now();
        //计算两个时间
        Duration between = Duration.between(beginTime, endTime);
        //获得相差毫秒
        long l = between.toMillis();
        log.info("当前请求,{}+用户请求时间:{}毫秒",request.getRequestURI(),l);
    }
}
  • 在自定义配置中使用我们的自定义的拦截器
@Configuration
public class CustomWebMvcConfig implements WebMvcConfigurer {

    /**
     *
     * @param configurer
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseTrailingSlashMatch(true);
    }

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustormInterceptor()) //添加拦截器
                .addPathPatterns("/**"); //拦截映射规则
    }
}
  • 测试
    可以看到控制台打印出了请求的开始时间和请求执行的耗时时间
    在这里插入图片描述

定制SpringMVC-CORS配置

跨域
在这里插入图片描述

全局cors配置

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") //映射服务器中哪些http接口运行跨域访问
                .allowedOrigins("http://localhost:63343/") //配置哪些来源有权限跨域
                .allowedMethods("GET","POST","DELETE","PUT"); //允许访问的方法

    }
@Configuration
public class CustomWebMvcConfig implements WebMvcConfigurer {

    /**
     *
     * @param configurer
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseTrailingSlashMatch(true);
    }

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustormInterceptor()) //添加拦截器
                .addPathPatterns("/**"); //拦截映射规则
    }

      @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") //映射服务器中哪些http接口运行跨域访问
                .allowedOrigins("http://localhost:63343/") //配置哪些来源有权限跨域
                .allowedMethods("GET","POST","DELETE","PUT"); //允许访问的方法

    }
}

针对某个方法加跨域解决

添加@CrossOrigin注解

 @GetMapping("/{id}")
    @ApiOperation("根据id获取用户")
    @CrossOrigin
    public Result<Person> getPerson(@PathVariable("id") Integer id){
        Person person = personService.getPersoById(id);
        return new Result<>("200","查询成功",person);
    }
  • 测试
    在这里插入图片描述

WebMvcConfigurer原理

实现WebMvcConfigurer接口可以扩展MVC实现,又既保留SpringBoot的自动配置
1、在自动配置类WebMvcAutoConfiguration也有一个实现了WebMvcConfigurer接口的配置类WebMvcAutoConfigurationAdapter
在这里插入图片描述
2、WebMvcAutoConfigurationAdapter也是通过重写了方法来自定义配置,帮助我们进行自动配置,我们只需定制(拦截器、视图控制器、CORS)等在开发中需要额外定制的功能。
3、导入了EnableWebMvcConfiguration

@Import({EnableWebMvcConfiguration.class})

4、EnableWebMvcConfiguration的父类上setConfigurers使用了@Autowired注解

  • 它会去容器中将所有实现了WebMvcConfigurer接口的bean都自动注入进来,添加到configurers变量中
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
  • 添加到delegates委派器中
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
	public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.delegates.addAll(configurers);
		}
	}
  • 底层调用WebMvcConfigurer对应的方法时,就是去拿到之前添加到delegates中的 WebMvcConfigurer
    在这里插入图片描述
@Override
	public void configurePathMatch(PathMatchConfigurer configurer) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.configurePathMatch(configurer);
		}
	}
  • @EnableWebMvc
    当自定义WebMvcConfigurer 添加了@EnableWebMvc就不会使用SpringMVC自动配置类的默认配置了,默认配置就失效了
  • 为什么呢?原理
  • 在@EnableWebMvc中导入了DelegatingWebMvcConfiguration.class
    在这里插入图片描述
  • DelegatingWebMvcConfiguration类又继承了WebMvcConfigurationSupport类
    配置类继承某个类会把继承的这个类也装配进spring容器中所以WebMvcConfigurationSupport会被装配进spring容器中
    在这里插入图片描述
  • 我们在看WebMvcAutoConfiguration配置类(springMVC核心配置类)上有个条件注解@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
    在这里插入图片描述
    @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}):如果容器中没有WebMvcConfigurationSupport. bean,该配置类才生效

复习:@ConditionalOnMissingBean(类.class):没有某个Bean的时候才会注册某个bean到spring容器中

定制SpringMVC-JSON

1、JSON开发
SpringBoot提供了与三个JSON映射库的集成

  • Gson
  • Jackson
  • JSON-B
    Jackson是SpringBoot的默认json库
    2、国际化
    3、统一异常处理

JSON开发

1、jackson的使用
2、根据自己的业务需求进行json的序列号和反序列化

jackson的使用

  • @JsonIgnore
    进行排除json序列化,将它标注在属性上
  • @JsonFormat
    数据库中birthday:date类型
    在这里插入图片描述
    实体类birthday:date类型
    在这里插入图片描述
    如果不加@JsonFormat输出为
    在这里插入图片描述
    @JsonFormat:进行日期格式化,添加在字段上
  @ApiModelProperty("出生日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

测试
在这里插入图片描述

  • @JsonInclude(JsonInclude.Include.NON_NULL)
    字段不为空才包含在json中,为空就不包含
    不设置的话,输出的json串字段为空
    在这里插入图片描述
    设置的话,就不进行输出,不显示
  @ApiModelProperty("出生日期")
    @JsonFormat(pattern = "yyyy-MM-dd",locale = "zh")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Date birthday;
  • @JsonProperty
    对字段属性名设置别名
 @ApiModelProperty("姓名")
    @JsonProperty("u_name")
    private String name;

在这里插入图片描述

定制化json序列化和反序列化

SpringBoot还提供了@JsonComponent来根据自己的业务需求进行json的序列化和反序列化

@JsonComponent
public class CustomeJsonComponent {

    public static class Serializer extends JsonSerializer<Person>{

        @Override
        public void serialize(Person person, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {

        }
    }

    public static class DeSerializer extends JsonDeserializer<Person>{

        @Override
        public Person deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
            return null;
        }
    }
}

SpringBoot还提供了JsonObjectSerializer和JsonObjectDeserializer基类,它们在序列对象时为标准JackJson版本提供了有用的替代方法。

  • 序列化
@JsonComponent
public class CustomeJsonComponent {

    public static class Serializer extends JsonObjectSerializer<Person> {

        @Override
        protected void serializeObject(Person value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeObjectField("id",value.getId());
            jgen.writeObjectField("uname","dgdg");

            //一次查不出完整的数据返回给前端,需要根据需求去做一些个性化调整
            //根据不同的权限给他返回不同的序列化数据
        }
    }

    public static class DeSerializer extends JsonObjectDeserializer<Person> {

        @Override
        protected Person deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, JsonNode tree) throws IOException {
            return null;
        }
    }
}

可以看到输出是由我们自定义的json来进行输出
在这里插入图片描述

  • 反序列化
@JsonComponent
public class CustomeJsonComponent {

    public static class Serializer extends JsonObjectSerializer<Person> {

        @Override
        protected void serializeObject(Person value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeObjectField("id",value.getId());
            jgen.writeObjectField("uname","dgdg");

            //一次查不出完整的数据返回给前端,需要根据需求去做一些个性化调整
            //根据不同的权限给他返回不同的序列化数据
        }
    }

    public static class DeSerializer extends JsonObjectDeserializer<Person> {

        @Override
        protected Person deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, JsonNode tree) throws IOException {
            Person person = new Person();
            person.setAge(tree.findValue("age").asInt());
            return person;
        }
    }
}

JSON国际化

在这里插入图片描述

  • 实现步骤
    1、添加国际化资源文件
    2、配置messageResource设置国际化资源文件
    3、需要去解析请求头中的Accept-Language获取url参数中的?local= 来获取请求语言
    在这里插入图片描述
    3.1、随意切换本地语言进行缓存
    4、通过messageResource获取国际化信息

添加国际化资源文件

在resources文件夹下新建i18n文件夹用了存放国际化资源文件
在这里插入图片描述
分别创建message.properties、message_en_US.properties文件和message_zh_CN.properties文件,名称是固定的(约定大于配置)

  • 添加一个key person-query.success对统一返回result做国际化
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

配置messageResource设置国际化资源文件

  • 理解下面代码的前提是熟悉@Conditional注解
@Conditional
  • 需要注册的bean
@Data
@Component
@Conditional(InjectTeacherCondition.class)
public class Teacher {
    private String name;
    private String job;
}
  • 自定义条件
    必须实现Condition 接口,并且重写matches方法,如果matches方法返回为true,则将teacher注册进spring容器中,返回为false则不注册到spring容器
public class InjectTeacherCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}
源码解读

在springboot中提供了MessageSourceAutoConfiguration配置类,所以我们不需要去配置messageResource

@Configuration(
    proxyBeanMethods = false
)
//如何自己配置了@bean 名字叫messageSource的bean,就会用自定义的
@ConditionalOnMissingBean(
    name = {"messageSource"},
    search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(Integer.MIN_VALUE)
//自定义条件匹配 会传入实现Condition接口的类ResourceBundleCondition,
//ResourceBundleCondition类会重写matches,自定义匹配规则,如果该方法返回为true,就匹配成功
@Conditional({ResourceBundleCondition.class})
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

ResourceBundleCondition类会重写matches,自定义匹配规则,如果该方法返回为true,就匹配成功

public abstract class SpringBootCondition implements Condition {

	private final Log logger = LogFactory.getLog(getClass());

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
		   //getMatchOutcome:具体的匹配规则就在这个里面
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
  • getMatchOutcome
    • 只要在这个方法中将返回的ConditionOutcome .isMatch=true那就匹配成功了。
	protected static class ResourceBundleCondition extends SpringBootCondition {

		private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		    //获取配置文件中spring.message.basename,由于我们没有配置,默认值是:message
			String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
			ConditionOutcome outcome = cache.get(basename);
			if (outcome == null) {
				outcome = getMatchOutcomeForBasename(context, basename);
				cache.put(basename, outcome);
			}
			return outcome;
		}
  • getMatchOutcomeForBasename
		private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
			ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
			for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
			    //根据message获取,该类路径下的所有的properties的资源文件
				for (Resource resource : getResources(context.getClassLoader(), name)) {
					if (resource.exists()) {
					    //new 了一个ConditionOutcome并且将match设置为true
						return ConditionOutcome.match(message.found("bundle").items(resource));
					}
				}
			}
			return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
		}
配置国际化资源环境路径让MessageSourceAutoConfiguration 配置类生效

通过阅读以上源码我们可以知道,要么在配置文件加spring.messages.basename,要么在resources下新建messages文件夹,这个时候MessageSourceAutoConfiguration 才会生效

  • 没有生效的原因
	@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		    //获取配置文件中spring.message.basename,由于我们没有配置,默认值是:message
			String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
			ConditionOutcome outcome = cache.get(basename);
			if (outcome == null) {
				outcome = getMatchOutcomeForBasename(context, basename);
				cache.put(basename, outcome);
			}
			return outcome;
		}

添加配置文件让配置类MessageSourceAutoConfiguration 生效

spring
messages:
basename: i18n.message

  • 配置文件设置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/personaltest
    username: root
    password: 123456
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  messages:
    basename: i18n.message

mybatis:
  mapper-locations: classpath*:mapper/*.xml
  • 测试
    在配置文件加debug: true,启动项目后通过控制台可以看到该配置类已经生效
    在这里插入图片描述
    总结:如果要让配置类MessageSourceAutoConfiguration 生效
  • 必须保证,在类路径下的message文件夹中有国际化的资源文件
  • 或者自己配置spring.messages.basename 告诉它资源文件在哪里
sping:
  messages:
    basename: i18n.message
  • 只要找到了国际化的资源文件那就会设置ConditionOutcome .isMatch=true
  • 当ConditionOutcome .isMatch=true,那么@Conditional({ResourceBundleCondition.class}),就匹配成功
  • 一旦匹配成功,那自动配置类就会生效,就会帮我们配置一个meessageSource
    在这里插入图片描述

需要去解析请求头中的Accept-Language或者获取url参数中的?local=,来判断语言

源码解读
  • 其实WebMvcAutoConfiguration 配置类也帮我们配置了一个解析请求头中Accept-Language的localResolver
        @Bean
        @ConditionalOnMissingBean(
            name = {"localeResolver"}
        )
        public LocaleResolver localeResolver() {
           // 当配置spring.mvc.locale-resolver=fixed
            if (this.webProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.WebProperties.LocaleResolver.FIXED) {
                //就会使用配置文件中的本地化语言:spring.mvc.locale=en_US 就可以设死本地化语言
                return new FixedLocaleResolver(this.webProperties.getLocale());
            } else {
               // 默认就是使用AcceptHeaderLocaleResolver作为本地化解析器
                AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
                //spring.mvc.locale=en_US 作为默认的本地化语言  
                localeResolver.setDefaultLocale(this.webProperties.getLocale());
                return localeResolver;
            }
        }
  • AcceptHeaderLocaleResolver
@Override
	public Locale resolveLocale(HttpServletRequest request) {
		Locale defaultLocale = getDefaultLocale();
		//当Accept-Language为null 才会使用配置文件中设置的local:spring.mvc.locale
		if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
			return defaultLocale;
		}
		Locale requestLocale = request.getLocale();
		List<Locale> supportedLocales = getSupportedLocales();
		if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
			return requestLocale;
		}
		Locale supportedLocale = findSupportedLocale(request, supportedLocales);
		if (supportedLocale != null) {
			return supportedLocale;
		}
		return (defaultLocale != null ? defaultLocale : requestLocale);
	}
随意切换本地语言,进行缓存

在mvc配置类中自定义一个LocaleResolver 来覆盖原有的LocaleResolver ,因为原来的LocaleResolver 只会从Accept-Language获取语言:
先不考虑分布式session的情况,用cookie进行缓存(因为cookie在同一个域是可以共享的)

  • 自定义配置bean
@Configuration
public class CustomWebMvcConfig implements WebMvcConfigurer {
    @Bean
    public LocaleResolver localeResolver() {
        //先不考虑分布式session的情况,用cookie进行缓存(因为cookie在同一个域是可以共享的)
        CookieLocaleResolver cookie=new CookieLocaleResolver();
        cookie.setCookieMaxAge(60*60*24*30); //设置cookie过期时间
        cookie.setCookieName("local"); //设置cookie名称
        return cookie;
    }
    }
  • 将LocaleChangeInterceptor添加到拦截器中
    这个拦截器会默认把local=zh_CN的url进行拦截并存储到cookie中,然后我们自定义的localeResolver,就会解析cookie获取当前语言
@Configuration
public class CustomWebMvcConfig implements WebMvcConfigurer {
  /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustormInterceptor()) //添加拦截器
                .addPathPatterns("/**"); //拦截映射规则

        registry.addInterceptor(new LocaleChangeInterceptor())
                .addPathPatterns("/**");
    }
  • 测试
    后缀必须加?en_US或者zh_CN
    在这里插入图片描述

通过messageResource获取国际化信息

  • 第一种方法:在handler方法参数中加入Local参数,注入MessageSource 对象
messageSource.getMessage
  • 第二种方法:使用自定义工具类
在handler方法参数中加入Local参数,注入MessageSource 对象
  • 注入MessageSource 对象
 @Autowired
    private MessageSource messageSource;
  • 在handler中使用
 String message=messageSource.getMessage("person-query.success",null, LocaleContextHolder.getLocale());
 @GetMapping("/{id}")
    @ApiOperation("根据id获取用户")
    public Result<Person> getPerson(@PathVariable("id") Integer id){
        // LocaleContextHolder:就是一个Local的持有器 springmvc底层会自动将localeResolver中的语言设置进去
        String message=messageSource.getMessage("person-query.success",null, LocaleContextHolder.getLocale());
        Person person = personService.getPersoById(id);
        return new Result<>("200",message,person);
    }
  • 测试
    修改浏览器默认语言为en_US
    在这里插入图片描述
    在这里插入图片描述
使用自定义工具类

其实也是第一种方式

@Component
@Slf4j
public class I18nMessageUtil {
    @Autowired
    private MessageSource messageSource;

    @Autowired
    private HttpServletRequest request;

    /**
     * 根据code获取国际化信息
     * @param code code
     * @return
     */
    public String getLocaleMessage(String code) {
        return  getLocaleMessage(code,null,null);
    }

    /**
     * 根据code获取国际化信息,如果没有则使用默认提示信息
     * @param code code
     * @param defaultMsg 默认提示信息
     * @return
     */
    public String getLocaleMessage(String code, String defaultMsg) {
        return  getLocaleMessage(code,defaultMsg,null);
    }

    /**
     * 根据code获取国际化信息,并且替换占位符
     * @param code
     * @param params
     * @return
     */
    public String getLocaleMessage(String code, String[] params) {
        return  getLocaleMessage(code,null,params);
    }


    /**
     * 根据code获取国际化信息,没有就使用默认值,并且替换占位符
     * @param code code
     * @param defaultMsg 默认提示信息
     * @param params 替换占位符的参数
     * @return
     */
    public String getLocaleMessage(String code, String defaultMsg, Object[] params) {
        String language = request.getParameter("locale");
        Locale locale = Objects.nonNull(language) ? new Locale(language) : LocaleContextHolder.getLocale();
        try {
            return messageSource.getMessage(code, params, locale);
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("本地化异常消息发生异常: {}, {}", code, params);
            return defaultMsg;
        }
    }
}

统一异常处理

@ControllerAdvice是Spring3.2提供的新注解,它是对Controller的增强,可对controller中被@RequestMapping注解的方法加一些逻辑处理:

  • 1、全局异常处理
  • 2、全局数据绑定
  • 3、全局数据预处理

@ExceptionHandler

  • @ExceptionHandler加在Controller中:只处理当前控制器的异常,优先级比全局高
  • @ExceptionHandler,加在ControllerAdvice中:处理全局异常

SpringBoot统一异常处理

SpingBoot 有统一异常处理的自动配置类:ErrorMvcAutoConfiguration

源码解读-ErrorMvcAutoConfiguration配置类

重要组件:

  • DefaultErrorAttributes
  • BasicErrorController
  • DefaultErrorViewResolver:用了解析错误视图页面

BasicErrorController

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

原理图
在这里插入图片描述
如果发生了异常,springmvc就会转发一个到一个/error,再由springmvc中央调度器去匹配,最终匹配到了BasicErrorController

  • 怎么处理的-怎么判断交给那个handler方法呢?
    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE):produces 就是处理请求头中Accept的
    如果@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)能捕捉到Accept并且value中含有text/html 就说明是一个网页。
    在这里插入图片描述
  • 当使用浏览器发送请求时,请求头是Accept:text/html,就会交给errorHtml方法进行处理,返回一个modeAndView
  • 除了text/html的其他请求都会交给error方法进行处理
	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)  //public static final String TEXT_HTML_VALUE = "text/html";
	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);
	}
errorHtml:怎么去定制它的返回页面?
  • getErrorAttributes()
  • 用来获取所需要的异常信息
resolveErrorView源码解读
  • resolveErrorView:解析视图
  • 会调用DefaultErrorViewResolver的resolveErrorView方法来进行解析
	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}
  • resolve:先从模版视图去解析(由于我们没有配置模版视图,所以并不解析出来),没有模版所以调resolveResource,进一步从自定义的包里查找
	private ModelAndView resolve(String viewName, Map<String, Object> model) {
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);
	}

在这里插入图片描述

  • resolveResource:这个方法就是去下面的路径去找:classpath:/static/error/xxx.html
    在这里插入图片描述
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
		for (String location : this.resources.getStaticLocations()) {
			try {
				Resource resource = this.applicationContext.getResource(location);
				resource = resource.createRelative(viewName + ".html");
				if (resource.exists()) {
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			}
			catch (Exception ex) {
			}
		}
		return null;
	}
定制化页面

由以上源码分析可知,springboot会从static/error/ 路径下去找

  • 未配置定制化页面之前
    在这里插入图片描述
  • 配置定制化页面
    在resources/static/error 文件夹下新建400.html页面
    在这里插入图片描述
  • 测试

在这里插入图片描述
总结: 从errorHtml方法可以得出结论:我们需要使用自定义的页面响应错误,只需要再对应的路径上创建对应错误代码的页面就行了,但是如果想记录日志的话就需要定制了。

error:看它是怎么返回json数据的,从而要定制自己的json数据
  • error方法
	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		//就是调用getErrorAttributes获取了异常信息
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}
  • getErrorAttributes如何获取错误信息呢?
    就是获取errorAttributes来获取错误信息
    那errorAttributes怎么来的呢?
    在basicErrorController,被注册的时候,就已经注入进来了
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

SpringBoot定制浏览器请求和ajax请求,返回异常处理

  • 对浏览器请求:扩展日志处理
  • 对ajax请求:修改返回类型 加上日志处理

自定义异常处理类(覆盖BasicErrorController)

在这里插入图片描述

@Controller
@RequestMapping("/error")
public class CustomErrorController extends AbstractErrorController {
    public CustomErrorController(ErrorAttributes errorAttributes, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
    }

    Logger log= LoggerFactory.getLogger(CustomException.class);


    /**
     * 处理浏览器请求的
     * 在这个基础上加上日志处理
     *
     * @param request
     * @param response
     * @return
     */
    @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()));
        response.setStatus(status.value());
        log.error(model.get("trace").toString());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }

    /**
     * 处理ajax
     * 修改返回类型:Result 加上异常日志记录
     *
     * @param request
     * @return
     */
    @RequestMapping
    @ResponseBody
    public Result<String> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new Result<String>("204", "No Content");
        }

        Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions());
        String code = body.get("status").toString();
        String message = body.get("message").toString();
        log.error(message);
        return new Result<String>(code, message);
    }


    /**
     * 异常信息的选项
     * @return
     */
    protected ErrorAttributeOptions getErrorAttributeOptions() {
        //添加需要的异常信息
        return ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE, ErrorAttributeOptions.Include.STACK_TRACE,
                ErrorAttributeOptions.Include.EXCEPTION
        );
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/755811.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kafka面试必备:深度解析Replica副本的作用与机制

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! Hey大家好!我是小米,一个超级喜欢分享技术干货的大哥哥!今天咱们来聊聊阿里巴巴面试题中的一个热门话题:Kafka中的Replica副本作用。这可是个既基础…

供应商关系管理(SRM)中的供应商绩效评估

供应商绩效评估是供应商关系管理&#xff08;SRM&#xff09;的核心组成部分&#xff0c;它涉及到对供应商在合作过程中的表现进行全面的分析和评价。一个有效的供应商绩效评估系统不仅可以帮助企业识别和解决供应链中的潜在问题&#xff0c;还可以促进供应商的持续改进和优化&…

npm创建一个空的vue3项目的方法或者pnpm创建vue3项目

1、前提我们已经安装了npm&#xff0c;或者pnpm 2、我们用npm来创建vue3项目 快速上手 | Vue.js 官网地址 这里我安装是的 node v18.20.3 以下是安装过程 &#xff1a; npm create vuelatest 根据自己的需要进行创建即可。 3、我们用pnpm来创建vite vue3项目 pnpm create …

【算法专题--栈】栈的压入、弹出序列 -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、解题方法 &#x1f4a7;栈模拟法&#x1f4a7;-- 双指针 ⭐ 解题思路 ⭐ 案例图解 四、总结与提炼 五、共勉 一、前言 栈的压入、弹出序列 这道题&#xff0c;可以说是--栈专题--&#xff0c;最经典的一道题&#xff0c;也是在…

LinkedIn被封原因和解封方法

对于初识领英和对领英生态规则不熟悉的人来说&#xff0c;很容易造成领英账号被封号(被限制登录)的情况&#xff0c;那么如何才能避免和解决领英帐号被封号(被限制登录)的难题呢&#xff1f; 领英帐号被封号或被限制登录主要会有两类情况。 首先要搞清楚&#xff0c; Linkedi…

“ONLYOFFICE 8.1版本评测:功能更强大,用户体验更佳”

最新版本的在线编辑器已经发布 ONLYOFFICE在线编辑器的最新版本8.1已经发布&#xff0c;整个套件带来了30多个新功能和432个bug修复。这个强大的文档编辑器支持处理文本文档、电子表格、演示文稿、可填写的表单和PDF&#xff0c;并允许多人在线协作&#xff0c;同时支持AI集成…

IP白名单及其作用解析

在网络安全领域&#xff0c;IP白名单是一项至关重要的策略&#xff0c;它允许特定的IP地址或地址范围访问网络资源&#xff0c;从而确保只有受信任的终端能够连接。下面&#xff0c;我们将深入探讨IP白名单的定义、作用以及实施时的关键考虑因素。 一、IP白名单的定义 IP白名单…

利用python爬取上证指数股吧评论并保存到mongodb数据库

大家好&#xff0c;我是带我去滑雪&#xff01; 东方财富网是中国领先的金融服务网站之一&#xff0c;以提供全面的金融市场数据、资讯和交易工具而闻名。其受欢迎的“股吧”论坛特别适合爬取股票评论&#xff0c;东方财富网的股吧聚集了大量投资者和金融分析师&#xff0c;他们…

GOROOT GOPATH GOPROXY GO111MODULE

GOROOT GOROOT代表Go的安装目录。可执行程序go(或go.exe)和gofmt(或gofmt.exe)位于 GOROOT/bin目录中。 配置GOROOT环境变量&#xff0c;其值为Go的安装目录&#xff1b;然后在环境变量PATH中添加GOROOT/bin路径。 注意&#xff1a;GOROOT变量只是代表了安装目录&#xff0c;不…

DiskGeniusV5.6.0.1565发布!

DiskGenius是一款功能强大的磁盘管理和数据恢复工具&#xff0c;V5.6.0.1565上线。新版本变化比较大&#xff0c;增加新的功能&#xff0c;修正已经问题&#xff0c;值得试一下。提醒大家&#xff0c;磁盘管理软件涉及数据安全&#xff0c;请始终使用最新版本&#xff01; 下面…

Linux开发讲课18--- “>file 2>1“ 和 “2>1 >file“ 的区别

在 Bash 脚本和命令行操作中&#xff0c;输出重定向是一项基本且强大的功能。它允许用户控制命令的输出流&#xff0c;将数据从一个地方转移到另一个地方&#xff0c;实现更加灵活和高效的工作流程。本文旨在记录 Bash 中几种常见的输出重定向方法&#xff0c;包括: -. > fi…

计算机Java项目|基于SpringBoot的新闻稿件管理系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

密码学:用随机函数隐藏指纹

英文中e的出现频率高&#xff0c;加密后&#xff0c;频率最高的那个符号代表e。这是历史上的一次真实案例。这些符号的概率&#xff0c;叫做“指纹”。 把e加密成2个符号&#xff0c;用随机函数选择&#xff0c;例如70%概率下选择符号1&#xff0c;30%选择符号2。解密时&#…

启智畅想:AI集装箱箱号识别系统,解决方案提供商

AI集装箱箱号识别系统 当前,智能卡口管理行业正处于快速发展的阶段。随着物联网、大数据、人工智能等技术的不断进步,智能卡口管理系统已经能够实现对集装箱运输的全程跟踪、监控和管理,大大提高了管理效率和安全性。然而,市场上现有的智能卡口管理系统仍然存在一些痛点问题,如…

Spring容器的启动过程及留给开发者的可拓展点

一、Spring容器启动经过了哪些过程&#xff1f; 1、首先需要加载读取到应用环境中的配置&#xff0c;比如要加载的bean的class的包路径等信息。 【读取配置】 2、再就需要找到哪些类是需要spring进行类实例创建并管理的&#xff0c;扫描到具体的Class及Class元信息上的一些注…

【漏洞复现】电信网关配置管理系统——命令执行

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 电信网关配置管理系统是一个用于管理和配置电信网关设备的软件系…

为什么Modbus链接/从机不通?From 摩尔信使MThings

为了回应用户平日里关于摩尔信使&#xff08;MThings&#xff09;使用过程中最常见的问题&#xff0c;包括“网络链接连不上”、“为什么不能增加串口”和“为什么从机不通”&#xff0c;我们在此统一介绍解决方法。 1、具备哪些通信能力 支持串口和网络两种通信方式。 需要…

开箱即用的fastposter海报生成器

什么是 fastposter ? fastposter 海报生成器是一款快速开发海报的工具。只需上传一张背景图&#xff0c;在对应的位置放上组件&#xff08;文字、图片、二维码、头像&#xff09;即可生成海报。 点击代码直接生成各种语言 SDK 的调用代码&#xff0c;方便快速开发。 软件特性&…

NSSCTF-Web题目18(反序列化)

目录 [NISACTF 2022]babyserialize 1、题目 2、知识点 3、思路 [SWPUCTF 2022 新生赛]ez_ez_unserialize 4、题目 5、知识点 6、思路 [NISACTF 2022]babyserialize 1、题目 2、知识点 反序列化、绕过过滤、命令执行 3、思路 <?php include "waf.php";…

正版软件 | R-Studio Corporate:企业级数据恢复的终极解决方案

数据是企业的生命线&#xff0c;而数据丢失可能随时威胁到企业的正常运营。R-Studio Corporate 是一款专为企业环境设计的多功能数据恢复软件&#xff0c;确保您在面临数据危机时&#xff0c;能够迅速、高效地恢复宝贵数据。 跨平台操作&#xff0c;灵活恢复 R-Studio Corporat…