文章目录
- 1.SpringMVC的自动管理
- 1.1中央转发器
- 1.1.1Spring boot配置多个DispatcherServlet
- 1.2控制器
- 1.2.1找到启动类的位置
- 1.2.1.1SpringApplication.run()
- 1.2.1.2SpringApplication 构造方法
- 1.2.1.3deduceMainApplicationClass()
- 1.2.2@ComponentScan 注解
- 1.3视图解析器自动管理
- 1.4静态资源访问
- 1.5消息转换和格式化
- 1.6欢迎页面的自动配置
- 2.SpringBoot扩展springmvc
- 2.1WebMvcConfigurer 接口
- 2.2在容器中注册视图控制器(请求转发)
- 2.3注册格式化器
- 2.4消息转换器扩展fastjson
- 2.5拦截器注册
1.SpringMVC的自动管理
中央转发器(DispatcherServlet)
控制器
视图解析器
静态资源访问
消息转换器
格式化
静态资源管理
1.1中央转发器
在之前的SSM项目中,中央转发器需要在.xml中配置
<servlet><servlet-name>chapter2</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><servlet-name>chapter2</servlet-name><url-pattern>/</url-pattern>
</servlet-mapping>
使用SpringBoot后,中央转发器被SpringBoot自动接管,不需要在web.xml中配置
1.1.1Spring boot配置多个DispatcherServlet
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
中有个DispatcherServletRegistrationBean
这里只能指定一个path
源码如下:
如果想指定多个DispatcherServlet,我们需要自己写DispatcherServletRegistrationBean这个Bean
示例代码如下:
@Autowired
private WebMvcProperties webMvcProperties;
@Autowired
private MultipartConfigElement multipartConfig;@Bean
@Primary
public DispatcherServletRegistrationBean dispatcherServlet1(DispatcherServlet dispatcherServlet) {DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, "/*");registration.setName("dispatcherServlet1");registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());if (this.multipartConfig != null) {registration.setMultipartConfig(this.multipartConfig);}return registration;
}@Bean
public DispatcherServletRegistrationBean dispatcherServlet2(DispatcherServlet dispatcherServlet) {DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, "/aaa/*");registration.setName("dispatcherServlet2");registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());if (this.multipartConfig != null) {registration.setMultipartConfig(this.multipartConfig);}return registration;
}@Bean
public DispatcherServletRegistrationBean dispatcherServlet3(DispatcherServlet dispatcherServlet) {DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, "/bbb/*");registration.setName("dispatcherServlet3");registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());if (this.multipartConfig != null) {registration.setMultipartConfig(this.multipartConfig);}return registration;
}
这里是做了三个Bean,注意有一个一定要加上@Primary注解,否则启动会有报错。
如果我们系统有一个接口url是/api/test,那么通过/aaa/api/test或者/bbb/api/test也都可以访问了。
1.2控制器
控制器Controller在springboot的注解扫描范围内自动管理。底层逻辑为java反射。
Spring Boot 的 @ComponentScan
默认会扫描启动类所在包及其子包。这是通过 @SpringBootApplication
中的 @ComponentScan
实现的。因此,启动类的位置对组件扫描范围至关重要。
1.2.1找到启动类的位置
1.2.1.1SpringApplication.run()
Spring Boot 启动器的定位通过,SpringBoot的入口类SpringApplication.run()
方法触发。
SpringApplication.run()
是启动流程的入口,其源码如下:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args);}
这里的 primarySource
就是启动类(启动器)。
primarySource
的来源: 通过调用SpringApplication
的构造方法传入。
1.2.1.2SpringApplication 构造方法
public SpringApplication(Class<?>... primarySources) {this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));this.webApplicationType = deduceWebApplicationType();setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();
}
this.primarySources
:存储传入的启动类。
deduceMainApplicationClass()
:尝试自动推断启动类。
1.2.1.3deduceMainApplicationClass()
private Class<?> deduceMainApplicationClass() {try {StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();for (StackTraceElement stackTraceElement : stackTrace) {if ("main".equals(stackTraceElement.getMethodName())) {return Class.forName(stackTraceElement.getClassName());}}} catch (ClassNotFoundException ex) {// 忽略异常}return null;
}
- 通过分析当前线程的调用堆栈,找到调用
main
方法的类。 - 找到第一个包含
main
方法的类并返回。
这就是为什么 Spring Boot 能自动定位启动类的位置。
1.2.2@ComponentScan 注解
@ComponentScan
是 Spring 框架用于自动扫描和注册组件的注解。@SpringBootApplication
中自带了 @ComponentScan
,默认会扫描启动类所在的包及其子包。
通过excludeFilters
来过滤掉,到底应该扫描哪些包
1.3视图解析器自动管理
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
ContentNegotiatingViewResolver:组合所有的视图解析器的;
曾经的配置文件无需再配
<bean id="de" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/jsp/"></property><property name="suffix" value="*.jsp"></property>
</bean>
源码:
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class));resolver.setOrder(-2147483648);return resolver;
}
用不同的模板引擎会有不同的自动配置
Thymeleaf 示例
如果你的视图文件路径或者文件名不同,可以通过配置文件来修改。
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
当我们做文件上传的时候我们也会发现是multipartResolver
自动被配置好的
<form action="/upload" method="post" enctype="multipart/form-data"><input name="pic" type="file"><input type="submit">
</form>
@ResponseBody
@RequestMapping("/upload")
public String upload(@RequestParam("pic")MultipartFile file, HttpServletRequest request){String contentType = file.getContentType();String fileName = file.getOriginalFilename();/*System.out.println("fileName-->" + fileName);System.out.println("getContentType-->" + contentType);*///String filePath = request.getSession().getServletContext().getRealPath("imgupload/");String filePath = "D:/imgup/";try {this.uploadFile(file.getBytes(), filePath, fileName);} catch (Exception e) {// TODO: handle exception}return "success";
}public static void uploadFile(byte[] file, String filePath, String fileName) throws Exception {File targetFile = new File(filePath);if(!targetFile.exists()){targetFile.mkdirs();}FileOutputStream out = new FileOutputStream(filePath+fileName);out.write(file);out.flush();out.close();
}
文件上传大小可以通过配置来修改
打开application.properties, 默认限制是10MB,我们可以任意修改
1.4静态资源访问
Spring Boot 会默认将以下路径作为静态资源的目录:
src/main/resources/static
src/main/resources/public
src/main/resources/resources
src/main/resources/META-INF/resources
将静态文件放在这些目录中,可以通过 HTTP 直接访问它们。
1.5消息转换和格式化
Springboot自动配置了消息转换器
源码部分
@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {this.messageConvertersProvider.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));}
格式化转换器的自动注册
我们可以在配置文件中修改时间类型
1.6欢迎页面的自动配置
Springboot自动指定resources下的index.html
Spring Boot 会自动配置静态资源的路径,默认情况下,resources/static
、resources/public
、resources/resources
和 resources/META-INF/resources
等目录下的资源会被自动映射到 HTTP 请求的根路径 /
。
2.SpringBoot扩展springmvc
在实际开发中,Spring Boot 确实提供了很多自动配置功能,帮助我们简化了很多配置和开发工作。但由于每个项目的业务需求不同,Spring Boot 并不能覆盖所有的场景,因此,开发者常常需要根据具体的业务需求对其进行扩展。
2.1WebMvcConfigurer 接口
public interface WebMvcConfigurer {default void configurePathMatch(PathMatchConfigurer configurer) {}default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}default void addFormatters(FormatterRegistry registry) {}default void addInterceptors(InterceptorRegistry registry) {}default void addResourceHandlers(ResourceHandlerRegistry registry) {}default void addCorsMappings(CorsRegistry registry) {}default void addViewControllers(ViewControllerRegistry registry) {}default void configureViewResolvers(ViewResolverRegistry registry) {}default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}@Nullabledefault Validator getValidator() {return null;}@Nullabledefault MessageCodesResolver getMessageCodesResolver() {return null;}
}
2.2在容器中注册视图控制器(请求转发)
创建一个MyMVCCofnig实现WebMvcConfigurer接口,实现一下addViewControllers方法,我们完成通过/tx访问,转发到success.html的工作
@Configuration
public class MyMVCCofnig implements WebMvcConfigurer{@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/tx").setViewName("success");}
}
2.3注册格式化器
用来可以对请求过来的日期格式化的字符串来做定制化。当然通过application.properties配置也可以办到。
@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addFormatter(new Formatter<Date>() {@Overridepublic String print(Date date, Locale locale) {// 将 Date 对象转换为字符串(格式化)// 使用 SimpleDateFormat 将 Date 转换为指定格式的字符串return new SimpleDateFormat("yyyy-MM-dd").format(date);}@Overridepublic Date parse(String s, Locale locale) throws ParseException {// 将字符串解析为 Date 对象// 假设日期格式是 "yyyy-MM-dd"return new SimpleDateFormat("yyyy-MM-dd").parse(s);}});}
2.4消息转换器扩展fastjson
在pom.xml中引入fastjson
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version>
</dependency>
配置消息转换器,添加fastjson
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {FastJsonHttpMessageConverter fc = new FastJsonHttpMessageConverter();FastJsonConfig fastJsonConfig = new FastJsonConfig();fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);fc.setFastJsonConfig(fastJsonConfig);converters.add(fc);
}
public class User {private String username;private String password;private int age;private int score;private int gender;@JSONField(format = "yyyy-MM-dd")private Date date;
2.5拦截器注册
- 创建拦截器
public class MyInterceptor implements HandlerInterceptor{public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {System.out.println("前置拦截");return true;}public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("后置拦截");}public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) throws Exception {System.out.println("最终拦截");}
}
拦截器注册
//配置注册自己的拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(new MyInterceptor())//配置拦截所有路径.addPathPatterns("/**")//配置不拦截路径 可变参数 可以使多个参数 数组.excludePathPatterns("/test2");}