搭建原理
springMVC版本
配置内嵌tomcat
- 为了简化开发,使用tomcat插件实现web项目的运行,只需要在pom.xml中配置一个插件即可,如下:
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
</configuration>
</plugin>
</plugins>
- 之后在IDEA右侧的maven处可以看见tomcat7这个插件了,点击run即可运行
配置DispatcherServlet初始化器
- 配置的方式有多种,但是根据Spring文档推荐的方式如下:
import cn.tedu.demo.config.AppConfig;
import cn.tedu.demo.config.WebMvcConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* 配置DispatcherServlet初始化器,在容器启动的时候会加载初始化
* 入口就是/org/springframework/spring-web/5.1.8.RELEASE/spring-web-5.1.8.RELEASE.jar!/META-INF/services/javax.servlet.ServletContainerInitializer
* web容器在启动的时候会加载META-INF/service下的文件
*/
public class StrartWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 配置主配置类,主配置类的作用就是配置业务所需要的各种Bean,比如dao,service
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{AppConfig.class};
}
/**
* 配置MVC所需的配置类,该配置类的作用就是扫描controller,配置mvc的各种组件,比如视图解析器,拦截器等
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebMvcConfig.class};
}
/**
* 配置servletMapping,相当于在DispatcherServlet中配置的url
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
主配置文件
- 主配置文件主要的作用就是配置业务需求的Bean,比如dao,service层的
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
/**
* 业务逻辑的配置类,扫描所有的业务Bean,比如dao,service,排除所有的controller
*/
@Configuration
@ComponentScan(basePackages = {"cn.tedu.demo"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})})
public class AppConfig {
}
MVC配置类
- MVC配置类主要的作用就是扫描Controller,配置各种组件,比如视图解析器,拦截器等等
- 重要的两点如下:
- 使用
@EnableWebMvc
注解开启MVC功能,相当于xml文件中的<mvc:annotation-driven/>
- 配置类需要实现
WebMvcConfigurer
,该接口下有各种方法,开发者可以实现其中的方法完成相关组件的生成
import cn.tedu.demo.interceptor.CustomInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.*;
/**
* MVC的配置类,扫描所有的controller,排除所有的业务类
* @EnableWebMvc 注解开启mvc功能
* @ComponentScan 注解中的属性useDefaultFilters(默认是true,扫描全部的Bean),这里我们定义了只扫描controller,因此要设置该属性为false,否则不起作用,排除Bean则不需要
*/
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = {"cn.tedu.demo"},includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})},useDefaultFilters = false)
public class WebMvcConfig implements WebMvcConfigurer {
}
配置拦截器
/**
* 自定义一个拦截器,实现HandlerInterceptor
*/
@Component
public class CustomInterceptor implements HandlerInterceptor {
/**
* 在拦截器方法之前执行
* @param request request
* @param response response
* @param handler 拦截的handler
* @return 如果返回false,后续的拦截器和拦截的handler不执行
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("在之前执行");
return true;
}
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//创建
CustomInterceptor customInterceptor = new CustomInterceptor();
//添加自定义的拦截器
registry.addInterceptor(customInterceptor).addPathPatterns("/**");
}
- 自定义的拦截器的真实实现类其实是
MappedInterceptor
,在源码中获取处理器执行链的时候会将其添加到执行链中。
配置过滤器
- 过滤器不属于SpringMVC,而是属于Servlet中的组件,因此配置过滤器使用的并不是MVC的配置,但是在Servlet3.0中也是提供了注解版的Servlet和Filter的生成方式,我们使用注解生成一个Filter,如下:
/**
* 自定义过滤器
*/
@WebFilter(filterName = "customFilter",urlPatterns = "/*")
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("过滤器执行");
chain.doFilter(request,response);
}
@Override
public void destroy() {
System.out.println("过滤器销毁");
}
}
配置视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB_INF/",".jsp");
}
配置ViewController
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//定义一个controller,访问路径是/index.do,跳转的视图是index.jsp
registry.addViewController("/index.do").setViewName("index");
}
配置MessageConverters
- 消息转换器用于对Request和Response的消息进行处理,比如将Response中的消息转换为指定JSON字符串的形式
- 默认的消息转换器对于日期的类型的转换是时间戳,即是返回的JSON字符串的日期类型是时间戳,接收的日期类型参数也只能是时间戳
- 如何配置消息转换器,只需要重写springmvc配置类中的方法即可。
- 我们使用的是
MappingJackson2HttpMessageConverter
这类转换器,但是其中依赖的是ObjectMapper,因此我们比如引入依赖,如下:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
- 在上述的MVC配置类中重写如下方法:
- 设置日期的格式化格式是yyyy-MM-dd,此时返回和接收的格式就是
yyyy-MM-dd
- 在配置类中配置的消息转换器属于全局配置,所有的消息都会遵循这种配置。
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
//指定格式化的日期,这里只是举例,不建议在此处全局配置
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
//设置时区,默认是UTC,需要修改成北京时间
.timeZone("GMT+8");
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
注解版
- 在实际的项目中这种方式太鸡肋,实际的需求有实际的变化,因此我们最好能够寻找一种灵活的处理方式,类似注解的方式。
- 在
jackson-databind
中提供了许多的注解,可以供我们使用,可以覆盖全局配置,和全局配置形成一种互补的作用。 @JsonFormat
:日期格式化注解,如下:
//timeZone如果在全局配置过,可以不写
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date birthDay;
@JsonIgnore
:在返回的JSON字符串中不显示
@JsonIgnore
private String name;
- 其他的注解请参考https://blog.51cto.com/7308310/2310930?source=dra
异常处理器
- springMvc处理异常有三种方式,分别为:
ExceptionHandlerExceptionResolver
:通过调用或 类中的@ExceptionHandler
方法来解决异常,可以结合@ControllerAdvice
DefaultHandlerExceptionResolver
:对一些特殊的异常进行处理ResponseStatusExceptionResolver
:使用@ResponseStatus
解析异常,并根据注解中的值将它们映射到HTTP状态代码 SimpleMappingExceptionResolver
:异常和视图的映射,可以自定义指定的异常对应的视图
- 原理:主要的解析逻辑都是在
doResolveException
方法中完成的。
异常处理器执行的顺序
- 异常处理器的执行是有顺序的,优先级高的执行完之后,如果有对应的处理,那么后续的就不再执行。
- 异常处理器的执行顺序如下:
ExceptionHandlerExceptionResolver
DefaultHandlerExceptionResolver
ResponseStatusExceptionResolver
SimpleMappingExceptionResolver
- 四种异常处理器的顺序执行可以形成一种互补的配置。
SimpleMappingExceptionResolver
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
//设置默认的视图,如果有的异常没有指定处理,那么使用默认的视图
resolver.setDefaultErrorView("index");
//设置排除的异常
// resolver.setExcludedExceptions();
//指定异常视图映射
Properties properties=new Properties();
properties.put(RuntimeException.class.getName(),"error");
resolver.setExceptionMappings(properties);
return resolver;
}
DefaultHandlerExceptionResolver
- 此类异常解析器只能针对一些特殊的异常进行处理,如下:
ExceptionHTTP Status CodeHttpRequestMethodNotSupportedException405 (SC_METHOD_NOT_ALLOWED)HttpMediaTypeNotSupportedException415 (SC_UNSUPPORTED_MEDIA_TYPE)HttpMediaTypeNotAcceptableException406 (SC_NOT_ACCEPTABLE)MissingPathVariableException500 (SC_INTERNAL_SERVER_ERROR)MissingServletRequestParameterException400 (SC_BAD_REQUEST)ServletRequestBindingException400 (SC_BAD_REQUEST)ConversionNotSupportedException500 (SC_INTERNAL_SERVER_ERROR)TypeMismatchException400 (SC_BAD_REQUEST)HttpMessageNotReadableException400 (SC_BAD_REQUEST)HttpMessageNotWritableException500 (SC_INTERNAL_SERVER_ERROR)MethodArgumentNotValidException400 (SC_BAD_REQUEST)MissingServletRequestPartException400 (SC_BAD_REQUEST)BindException400 (SC_BAD_REQUEST)NoHandlerFoundException404 (SC_NOT_FOUND)AsyncRequestTimeoutException503 (SC_SERVICE_UNAVAILABLE)
ResponseStatusExceptionResolver
- 在自定义的异常类上标注
@ResponseStatus
注解,当抛出此种异常的时候,将会响应定义的状态码和提示语
@ResponseStatus(code = HttpStatus.FORBIDDEN,reason = "没有权限")
public class CustomException extends RuntimeException {
}
ExceptionHandlerExceptionResolver
- 集合
@ControllerAdvice
和@RestControllerAdvice
使用 - 方法中能够自动赋值的参数和返回值的类型都在Spring文档上有详细的记载,参考
https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-ann-exceptionhandler-args
- 详细的使用如下:
@ControllerAdvice
public class ExceptionController {
/**
*
*处理FileNotFoundException,返回JSOn数据
*/
@ExceptionHandler(value = ArrayIndexOutOfBoundsException.class)
@ResponseBody
public Object handleFileNotFoundException(Exception ex, HttpServletRequest request, HandlerMethod method){
System.out.println(request.getRequestURI());
System.out.println(method);
System.out.println(ex);
return "index";
}
@ExceptionHandler(value = Exception.class)
public Object handleException(Exception ex, HttpServletRequest request, HandlerMethod method){
System.out.println(request.getRequestURI());
System.out.println(method);
System.out.println(ex);
return "index";
}
}
配置跨域请求
使用注解
- 使用注解
@CrossOrigin
,可以标注在Controller上,也可以标注在方法上,如下:
@CrossOrigin
@PostMapping("/getObj")
public Object getObject(@RequestBody AdminReq req){
System.out.println(req);
return new Admin("陈加兵",22,new Date(),new Date());
}
- 该注解中可以配置各种属性,这里不再细讲,在下面的全局配置中会涉及到。
全局配置
- 全局配置就是在MVC的配置文件中重写方法即可,如下:
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
//允许的源
.allowedOrigins("https://domain2.com")
//允许请求跨域的请求类型
.allowedMethods("PUT", "DELETE")
//允许的请求头
.allowedHeaders("header1", "header2", "header3")
//暴露的请求头
.exposedHeaders("header1", "header2")
//允许携带cookie等用户信息,这样才能实现登录
.allowCredentials(true).maxAge(3600);
}
配置静态资源解析
- springmvc中的DispatcherServlet如果设置了拦截的请求是
/
,那么也会拦截静态资源,但是我们可以在配置文件中配置,如下:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//拦截的请求
registry.addResourceHandler("/resources/**")
//资源的位置
.addResourceLocations("/public", "classpath:/static/")
//缓存的时间,单位秒
.setCachePeriod(31556926);
}
- 该配置会在ioc中注册一个
ResourceHttpRequestHandler
,封装在SimpleUrlHandlermapping中。
高级配置
@EnableMvc
注解其实就是注入了一个配置类DelegatingWebMvcConfiguration
,那么我们可以将自定义的配置类实现该类即可完成MVC的高级功能,此时就不需要使用该注解了,如下:
@Configuration
@ComponentScan(basePackages = {"cn.tedu.demo"},includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Component.class})},useDefaultFilters = false)
public class AdvanceConfig extends DelegatingWebMvcConfiguration {
}