SpringBoot整合springmvc
文章目录
- 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自动配置了消息转换器
源码部分
@Override
public 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) {
}
@Nullable
default Validator getValidator() {
return null;
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
2.2在容器中注册视图控制器(请求转发)
创建一个MyMVCCofnig实现WebMvcConfigurer接口,实现一下addViewControllers方法,我们完成通过/tx访问,转发到success.html的工作
@Configuration
public class MyMVCCofnig implements WebMvcConfigurer{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/tx").setViewName("success");
}
}
2.3注册格式化器
用来可以对请求过来的日期格式化的字符串来做定制化。当然通过application.properties配置也可以办到。
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new Formatter<Date>() {
@Override
public String print(Date date, Locale locale) {
// 将 Date 对象转换为字符串(格式化)
// 使用 SimpleDateFormat 将 Date 转换为指定格式的字符串
return new SimpleDateFormat("yyyy-MM-dd").format(date);
}
@Override
public 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("最终拦截");
}
}
拦截器注册
//配置注册自己的拦截器
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new MyInterceptor())
//配置拦截所有路径
.addPathPatterns("/**")
//配置不拦截路径 可变参数 可以使多个参数 数组
.excludePathPatterns("/test2");
}