局部配置
@InitBinder
日期参数绑定
通常我们使用它,来解决日期类型参数绑定的问题:
@Controller
@RequestMapping("/ad")
public class AdapterController {
private SimpleDateFormat yMd = new SimpleDateFormat("yyyy-MM-dd");
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(yMd,false));
}
}
参数校验 with @Valid
- 1.注册一个敏感词Validator,
@Component
public class SensitiveNameValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return String.class == clazz;
}
@Override
public void validate(Object target, Errors errors) {
String str = (String) target;
if(str.indexOf("xi") > -1) {
errors.reject("-0001","包含敏感词");
}
}
}
- 2.在controller中添加
validator
信息:
@Controller
@RequestMapping("/ad")
public class AdapterController {
@InitBinder
public void initBinderValidator(WebDataBinder binder) {
//设置为sensitiveNameValidator
// binder.setValidator(sensitiveNameValidator);
//添加一个validator
binder.addValidators(sensitiveNameValidator);
}
}
- 3.在目标方法中使用validator:
@Controller
@RequestMapping("/ad")
public class AdapterController {
@RequestMapping(value = "/valid")
@ResponseBody
public String index(@Valid String name, BindingResult br){
if(br.hasErrors()){
br.getAllErrors().forEach(error ->{
System.out.println("error===="+error);
});
}
return "hello:" + name;
}
}
@ModelAttribute
@ModelAttribute
方法通常被用来填充一些公共需要的属性或数据。
@Controller
@RequestMapping("/ad")
public class AdapterController {
//给当前model设置("className",xxx)
@ModelAttribute("className")
public String setModel(){
return this.getClass().getName();
}
//给当前model设置("teacher", "老夫子")
@ModelAttribute
public void setModel2(Model model2){
model2.addAttribute("teacher", "老夫子");
}
// 给当前model设置("string","execlib")
@ModelAttribute
public String setModel3(){
return "execlib";
}
@RequestMapping("/attr")
@ResponseBody
public String attr(Model model){
Map map = model.asMap();
System.out.println("========="+map.get("className")); //cn.jhs.mvc.controller.AdapterController
System.out.println("========="+map.get("teacher")); //老夫子
System.out.println("========="+map.get("string")); //execlib
return "success";
}
@RequestMapping("attr2")
public String attr2(@ModelAttribute("teacher") String teacher, BindingResult result) {
if (result.hasErrors()) {
return "error";
}
System.out.println("========="+teacher); //老夫子
return "success";
}
}
@SessionAttributes
Spring MVC对session的操作有如下两种方式:
- 基于HttpSession
- 基于注解@SessionAttributes
@SessionAttributes
SessionAttributes是只能注解于类或者接口,@SessionAttributes的value代表我们需要把什么样的对象放入session,在我们的方法后当我们把对象放入ModelMap这个对象的时候,根据匹配规则也会自动设置到session中。
@SessionAttributes比较简单,只有两个属性:value
和types
value
: 数组。 存储任意modelAttribute中任意 同名的属性。types
: 数组。 存储任意modelAttribute中任意 同类型的属性。- 注意 :当两个属性同时配置时, 二者是
or
的关系,取二者匹配到属性的合集
。
demo
@Controller
@RequestMapping("/session")
@SessionAttributes(value={"name","birth","age"},types = {String.class,Date.class})
public class SessionController {
private final String CUR_USER_NAME_KEY = "cur_user_name";
@RequestMapping("/set")
@ResponseBody
public String set(Model model , HttpSession session) {
//与@SessionAttributes value匹配 或者属于 types中任意类型即可
model.addAttribute("name", "Trump");
model.addAttribute("birth", new Date());
model.addAttribute("age", 31);
//传统session方式
session.setAttribute(CUR_USER_NAME_KEY,"X-MAN");
return "set session success!";
}
@RequestMapping("/get")
@ResponseBody
public String error(HttpSession session){
System.out.println("======"+session.getAttribute(CUR_USER_NAME_KEY)); // X-MAN
System.out.println("======"+session.getAttribute("name")); // Trump
System.out.println("======"+session.getAttribute("birth")); //当前date
System.out.println("======"+session.getAttribute("age")); //31
return "success";
}
}
全局配置
上面列出的@InitBinder、@ModelAttribute都仅在Controller内部有效,那么有没有能够一处配置,所有的@Controller都起作用的配置方法呢,答案是有的。
@ControllerAdvice
//org.springframework.web.bind.annotation.ControllerAdvice ;
public @interface ControllerAdvice {
//basePackages属性的别名
String[] value() default {};
//扫描basePackages下的所有Controller
String[] basePackages() default {};
//相对于basePackages更细粒度,到具体的classes
Class<?>[] basePackageClasses() default {};
//Controller 具有指定的父类或实现指定的接口
Class<?>[] assignableTypes() default {};
//Controller 具有指定类型注解
Class<? extends Annotation>[] annotations() default {};
}
全局属性配置
@ControllerAdvice
public class MyControllerAdvice {
@ModelAttribute("globalAttr")
public String setModelGlobal(){
return "全局属性";
}
}
全局异常处理 with @ExceptionHandler
@ControllerAdvice
public class MyControllerAdvice {
public static final String DEFAULT_ERROR_VIEW = "/error";
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
RestControllerAdvice
RestControllerAdvice
,作用于@RestController
注解下的bean中。用法同ControllerAdvice
类似。
@ResponseBodyAdvice
@ResponseBodyAdvice
可以修改返回值,比如数据脱敏、数据加密等。
返回值类型限制
它只能对如下两种返回值的方法起作用:
- 返回值为
HttpEntity类型
- 返回值或处理器方法上注释了
@ResponseBody
类定义限制
ResponseBodyAdvice必须
- register到RequestMappingHandlerAdapter 或者ExceptionHandlerExceptionResolver
- 它的定义上有@ControllerAdvice注解。
demo
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<String> {
//判断是否为手机号方法,逻辑省略,直接返回true
private boolean isTelNo(MethodParameter returnType){
return true;
}
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return isTelNo(returnType);
}
@Override
public String beforeBodyWrite(String telNo, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//手机号脱敏
return telNo.replaceAll("(\\d{3})(\\d{4})(\\d{3})","$1****$3") ;
}
}
参数绑定
@MatrixVariable
MatrixVariable也是url中的一部分,不过它不是url的一个值,而是值的一些属性,它通过分号和逗号配合使用。
SpringMvc使用
<mvc:annotation-driven enable-matrix-variables="true"> </mvc:annotation-driven>
开启@MatrixVariable功能。
基本用法
//GET /phone/xiaomi;color=red
@RequestMapping("/phone/{factory}")
public String matrix(@PathVariable String factory,@MatrixVariable String color){
System.out.println(factory);//xiaomi
System.out.println(color); //red
return factory + ":" +color;
}
多个@MatrixVariable
//GET /phone/xiaomi;size=64g;color=red
@RequestMapping("/phone/{factory}")
public String multi(@PathVariable String factory,@MatrixVariable String size,@MatrixVariable String color){
System.out.println(factory);//xiaomi
System.out.println(size); //64g
System.out.println(color); //red
return factory + ":" +size + ":" +color;
}
多个@MatrixVariable转换Map
使用map时,map的value是List
//GET /phone/xiaomi;size=64g;color=red
@RequestMapping("/phone/{factory}")
public String map(@PathVariable String factory,@MatrixVariable Map map){
//返回的value是List
System.out.println(factory);//xiaomi
System.out.println(map.get("size")); // [11]
System.out.println( map.get("color")); // [red],
return factory + ":" +map;
}
传递Set属性
//GET /phone/xiaomi;size=64g;color=red,black
@RequestMapping("/phone/{factory}")
public String set(@PathVariable String factory,@MatrixVariable String size,@MatrixVariable Set color){
System.out.println(factory);//xiaomi
System.out.println(size); //64g
System.out.println(color); //[red, black]
return factory + ":" +size + ":" +color;
}
指定pathVar
{factory}和{category}
都有color
属性,可以通过pathVar
来分割,将属性存储至不同的map中去。
//GET /phone/xiaomi;color=red,write/note5;color=black
@RequestMapping("/phone/{factory}/{category}")
public String multiMap(@PathVariable String factory,@MatrixVariable(value="color",pathVar = "factory") Set colorSet ,@MatrixVariable(pathVar = "category",value = "color") String color2 ){
System.out.println(factory);//xiaomi
System.out.println(colorSet); //[red,write]
System.out.println(color2); //black
return factory + ":" +colorSet + ":" +color2;
}
异常处理
servlet容器默认错误页面
web.xml
<error-page>
<error-code>404</error-code>
<location>/errors/404.jsp</location>
</error-page>
servlet处理
首先定义servlet
@Controller
public class ErrorController {
@RequestMapping(value = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
error.jsp
<%@ page contentType="application/json" pageEncoding="UTF-8"%>
{
status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
reason:<%=request.getAttribute("javax.servlet.error.message") %>
}
@ResponseStatus
使用@ResponseStatus
,当异常被抛出时,ResponseStatusExceptionResolver会设置相应的响应状态码。
自定义异常类
@ResponseStatus(value= HttpStatus.NOT_FOUND, reason="全局 not found exception")
public class MyMvcException extends RuntimeException {
}
使用:
@RequestMapping("/status")
public String status(){
throw new MyMvcException();
}
结果如下图:
定义在method
@RequestMapping("/status2")
@ResponseBody
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "FORBIDDEN更改状态")
public String status2(){
return "success";
}
结果如下图:
定义在方法上时,无论有无异常抛出,Http status状态码都会被改写!
结合@ExceptionHandler
@ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="数组越界异常....") // 400
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
public void conflict() {
// Nothing to do
}
@RequestMapping("/status3")
public String status3(){
throw new ArrayIndexOutOfBoundsException();
}
处理结果:
全局页面配置
使用Spring MVC的时候,当添加一个新页面访问总是要新增一个Controller
或者方法,后跳转到指定的页面上去。
可以使用如下的全局配置
WebMvcConfigurerAdapter
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/success").setViewName("success");
registry.addViewController("/error").setViewName("error");
registry.addViewController("/loginPage").setViewName("login");
}
}
而在spring5.x之后,WebMvcConfigurerAdapter类被标记为@Deprecated
了
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
}
}
,原因是JDK8
之后,接口支持default
方法,所以我们可以直接通过实现了WebMvcConfigurer
来实现诸上功能;
WebMvcConfigurer
@Configuration
public class MvcConfig implements WebMvcConfigurer{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/success").setViewName("success");
registry.addViewController("/error").setViewName("error");
registry.addViewController("/loginPage").setViewName("login");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册interceptor
}
}
BasicErrorController
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class ExtendedErrorController extends BasicErrorController implements ErrorController {
public ExtendedErrorController(ErrorAttributes errorAttributes, ServerProperties properties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, properties.getError(), errorViewResolvers);
}
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
response.setStatus(status.value());
Map<String, Object> errorAttributes = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML));
ModelAndView modelAndView = this.resolveErrorView(request, response, status, errorAttributes);
response.setStatus(status.value());
if (modelAndView == null) {
modelAndView = new ModelAndView();
String errorPath = this.errorPath(status);
InternalResourceView view = new InternalResourceView(errorPath);
modelAndView.setView(view);
modelAndView.setStatus(status);
}
return modelAndView;
}
@RequestMapping(
produces = {"application/json;charset=UTF-8"}
)
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
return super.error(request);
}
private String errorPath(HttpStatus status) {
switch(status) {
case NOT_FOUND:
return "/404.html";
case UNPROCESSABLE_ENTITY:
return "/422.html";
default:
return "/500.html";
}
}
}