主要介绍springboot如何进行web开发的,因为springboot项目是达成jar包运行的,所以他不支持jsp技术,对于这种需要模版引擎技术的支持。
关于springboot对web的功能配置,都是在WebMvcAutoConfiguration
中,下面的介绍这些配置。
1. springboot对静态资源的映射规则
sb对静态资源的配置在:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//对webjars资源的获取,去classpath:/META-INF/resources/webjars/
//去每个jar包的类路径下去寻找。
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(
registry.addResourceHandler("/webjars/**")
.addResourceLocations(
"classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
//获取静态资源文件夹映射,staticPathPattern是/**
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
//配置欢迎页映射路径
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ResourceProperties resourceProperties) {
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
//顺着代码一路走过去,映射的地址是/**,对应的资源则是静态资源存放的文件下的index.html文件。
}
//静态文件存放的位置。
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
private static final String[] RESOURCE_LOCATIONS;
1. webjars
通过上面的配置类,配置了一条映射规则,所有的webjars/**映射,都是去classpath:META-INF/resources/webjars找资源。
webjars是以jar包的方式引入资源,比如我们使用jquery通过webjars引入到项目中,如果前台想要获取这个资源,则访问的路径则是通过 http://xxx/*/webjars/jquery.js
.
引入webjars资源
<!‐‐引入jquery‐webjar‐‐>在访问的时候只需要写webjars下面资源的名称即可
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
我们要向查看我们引入的js资源,按照spring的配置,实际访问位置是META-INF/resources/webjars找资源。所以自动定位到了webjars目录下。
我们启动服务器,访问路径则是:
http://127.0.0.1:8080/webjars/jquery/3.3.1/jquery.js
2. 静态资源
配置了一个映射/**规则,是访问当前项目的任何资源,都去静态资源文件夹下去找资源
"classpath:/META‐INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径
localhost:8080/abc === 去静态资源文件夹里面找abc
3. 欢迎页
静态资源文件夹下的index.html页面,都被/**映射
2. 模版引擎
模版引擎技术包括jsp,Velocity,freemarker,thymeleaf技术,这些技术的特点都是如上图,将静态页面中的引擎语法和数据相整合,然后重新生成一个带数据的新页面。本质就是将表达式用数据替换,将替换好的页面输出。
1. 引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐thymeleaf</artifactId>
2.1.6
</dependency>
切换thymeleaf版本
<properties>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<!‐‐ 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 ‐‐>
<!‐‐ thymeleaf2 layout1‐‐>
<thymeleaf‐layout‐dialect.version>2.2.2</thymeleaf‐layout‐dialect.version>
</properties>
2. 使用
只要将静态的html页面,引入到templates目录下,就能自动渲染
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
-
在静态页面导入名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
-
语法:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF‐8"> <title>Title</title> </head> <body> <h1>成功!</h1> <!‐‐th:text 将div里面的文本内容设置为 ‐‐> <div th:text="${hello}">这是显示欢迎信息</div> </body> </html>
这样就可以使用了,如果上下文中含有hello对应的值,则可以取出来。
@Controller public class HelloController { @RequestMapping("/hello") public String hello(Map model) { model.put("hello", "你好,我来自后台"); return "hello"; } }
-
语法规则
1)、th:text;改变当前元素里面的文本内容;
th:任意html属性;来替换原生属性的值
语法配置自行百度。
3. SpringMVC自动配置原理
通过查看springmvc的自动配置类,了解配置好的一些组件。
-
以下是SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)
- 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))
- ContentNegotiatingViewResolver:组合所有的视图解析器的;
- 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;
-
Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars。
-
Static index.html support. 静态首页访问
-
Custom Favicon support (see below). favicon.ico
-
自动注册了 of Converter , GenericConverter , Formatter beans.
-
Converter:转换器; public String hello(User user):类型转换使用Converter
-
Formatter 格式化器; 2017.12.17===Date;
@ConditionalOnProperty(prefix = "spring.mvc", name = "date‐format")//在文件中配置日期格式化的规则 public Formatter<Date> dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件 }
自己添加的格式化器转换器,我们只需要放在容器中即可
-
-
Support for HttpMessageConverters (see below).
- HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json;
- HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter;
自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中
(@Bean,@Component)
-
Automatic registration of MessageCodesResolver (see below).定义错误代码生成规则
4. 扩展SpringMVC
/**
* 通过实现WebMvcConfigurer接口,可以作为springmvc的配置类,
* adapter是WebMvcConfigurer的适配类,是空实现。
* 原理是:
* 在WebMvcAutoConfigurationAdapter被使用时,含有注解@Import(EnableWebMvcConfiguration.class),
* 导入了类EnableWebMvcConfiguration类,这个类中有一个方法是获取所有的WebMvcConfigurer组件。
*
* @author: mahao
* @date: 2019/11/22
*/
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* 添加拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor());
}
private static class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("前置拦截:" + request.getParameterMap());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("后置拦截:" + response.getStatus());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
}
项目练习
1. 引入资源
静态资源导入到static文件夹下,需要模版引擎支持的页面导入到templates目录下。
2. 访问首页
对于springboot,我们知道他的默认欢迎页是静态文件夹下的index.html页面,但是现在我们需要将欢迎页指定为其他目录,则可以通过配置映射,将欢迎页的映射配置到我们自己的资源。方式有2种:
-
配置首页访问controller
/** * 将项目的欢迎页转发到自己的页面上,springboot默认的欢迎页是index.html,当请求是项目/或者项目/index.html, * 都是在访问欢迎页。我们将这两个请求,转发到我们自己的页面。 * * @author: mahao * @date: 2019/11/22 */ @Controller public class IndexController { @RequestMapping({"/", "index.html"}) public String index() { return "index"; } }
-
使用配置类,手动添加资源映射
@Configuration public class MainConfig { /** * 手动扩展配置springmvc,通过WebMvcConfigurer接口,在加载springmvc的配置文件时,会加载WebMvcConfigurer * 所有的类,可以通过实现这里面的方法,去注册ViewControllers。 * * @return */ @Bean public WebMvcConfigurer webMvcConfigurer() { //使用的是适配器接口 return new WebMvcConfigurerAdapter() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/index.html").setViewName("index"); } }; } }
3. 国际化
-
编写配置文件,配置文件的格式是: 文件名_语言-地区;
一共有三个多个配置文件,
login.properties #默认的
login.tip=请登录
login.username=用户名
---------------------------------
login_zh_CN.properties
login.tip=请登录
login.username=用户名
---------------------------------
login_en_US.properties
login.tip=Please sigin in
login.username=username
-
使用ResourceBundleMessageSource管理国际化资源文件
由于是使用springboot,已经配置好了管理国际化资源文件的组件了
MessageSourceAutoConfiguration
;//将国际化管理的组件添加到spring中 @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(this.basename)) { //获取所有的资源,通过basename指定文件名,这个basename是去掉语言的基础名,默认的是messages,所以,我们定义国际化文件的基础名为messages,则可以直接使用,而不需要配置了。 messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(this.basename))); } if (this.encoding != null) { messageSource.setDefaultEncoding(this.encoding.name()); } messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale); messageSource.setCacheSeconds(this.cacheSeconds); messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat); return messageSource; }
我们可以在配置文件中,指定basename为login,作为我们的国际化配置文件,
-
页面使用thremleaf模版引擎获取值
th:text=#{login.username} //取出模版引擎的值,
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1> <label class="sr-only" th:text="#{login.username}">Username</label>
-
中英文切换
需要完成点击按钮,去切换中英文。
国际化的原理就是Local(区域信息对象);LocalResolver(获取区域信息对象);
在springmvc中配置了LocalResolver;
@Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { //用默认配置的LocalResolver if (this.mvcProperties .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } //用根据请求头中信息,生成国际化 AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; }
要想实现点击链接实现国际化切换,则可以在连接上携带参数,国际化的参数;
/** * 替换掉系统使用的LocalResolver,使用自定义的localResolver; * 根据请求中携带的信息,判断使用哪种国际化. */ @Bean public LocaleResolver localeResolver() { return new LocaleResolver() { //如果指定了使用哪一种国际化,则使用,没有的话,使用默认的; @Override public Locale resolveLocale(HttpServletRequest request) { String l = request.getParameter("l"); System.out.println(l); if (!StringUtils.isEmpty(l)) { String[] split = l.split("_"); return new Locale(split[0], split[1]); } else { return Locale.getDefault(); } } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } }; }
4. 登录
@Controller
public class LoginController {
@PostMapping("/login")
public String login(String name, String pwd, HttpServletRequest request) {
if ("zs".equalsIgnoreCase(name) && "123456".equalsIgnoreCase(pwd)) {
request.getSession().setAttribute("user", "zs");
return "dashboard";
} else {
return "index";
}
}
}
新创建一个controller,处理登录功能。如果失败,返回登录页面。如果成功了,去模版引擎下的dashboard.html页面,但是这个操作是转发,浏览器的地址栏仍然是login操作。所以要使用重定向,定向到dashboard.html页面,但是这个要经过模版引擎解析,如果只是直接返回的是 redirect:dashboard
,则页面会访问http://xxx/dashboard.html,但是在模版引擎目录下,无法访问。所以,需要配置一个资源映射,作为主页面的映射。
可以在之前使用的添加登录页面的WebmvcConfiguration中配置。
@PostMapping("/login")
public String login(String name, String pwd, HttpServletRequest request) {
if ("zs".equalsIgnoreCase(name) && "123456".equalsIgnoreCase(pwd)) {
request.getSession().setAttribute("user", "zs");
return "redirect:main.html";
} else {
return "index";
}
}
@Bean
public WebMvcConfigurer webMvcConfigurer() {
//使用的是适配器接口
return new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
};
}
5. 登录拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String user = (String) request.getSession().getAttribute("user");
if (user == null) {
//重定向回去,改变路径;
//request.getRequestDispatcher("/index.html").forward(request, response);
response.sendRedirect("index.html");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
6. restful 风格crud
具体代码忽略;
7. 错误处理机制
1. springboot默认的错误处理机制
制造一个错误,当分页传入的页数为负数时,会发生数组越界错误;
-
浏览器,返回一个默认的错误页面
-
如果是其他客户端,默认响应一个json数据
{ "timestamp": 1574489861201, "status": 500, "error": "Internal Server Error", "exception": "java.lang.ArrayIndexOutOfBoundsException", "message": "-4", "path": "/crud/emps" }
2. 原理
配置文件是 ErrorMvcAutoConfiguration
,错误处理的自动配置;
//错误信息
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
//返回错误页面跳转的controller,帮我们处理/error请求
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
//错误页面扩展,系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties);
}
自动配置类中注册了3个组件,ErrorPageCustomizer
是确定错误页面的请求路径,默认的是 ’/error‘,组件 BasicErrorController
则是负责处理这个请求。 DefaultErrorAttributes
是封装的错误信息,提供给用户错误的属性的获取。
DefaultErrorAttributes:
//帮我们在页面共享信息
@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;
}
BasicErrorController
处理/error请求
//产生html类型的数据;浏览器发送的请求来到这个方法处理
@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());
//通过resolveErrorView解析获取去那个错误View
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//如果modelandview为null,创建一个默认的,视图名为error的视图;
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
//产生json数据,其他客户端来到这个方法处理
@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);
}
DefaultErrorViewResolver是resolveErrorView()方法;
//解析方法
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,Map<String, Object> model) {
//获取modelandview,根据传入的视图名
ModelAndView modelAndView = resolve(String.valueOf(status), model);
//获取失败了,判断SERIES_VIEWS中是否包含status.series(),即变成4xx
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
//如果上面都没有成功,则返回null,就用error视图;
return modelAndView;
}
//获取modelandview,根据传入的视图名
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//将视图拼接成了error/404这种;
String errorViewName = "error/" + viewName;
//是否有模版引擎,
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
//有的话,封装成ModelAndView,路径是errorViewName
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");
if (resource.exists()) {
//直接返回对应的静态html页面;
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
步骤:
一旦系统出现4xx,5xx之类的错误,ErrorpageCustomer
就会生效(定制错误的响应规则);就会来到/error请求;就会被 BasicErrorController
处理:
-
响应页面:去哪个页面是由
DefaultErrorViewResolver
解析得到到;protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { //所有的ErrorViewResolver得到ModelAndView for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }
-
解析规则:
1.如果有模版引擎的情况下,/error/状态码,为解析的视图名;
2.没有模版引擎的情况下,会直接使用静态资源路径下的状态码.html页面;
3.如果不存在对应的视图,则是使用默认的
new ModelAndView("error", model)
;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; }
对于json类型的数据响应:
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
//将错误信息,封装成map,向页面json化ResponseEntity类
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
错误信息的获取: DefaultErrorAttributes
//功能就是将request中的错误信息,包括状态码,错误信息,时间封装成map;
@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;
}
3. 定制错误响应
-
对于浏览器端:
- 如果有模版引擎的支持,在templates/error/下面对应状态码的html或者,4xx这种模糊开头的页面。
- 没有模版引擎的支持,在静态路径下创建对应的html文件,也必须是error目录下,依据上面的源码中有解析。
-
对于json数据
如果我们要向实现定制的类型的json数据,我们可以使用springmvc提供的全局异常处理,将controller抛出的错误都捕获;
通过 @ControllerAdvice 注解,我们可以在一个地方对所有 @Controller 注解的控制器进行管理。 注解了 @ControllerAdvice 的类的方法可以使用 @ExceptionHandler、 @InitBinder、 @ModelAttribute 注解到方法上,这对所有注解了 @RequestMapping 的控制器内的方法都有效。
@ExceptionHandler:用于捕获所有控制器里面的异常,并进行处理。
@InitBinder:用来设置 WebDataBinder,WebDataBinder 用来自动绑定前台请求参数到 Model 中。
@ModelAttribute:@ModelAttribute 本来的作用是绑定键值对到 Model 里,此处是让全局的@RequestMapping 都能获得在此处设置的键值对。
本文使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理。只要设计得当,就再也不用在 Controller 层进行 try-catch 了!
我们编写全局异常处理类:package cn.mh.sb.webcrud.compont; import cn.mh.sb.webcrud.common.ApiResult; import cn.mh.sb.webcrud.common.ApiStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * 全局异常处理类 * * @author: mahao * @date: 2019/11/23 */ @ControllerAdvice//标记注解,拦截所有的@Controller类 public class GlobalExceptionResolver { /** * 处理所有不可知异常 * * @param e * @return */ @ExceptionHandler(Exception.class) @ResponseBody public ApiResult handleException(Exception e) { System.out.println(e.getMessage()); return ApiResult.of(ApiStatus.UNKNOWN_ERROR); } /** * 处理自定义的异常 * * @param e * @return */ @ExceptionHandler(SBException.class) @ResponseBody public ApiResult handleMyException(Exception e) { System.out.println(e.getMessage()); return new ApiResult(ApiStatus.SUCCESS); } }
使用springmvc的全局异常处理机制,我们可以完成自定义json数据的返回,但是sprignboot提供的自适应功能我们失去了。如何更好的改变异常处理机制呢。我们可以使用springmvc全局异常处理机制+springboot自适应功能来实现。
我们想一下,在未配置springmvc全局异常处理之前,我们程序出现异常了,会调用ErrorPageCustomizer
组件的方法,寻找错误跳转请求,默认是/error
,然后会有一个 BaseController
根据请求头的不同去判断返回时json还是页面跳转,在跳转前会使用 DefaultErrorAttributes
去封装异常信息。所以我们可以使用 springmvc的全局异常处理机制,去跳转到/error请求,让springboot继续自适应,然后我们在跳转前封装我们自己的数据到返回信息中,我们可以自己创建一个ErrorAttributes组件。
//扩展的结果
{
"timestamp": 1574513432414,
"status": 508,
"error": "Loop Detected",
"exception": "java.lang.ArrayIndexOutOfBoundsException",
"message": "-4",
"path": "/crud/emps",
"ext": {
"msg": "-4"
},
"company": "mh"
}
先使用springmvc的全局异常处理机制,将页面跳转到 /error请求中;
/**
* 带自适应的异常处理机制,自定义实现数据封装组件,具体的看DefaultErrorAttributes的使用
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public String handleExceptionbySb(Exception e, HttpServletRequest request) {
//设定我们自定义异常的状态码,会根据这个状态码去寻找错误页面
request.setAttribute("javax.servlet.error.status_code", 508);
HashMap<Object, Object> map = new HashMap<>();
map.put("msg", e.getMessage());
request.setAttribute("extendMsg",map);
System.out.println("..................");
return "forward:/error"; //转发到/error请求
}
需要注意的是,要设置状态码,否则转发过去后,状态码是200,无法成功,而且状态码必须是400/500这些,否则不成功,设置状态码是为了自定义跳转的页面。这里定义了一个508,是让controller的错误跳转到自定义的错误页面。在这里我们可以向request域中存入我们要在错误中显示的信息。
重新添加 ErrorAttributes组件,替换掉springboot自带的错误解析组件。
/**
* 自定义实现错误信息的封装,我们继承了DefaultErrorAttributes,
* 在他的基础上实现我们的扩展信息。
*
* @author: mahao
* @date: 2019/11/23
*/
@Component
public class ExtendErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
//取出旧的信息
Map<String, Object> attributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
//取出扩展的信息,这些扩展信息是springmvc的全局异常页面跳转时放进去的数据
Object extendMsg = requestAttributes.getAttribute("extendMsg", RequestAttributes.SCOPE_REQUEST);
attributes.put("ext", extendMsg);
attributes.put("company", "mh");
return attributes;
}
}
上面就是显示全部了。
如果想要看懂如何实现的自定义json数据,需要看懂springboot的异常处理机制,以及三个组件的作用。
代码地址:
https://github.com/mashenghao/springboot-learn/tree/master/web-crud