SpringBoot的web开发
一、默认访问首页
使用WebMvcConfigurerAdapter
(基于SpringBoot 1.x)可以来扩展SpringMVC的功能,详情查看【7】SpringBoot的SpringMVC自动配置 - 第三节
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
//@EnableWebMvc 不要接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return adapter;
}
}
二、国际化
SpringMVC步骤:
- 编写国际化配置文件;
- 使用ResourceBundleMessageSource管理国际化资源文件
- 在页面使用fmt:message取出国际化内容
SpringBoot步骤:
- 编写国际化配置文件
编写国际化配置文件
使用IDEA Resouces Bundle视图编写,点击查看教程
SpringBoot的国际化自动配置
SpringBoot自动配置好了管理国际化资源文件的组件,源码:
@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties,能被SpringBoot直接识别
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
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;
}
由上面源码可知,国际化配置文件的默认文件夹是classpath下的messages文件夹,如放在别的位置,需要指定国际化文件的位置
spring.messages.basename=*
#如我放在了resources文件夹下的i18n下
spring.messages.basename=i18n/login
页面使用
#{login.tip}
切换语言环境
- SpringBoot默认就有区域信息解析器(
LocaleResolver
),根据当前浏览器环境自动切换
//WebMvcAutoConfigutation中的源码
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties
.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
- 点击链接切换国际化
步骤:
- 在链接上携带国际化信息,如:
xxx.html?lang=zh_CN
- 编写自己的区域信息解析器(
LocaleResolver
)- 将自己的区域信息解析器(
LocaleResolver
),添加到容器中
- 在链接上携带国际化信息
<a class="btn btn-sm" th:href="@{/index.html(lang='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(lang='en_US')}">English</a>
- 编写自己的区域信息解析器
public class MyLocaleResolver implements LocaleResolver {
//解析区域信息
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言信息
String lang = request.getParameter("lang");
//构建默认Locale对象
Locale locale = new Locale("zh", "CN");
if (!StringUtils.isEmpty(lang)){
String[] split = lang.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
- 将自己的区域信息解析器(
LocaleResolver
),添加到容器中
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
/**
* 讲自己写的区域信息解析器添加到容器中
*/
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
三、登录
使用th:action标签提交表单数据
<form th:action="@{/user/login}" method="post">
添加视图解析器
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
};
return adapter;
}
}
编写Controller
@PostMapping(value = "/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String,Object> map, HttpSession session){
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//登陆成功,防止表单重复提交,可以重定向到主页
session.setAttribute("loginUser",username);
return "redirect:/main.html";
}else{
//登陆失败
map.put("msg","用户名密码错误");
return "login";
}
}
注册拦截器,检查登录状态
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//静态资源; *.css , *.js
//SpringBoot已经做好了静态资源映射
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
//排除拦截请求
.excludePathPatterns("/index.html","/","/user/login");
}};
return adapter;
}
}
四、错误处理机制
SpringBoot默认的错误处理机制
SpringBoot在ErrorMvcAutoConfiguration
中已经添加了一些组件帮我们做了一些错误处理机制
主要有以下几种:
DefaultErrorAttributes
BasicErrorController
ErrorPageCustomizer
DefaultErrorViewResolver
处理逻辑
- 将请求转发到BasicErrorController控制器来处理请求,
- 浏览器请求响应BasicErrorController的errorHtml()方法,APP等客户端响应error()方法
- 以浏览器的404错为例:最终返回一个modelAndView
3-1. 调用BasicErrorController
的errorHtml(HttpServletRequest request, HttpServletResponse response)
方法,其中status=404
3-2. 调用AbstractErrorController
的resolveErrorView
方法,遍历ErrorMvcAutoConfiguration.errorViewResolvers
,寻找需要的modelAndView
;
3-3.ErrorMvcAutoConfiguration.errorViewResolvers
会有一个默认的DefaultErrorViewResolver
,于是便执行DefaultErrorViewResolver.resolveErrorView()
方法;
3-4.DefaultErrorViewResolver.resolveErrorView()
的具体实现:调用当前的this.resolve(status, model)
,创建modelAndView
;//即寻找error/404页面
3-5. 如果创建error/404视图失败(即找不到error/404视图),则创建error/4XX视图;否则,继续创建视图;
3-6. 如果创建error/4XX视图失败(即找不到error/4XX视图),则创建默认名为error的视图,而error视图在静态累WhitelabelErrorViewConfiguration
中进行配置和加载(即Springboot默认的Whitelabel Error Page页面);
3-7. 根据实际获取到的视图(ModalAndView
),进行渲染
默认的发生错误,它会将请求转发到BasicErrorController
控制器来处理请求,下面是该controller类的源码:
@Controller
//如果server.error.path未配置,则跳转到error方法
@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.emptyList());
}
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}
public String getErrorPath() {
return this.errorProperties.getPath();
}
/*
映射浏览器发送来的请求
*/
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
}
/*
不是浏览器而是其他软件app客户端发送的错误请求
*/
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
IncludeStacktrace include = this.getErrorProperties().getIncludeStacktrace();
if (include == IncludeStacktrace.ALWAYS) {
return true;
} else {
return include == IncludeStacktrace.ON_TRACE_PARAM ? this.getTraceParameter(request) : false;
}
}
protected ErrorProperties getErrorProperties() {
return this.errorProperties;
}
}
errorHtml
:映射浏览器发送来的错误请求,产生HTML类型的数据
error
:映射不是浏览器而是其他软件app客户端发送的错误请求,产生JSON的数据
定制错误的页面
有模版引擎
在模版引擎src/main/resources/templates
路径下新建文件夹error
文件夹,然后新建4xx.html和5xx.html页面即可,当然也可以定制精确状态码页面例如400.html、404.html、500.html等,并遵循精确优先原则:
演示效果:
以上默认返回视图(ModalAndView
),会返回一些状态信息,主要有以下几种:
- timestamp:时间戳
- status:状态码
- error:错误提示
- exception:异常对象
- message:异常消息
- errors:JSR303数据校验的错误都在这里
我们也可以在错误页面中,根据返回的状态信息,在错误页面输出内容:
<h3 color="red">自定义的错误页面4xx: [[${{status}}]]</h3>
<h3 color="red">错误提示: [[${{error}}]]</h3>
<h3 color="red">时间戳: [[${{timestamp}}]]</h3>
效果展示:
没有模版引擎
模板引擎找不到这个错误页面,默认在静态资源文件夹下找error/错误代码.html
页面。
如果也没找到,显示SpringBoot默认的错误页面
定制错误的数据
自定义异常处理&返回定制json数据
- 创建自定义异常:
public class UserNotExistException extends RuntimeException {
public UserNotExistException() {
super("用户不存在");
}
}
- 创建异常处理Controller:
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
- 编写请求方法模拟出异常:
@ResponseBody
@RequestMapping("/test_request_error")
public String testRequestError(@RequestParam("user") String user) {
if (user.equals("aaa")){
throw new UserNotExistException();
}
return null;
}
测试结果:
转发到/error进行自适应响应效果处理
如果需要让错误转发到相应的错误页面,只需要传入错误状态码(必须传入4xx/5xx),让SpringBoot默认的错误处理帮我们解析错误就可以了:
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
//转发到/error
return "forward:/error";
}
}
既要自适应处理,也要自定义数据
。。。未完