SpringBoot(六)——————国际化、Restful、错误处理机制

源码地址:https://github.com/877148107/scms_admin/tree/master/scms_learn_thymeleaf

参考文档:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/html/spring-boot-features.html#boot-features-internationalization

  • 国际化

1)、编写国际化配置文件,抽取页面需要显示的国际化消息

#配置国际化资源的配置文件路径+名称
spring.messages.basename=i18n.login

2)、SpringBoot自动配置好了管理国际化资源文件的组件;

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

	private static final Resource[] NO_RESOURCES = {};

	@Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
	}

 3)、可以根据浏览器语言切换

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">
		<title>Signin Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.4.1/css/bootstrap.css}" rel="stylesheet">
		<!-- Custom styles for this template -->
		<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
	</head>

	<body class="text-center">
		<form class="form-signin" action="dashboard.html">
			<img class="mb-4" src="asserts/img/bootstrap-solid.svg" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
			<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>
			<input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.userName}" required="" autofocus="">
			<label class="sr-only" th:text="#{login.password}">Password</label>
			<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
			<div class="checkbox mb-3">
				<label>
          <input type="checkbox" value="remember-me">[[#{login.remember}]]
        </label>
			</div>
			<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
			<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
			<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
		</form>

	</body>

</html>

4)、点击按钮切换国际化

SpringMvc自动配置中添加了国际化Locale(区域信息对象),LocaleResolver(获取区域信息对象)

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
......
    		@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;
		}
}

自己重写获取区域信息对象,那么自动配置中的就会失效

package com.wmy.scms.comment;


import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * @ClassName: MyLocaleResolver
 * =================================================
 * @Description: 区域信息
 * =================================================
 * CreateInfo:
 * @Author: William.Wangmy
 * @Email: [email protected]
 * @CreateDate: 2020/1/7 23:08
 * @Version: V1.0
 */
public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");
        Locale locale = Locale.getDefault();
        if (!StringUtils.isEmpty(l)) {
            String[] s = l.split("_");
            locale = new Locale(s[0],s[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

效果

  •  Restful

1)、RestfulCRUD:CRUD满足Rest风格;

URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作

  普通CRUD(uri来区分操作) RestfulCRUD
查询 getEmp emp---GET
添加 addEmp?xxx emp---POST
修改 updateEmp?id=xxx&xxx=xx emp/{id}---PUT
删除 deleteEmp?id=1 emp/{id}---DELETE

2)、查询

<tr th:each="emp:${emps}">
	<td th:text="${emp.id}"></td>
	<td>[[${emp.lastName}]]</td>
	<td th:text="${emp.email}"></td>
	<td th:text="${emp.gender}==0?'女':'男'"></td>
	<td th:text="${emp.department.departmentName}"></td>
	<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td>
	<td>
		<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
	</td>
</tr>
    /**
     * 修改页面
     * @param id
     * @param model
     * @return
     */
    @GetMapping("/emp/{id}")
    public String toEditPage(@PathVariable("id") Integer id,Model model){
        Employee employee = employeeDao.get(id);
        model.addAttribute("emp",employee);

        //页面要显示所有的部门列表
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts",departments);
        //回到修改页面(add是一个修改添加二合一的页面);
        return "emp/add";
    }

3)、添加

<!--需要区分是员工修改还是添加;-->
<form th:action="@{/emp}" method="post">
	<!--发送put请求修改员工数据-->
	<!--
	1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
	2、页面创建一个post表单
	3、创建一个input项,name="_method";值就是我们指定的请求方式
	-->
	<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
	<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
	<div class="form-group">
		<label>LastName</label>
		<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
	</div>
	<div class="form-group">
		<label>Email</label>
		<input name="email" type="email" class="form-control" placeholder="[email protected]" th:value="${emp!=null}?${emp.email}">
	</div>
	<div class="form-group">
		<label>Gender</label><br/>
		<div class="form-check form-check-inline">
			<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">
			<label class="form-check-label">男</label>
		</div>
		<div class="form-check form-check-inline">
			<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">
			<label class="form-check-label">女</label>
		</div>
	</div>
	<div class="form-group">
		<label>department</label>
		<!--提交的是部门的id-->
		<select class="form-control" name="department.id">
			<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
		</select>
	</div>
	<div class="form-group">
		<label>Birth</label>
		<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
	</div>
	<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
</form>
/**
 * 新增
 * @param employee
 * @return
 */
@PostMapping("/emp")
public String addEmp(Employee employee){
	//来到员工列表页面

	System.out.println("保存的员工信息:"+employee);
	//保存员工
	employeeDao.save(employee);
	// redirect: 表示重定向到一个地址  /代表当前项目路径
	// forward: 表示转发到一个地址
	return "redirect:/emps";
}

4)、修改

/**
 * 修改信息
 * @param employee
 * @return
 */
@PutMapping("/emp")
public String updateEmployee(Employee employee){
	System.out.println("修改的员工数据:"+employee);
	employeeDao.save(employee);
	return "redirect:/emps";
}

5)、删除

/**
 * 员工删除
 */
@DeleteMapping("/emp/{id}")
public String deleteEmployee(@PathVariable("id") Integer id){
	employeeDao.delete(id);
	return "redirect:/emps";
}
  • 错误处理机制

1)、Springboot默认的错误处理机制

  • 默认的错误显示方式

  • 浏览器请求和非浏览器请求的区别

  • Sprigboot错误自动配置,ErrorMvcAutoConfiguration

错误信息自动配置给容器中添加了一下组件:

1.DefaultErrorAttributes

页面共享信息

@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

	@Override
	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;
	}

timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里

以上信息都可以在页面获取到


2.BasicErrorController

第一个方法产生HTML数据、第二个方法产生JSON数据

@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, 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
	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, isIncludeStackTrace(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}
}


3.ErrorPageCustomizer

private 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);
		}

系统出现错误后来到error请求进行处理, BasicErrorController

public class ErrorProperties {

	/**
	 * Path of the error controller.
	 */
	@Value("${error.path:/error}")
	private String path = "/error";


4.DefaultErrorViewResolver

系统默认是找工程目录下error下的错误代码页面,比如error/404.html

模板引擎可以解析的就返回对应的页面,不能解析就去找静态资源下的页面error/404.html

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

	private static final Map<Series, String> SERIES_VIEWS;

	static {
		Map<Series, String> views = new EnumMap<>(Series.class);
		views.put(Series.CLIENT_ERROR, "4xx");
		views.put(Series.SERVER_ERROR, "5xx");
		SERIES_VIEWS = Collections.unmodifiableMap(views);
	}
	
	
	@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;
	}
	
	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);
	}
	
	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()) {
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			}
			catch (Exception ex) {
			}
		}
		return null;
	}

错误处理规则:

1.系统当中出现了错误信息4xx、5xx等错误信息ErrorPageCustomizer组件就会生效

2.出现错误后系统会发出error请求BasicErrorController进行处理,错误页面的跳转由默认的错误页面解析器处理DefaultErrorViewResolver

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;
	}
发布了101 篇原创文章 · 获赞 10 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/WMY1230/article/details/103724078
今日推荐