Springboot之Spring MVC
1.DispatcherServlet在springboot中的配置
1.1 在配置类中配置
@Bean
public ServletRegistrationBean dispatcherRegistration(
DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(
dispatcherServlet,"/api/*"
);
}
1.2在application.properties中配置
spring.mvc.pathmatch.use-suffix-pattern=true
server.servlet.context-path=/ ->是项目访问根目录:例如:/vms
2.参数校验
数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具
直接向后端请求一些违法数据。
JSR(Java Specification Requests)
是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!
校验的时候我们实际用的是 Hibernate Validator
框架。Hibernate Validator 是 Hibernate 团队最初的数据校验框架,Hibernate Validator 4.x
是Bean Validation 1.0(JSR 303)
的参考实现,Hibernate Validator 5.x
是 Bean Validation 1.1(JSR 349)
的参考实现,目前最新版的 Hibernate Validator 6.x
是 Bean Validation 2.0(JSR 380)
的参考实现。
SpringBoot 项目的 spring-boot-starter-web
依赖中已经有 hibernate-validator
包,不需要引用相关依赖。如下图所示(通过 idea 插件—Maven Helper 生成)
2.1. 一些常用的字段验证的注解
@NotEmpty
被注释的字符串的不能为 null 也不能为空
@NotBlank
被注释的字符串非 null,并且必须包含一个非空白字符
@Null
被注释的元素必须为 null
@NotNull
被注释的元素必须不为 null
@AssertTrue
被注释的元素必须为 true
@AssertFalse
被注释的元素必须为 false
@Pattern(regex=,flag=)
被注释的元素必须符合指定的正则表达式
@Email
被注释的元素必须是 Email 格式。
@Min(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=)
被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)
被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past
被注释的元素必须是一个过去的日期
@Future
被注释的元素必须是一个将来的日期
2.1.1验证请求体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
@NotNull(message = "classId 不能为空")
private String classId;
@Size(max = 33)
@NotNull(message = "name 不能为空")
private String name;
@Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")
@NotNull(message = "sex 不能为空")
private String sex;
@Email(message = "email 格式不正确")
@NotNull(message = "email 不能为空")
private String email;
}
需要在前端@RequestMapping
注解的方法中加上@Valid
@RestController
@RequestMapping("/api")
public class PersonController {
@PostMapping("/person")
public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
return ResponseEntity.ok().body(person);
}
}
如果验证不通过就会抛出MethodArgumentNotValidException
异常,我们可以在全局异常处理类中处理抛出的异常,如2.2.
2.2 @ControllerAdvice和@ExceptionHandler
@ControllerAdvice
在全局处理类上使用
@ExceptionHandler
在全局异常处理类的方法上使用
spring通过@ControllerAdvice
和@ExceptionHandler
来处理全局异常
2.2.1示例:
// Target all Controllers annotated with @RestController
//表示所有被@RestControlle注解的controller都会使用全局异常处理
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
//表示指定包下的所有controller都会使用全局异常处理
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
//表示指定的controller会使用该全局异常处理
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理自定义的业务异常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = BizException.class)
@ResponseBody
public ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
logger.error("发生业务异常!原因是:{}",e.getErrorMsg());
return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
}
/**
* 处理空指针的异常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value =NullPointerException.class)
@ResponseBody
public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){
logger.error("发生空指针异常!原因是:",e);
return ResultBody.error(CommonEnum.BODY_NOT_MATCH);
}
/**
*处理MethodArgumentNotValidException.class异常
*
**/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Object requestMethodArgumentNotValidException(HttpServletRequest request,MethodArgumentNotValidException exception){
Map<String,String> map = new HashMap<>();
map.put("code", ErrorCodeEnum.FAILURE.errCodeString());
map.put("message", "");
if (exception != null){
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
for (FieldError error : fieldErrors){
error.getDefaultMessage();
map.put("message", exception.getMessage());
break;
}
}
map.put("data", new Object().toString());
return map;
}
/**
* 处理其他异常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value =Exception.class)
@ResponseBody
public ResultBody exceptionHandler(HttpServletRequest req, Exception e){
logger.error("未知异常!原因是:",e);
return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
}
}
2.2.2全局异常处理类的特点
1.全局异常处理只能
处理被捕获的异常,如果在全局异常处理之前
,异常被捕获,那么就无法在全局异常处理中的进行处理
2.全局异常处理类中定了多种异常处理方法,会优先
处理子类异常,即如果同时定义了处理Exception和NullPointerException异常的方法,则会优先处理子类异常,也就是NullPointerException。如果只catch了子类异常,抛出更高级异常时,需在全局异常处理中定义处理方法;如果catch
住了Exception异常
,则所有的异常都不会被送到全局异常处理类
中
3.自义定全局异常处理除了可以处理上述的数据格式之外,也可以处理页面的跳转
,只需在新增的异常方法的返回处理上填写该跳转的路径,并且不
使用ResponseBody
注解即可 。
4.RestControllerAdvice
和ControllerAdvice
的功能大致一样,但是会将返回的数据自动转换为Json格式
5.@ExceptionHandler
在@RequestMapping
方法之后执行,而@ModelAttributehe,@InitBinder
在@RequestMapping
方法之前执行。
3.拦截器(Interceptor和过滤器(Filter)的区别
3.1 相似之处
Spring的Interceptor
(拦截器)与Servlet的Filter
有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查、日志记录等
3.2 区别
4.在springboot中注册Filter
1.通过配置类中声明FilterRegistrationBean
来注册
@Bean
public FilterRegistrationBean<Filter> getFilter(){
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
registration.setFilter(new Filter());
registration.addUrlPatterns("/ui/*");
}
2.通过@WebFilter注解,但是通过注解无法设置优先级
5.消息转换器
Message Converters
可以在配置类中实现WebMvcConfigurer,重写 configureMessageConverters()
或者重写 extendMessageConverters()
方法来配置自己的http消息转换器(替代springMvc默认的)
@Configuration
@EnableWebMvc ->在springboot中不需要添加该注解
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(
new ParameterNamesModule());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
}
xml的配置方式
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http
.converter.json
.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http
.converter.xml
.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="org.springframework
.http.converter.json
.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall=
"com.fasterxml.jackson
.module.paramnames.ParameterNamesModule"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
6.设置静态资源的访问路径
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public", "classpath:/static/")
.setCachePeriod(31556926);
}
}
8.异步请求的流程
1.控制器返回Callable
2.Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行
3.DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态;
4.Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
5.根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。
可以定义自己的TaskExecutor,并在实现了WebMvcConfigure的配置类中重写
/**
* <p>设置web异步请求callable的执行TaskExecutor</p>
* @params [configurer]
* @returns void
* @author lijun58 2020/2/20 17:09
*/
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(getExecutor());
}
9.如何在Service层获取当前Request
HttpServletRequest是何时放入上下文中的
在spring的DispatchServlet
extends FrameworkServlet中有个方法
void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
...
initContextHolders(request, localeContext, requestAttributes);
}
其中initContextHolders(request, localeContext, requestAttributes)
;
private void initContextHolders(HttpServletRequest request,@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(
localeContext, this.threadContextInheritable
);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(
requestAttributes,
this.threadContextInheritable
);
}
}
将requestAttribute
放入了RequestContextHolder
的
ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
在Service层调用方法
在使用的时候就可以直接调用
RequestAttribute attribute = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRquest)attribute.getRequest();