网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
@ComponentScan(“com.javacode2018.springmvc.chat12”)
@EnableWebMvc //1:使用EnableWebMvc开启springmvc注解方式配置
public class MvcConfig implements WebMvcConfigurer {
/**
* 2、添加视图解析器(可以添加多个)
*
* @param registry
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(“/WEB-INF/view/”);
resolver.setSuffix(“.jsp”);
resolver.setOrder(Ordered.LOWEST_PRECEDENCE);
registry.viewResolver(resolver);
}
@Autowired
private MyInterceptor myInterceptor;
/**
* 3、添加拦截器(可以添加多个)
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.myInterceptor).addPathPatterns(“/**”);
}
/**
* 4、配置静态资源访问处理器
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(“/static/**”).addResourceLocations(“/static/”);
}
/**
* 5、配置文件上传解析器
*
* @return
*/
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
//maxUploadSizePerFile:单个文件大小限制(byte)
//maxUploadSize:整个请求大小限制(byte)
commonsMultipartResolver.setMaxUploadSizePerFile(10 * 1024 * 1024);
commonsMultipartResolver.setMaxUploadSize(100 * 1024 * 1024);
return commonsMultipartResolver;
}
}
### 2、SpringMVC 容器的生命周期(9 个阶段)
1. 阶段 1:Servlet 容器初始化
2. 阶段 2:创建父容器
3. 阶段 3:创建 springmvc 容器
4. 阶段 4:Servlet 容器中注册 DispatcherServlet
5. 阶段 5:启动父容器:ContextLoaderListener
6. 阶段 6:启动 springmvc 容器:DispatcherServlet#init()
7. 阶段 7:springmvc 容器启动过程中处理@WebMVC
8. 阶段 8:组装 DispatcherServlet 中各种 SpringMVC 需要的组件
9. 阶段 9:销毁 2 个容器
### 3、阶段 1:Servlet 容器初始化
#### 3.1、ServletContainerInitializer
咱们知道 servlet3.0 中新增了一个接口:`ServletContainerInitializer`,这个接口功能特别的牛逼,有了它之后,web.xml 配置文件可要可不要了。
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
这个接口的实现类,如果满足下面 2 个条件,Servlet 容器启动的过程中会自动实例化这些类,然后调用他们的 onStartUp 方法,然后我们就可以在这些类的 onStartUp 方法中干活了,在 web.xml 干的所有事情,都可以在这个方法中干,特别强大:
>
> 1. 这个类必须实现 ServletContainerInitializer 接口,且非抽象类
> 2. 这个类的全类名必须要放在`META-INF/services/javax.servlet.ServletContainerInitializer`这个文件中
>
>
>
#### 3.2、SpringServletContainerInitializer
下面重点来了,springmvc 提供了一个类`SpringServletContainerInitializer`,满足了上面个条件。
![b5e2cd12a8f5245e0fa85a716e6cde5c.png](https://img-blog.csdnimg.cn/img_convert/b5e2cd12a8f5245e0fa85a716e6cde5c.png)
spring-web-5.3.6.jar!\META-INF\services\javax.servlet.ServletContainerInitializer
![cfe152a75ec058aea76cf740a1bc4cd6.png](https://img-blog.csdnimg.cn/img_convert/cfe152a75ec058aea76cf740a1bc4cd6.png)
所以 SpringServletContainerInitializer 的 onStart 方法会 servlet 容器自动被调用
#### 3.3、SpringServletContainerInitializer#onStartup 方法
这个类的源码,大家先看一下,这个类干的事情:
>
> 1. 类上有@HandlesTypes(WebApplicationInitializer.class) 这个注解,注解的值为`WebApplicationInitializer.class`,所以 onStartup 方法的第一个参数是`WebApplicationInitializer`类型的集合,这个集合由 web 容器自动扫描获取,然后传入进来
> 2. 实例化 WebApplicationInitializer 集合
> 3. 对 WebApplicationInitializer 集合进行排序
> 4. 循环调用 WebApplicationInitializer 的 onStartup 方法
>
>
>
@HandlesTypes(WebApplicationInitializer.class) //@1
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says…
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException(“Failed to instantiate WebApplicationInitializer class”, ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log(“No Spring WebApplicationInitializer types detected on classpath”);
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
下面重点要看`WebApplicationInitializer`接口了。
#### 3.4、WebApplicationInitializer:web 应用初始化
接口比较简单,就一个方法,参数是 servlet 上下文对象,有了个对象,可以干 web.xml 中的一切事情了,比如注册 servlet、filter、监听器等等
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
如下图,看一下类的继承关系,咱们的 MvcInit 就实现了这个接口,所以 MvcInit 的 onStartup 方法会被调费用
![988e006c67479cb1e95d14084db6ed48.png](https://img-blog.csdnimg.cn/img_convert/988e006c67479cb1e95d14084db6ed48.png)
关键代码在这 3 个类中
![148177da348ac47b14dd4920661da108.png](https://img-blog.csdnimg.cn/img_convert/148177da348ac47b14dd4920661da108.png)
#### 3.5、进入 AbstractDispatcherServletInitializer#onStartup 方法
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
**这里是重点:这个方法中干了 4 件事情**
>
> 1. 创建父容器,只是实例化了,并未启动
> 2. 创建了监听器 ContextLoaderListener,这是一个 ServletContextListener 类型的监听器,稍后会在这个监听器中启动父容器
> 3. 创建 springmvc 容器,只是实例化了,并未启动,启动的事情会在 DispatcherServlet#init 中做,稍后会说
> 4. Servlet 容器中注册 DispatcherServlet
>
>
>
下面,咱们来详细看这几个步骤,把这几个步骤作为阶段来解读。
### 4、阶段 2:创建父容器
父容器可有可无,并不是必须的,为了更好的管理 bean,springmvc 建议我们用父子容器,controller 之外的 bean,比如 service,dao 等,建议放到父容器中,controller 层的和 springmvc 相关的一些 bean 放在 springmvc 容器中,咱们继续。
#### 4.1、过程
`AbstractDispatcherServletInitializer#onStartup`方法中会调用父类的`onStartup`,即`AbstractContextLoaderInitializer#onStartup`,我们进到这个方法中,代码如下图,干了 2 个事情
>
> 1. 图中编号 ①:创建父容器,只是实例化了,并未启动
> 2. 图中编号 ②:创建了一个监听器 ContextLoaderListener,这是一个 ServletContextListener 类型的监听器,稍后会在这个监听器中启动父容器
>
>
>
![6cd8e179cfead8df1cc9e86fb18933fb.png](https://img-blog.csdnimg.cn/img_convert/6cd8e179cfead8df1cc9e86fb18933fb.png)
下面来分别来细说下上面 2 段代码干的活。
#### 4.2、①:负责创建父容器
`AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext`,只是创建了一个`AnnotationConfigWebApplicationContext`对象,并将父容器配置类 rootConfigClass 注册到容器中,并没有启动这个容器,若 rootConfigClass 为空,父容器不会被创建,所以父容器可有可无。
![a4559bae2da16e6fd604f924643ff02d.png](https://img-blog.csdnimg.cn/img_convert/a4559bae2da16e6fd604f924643ff02d.png)
#### 4.2、②:创建 ContextLoaderListener 监听器
代码如下,创建的时候将父容器对象 rootAContext 传进去了。
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
//getRootApplicationContextInitializers()返回置为ApplicationContextInitializer数组,是个函数式接口,在父容器初始化的过程中,会作为一个扩展点预留给开发者用
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
ContextLoaderListener,这是一个 ServletContextListener 类型的监听器,所以在 web 容器启动和销毁的过程中会被调用,如下图,这个监听器干了 2 件事
>
> 1. contextInitialized 方法:这个方法会在 web 容器启动时被调用,内部负责启动父容器
> 2. 在 contextDestroyed 方法:这个方法会在 web 容器销毁时被调用,内部负责关闭父容器
>
>
>
![004085310864ba72bb537358a82a8654.png](https://img-blog.csdnimg.cn/img_convert/004085310864ba72bb537358a82a8654.png)
### 5、阶段 3&4:创建 springmvc 容器&注册 DispatcherServlet
在回到`AbstractDispatcherServletInitializer#onStartup`,看这个方法的第二行,如下图
![b8b587130bc3c679757e68d88d73555f.png](https://img-blog.csdnimg.cn/img_convert/b8b587130bc3c679757e68d88d73555f.png)
`registerDispatcherServlet`源码如下
protected void registerDispatcherServlet(ServletContext servletContext) {
//①:DispatcherServlet的servlet名称,默认为:dispatcher
String servletName = getServletName();
//②:创建springmvc容器
WebApplicationContext servletAppContext = createServletApplicationContext();
//③:创建DispatcherServlet,注意这里将springmvc容器对象做为参数传递给DispatcherServlet了
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
//设置ApplicationContextInitializer列表,可以对springmvc容器在启动之前进行定制化
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//④:将 dispatcherServlet 注册到servlet上下文中
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
//⑤:注册Filter
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
//⑥:这个方法预留给咱们自己去实现,可以对dispatcherServlet做一些特殊的配置
customizeRegistration(registration);
}
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
### 6、阶段 5:启动父容器:ContextLoaderListener
#### 6.1、过程
上面的`onStartup`方法执行完毕之后,会执行监听器`ContextLoaderListener`的初始化,会进入到他的`contextInitialized`方法中
![3edcdc262245d9f0b9ea91ab6c5e5905.png](https://img-blog.csdnimg.cn/img_convert/3edcdc262245d9f0b9ea91ab6c5e5905.png)
`initWebApplicationContext`源码如下,截取了主要的几行
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//this.context就是父容器对象
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//①:配置及启动父容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
//将父容器丢到servletContext中进行共享,方便其他地方获取
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
}
#### 6.2、代码 ①:配置父容器以及启动父容器
//①:配置及启动父容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
`configureAndRefreshWebApplicationContext`方法关键代码如下
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//①:定制上线文,这里主要是遍历ApplicationContextInitializer列表,调用每个ApplicationContextInitializer#initialize方法来对容器进行定制,相当于一个扩展点,可以有程序员自己控制
customizeContext(sc, wac);
//②:刷新容器,就相当于启动容器了,此时就会组装里面的bean了
wac.refresh();
}
`customizeContext`方法,我们进去看一下,这里涉及到了一个新的类,所以有必要去看一下,混个脸熟,源码如下,这是给开发者留的一个扩展点,通过`ApplicationContextInitializer`这个来做扩展,这是一个函数式接口,下面代码会遍历`ApplicationContextInitializer`列表,然后调用其`initialize`方法,我们可以在这个方法中对 spring 上线文进行定制
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
List<Class<ApplicationContextInitializer>> initializerClasses =
determineContextInitializerClasses(sc);
for (Class<ApplicationContextInitializer> initializerClass : initializerClasses) {
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
throw new ApplicationContextException(String.format(
"Could not apply context initializer [%s] since its generic parameter [%s] " +
"is not assignable from the type of application context used by this " +
“context loader: [%s]”, initializerClass.getName(), initializerContextClass.getName(),
wac.getClass().getName()));
}
this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
}
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
#### 6.3、ApplicationContextInitializer 接口:容器启动前用来初始化容器
是个函数式接口,在容器启动之前用来对容器进行定制,作为一个扩展点预留给开发者用,父容器和 springmvc 容器都用到了。
@FunctionalInterface
public interface ApplicationContextInitializer {
/**
* 初始化给定的spring容器
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
### 7、阶段 6:启动 springmvc 容器:DispatcherServlet#init()
到目前为止父容器已经启动完毕了,此时 DispatcherServlet 会被初始化,会进入到他的 init()方法中。
#### 7.1、DispatcherServlet 类图
![991cdc1240f1b20ed3754b09ec5d4982.png](https://img-blog.csdnimg.cn/img_convert/991cdc1240f1b20ed3754b09ec5d4982.png)
#### 7.2、HttpServletBean#init()
>
> 这个方法会调用`initServletBean()`这个方法,其他的先不看
>
>
>
![ae38e03b19ca2a1a1fbb2ebca5c2cc83.png](https://img-blog.csdnimg.cn/img_convert/ae38e03b19ca2a1a1fbb2ebca5c2cc83.png)
#### 7.3、FrameworkServlet#initServletBean
>
> 提取了关键的代码,就 2 行
>
>
>
@Override
protected final void initServletBean() throws ServletException {
//初始化springmvc容器,就是启动springmvc容器
this.webApplicationContext = initWebApplicationContext();
//这个方法内部是空的,预留给子类去实现的,目前没啥用
initFrameworkServlet();
}
下面咱们进到`initWebApplicationContext`方法中去。
#### 7.4、FrameworkServlet#initWebApplicationContext
>
> 关键代码如下,干了 3 件事情:
>
>
> 1. 从 servlet 上线文对象中找到父容器
> 2. 为 springmvc 容器指定父容器
> 3. 调用 configureAndRefreshWebApplicationContext 方法配置 springmvc 容器以及启动容器,这个是关键咯
>
>
>
protected WebApplicationContext initWebApplicationContext() {
//①:从servlet上线文中获取父容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//②:this.webApplicationContext就是springmvc容器,此时这个对对象不为null,所以满足条件
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//springmvc容器未启动
if (!cwac.isActive()) {
//springmvc容器未设置父容器,则给其设置父容器,此时rootContext可能为空,这个没什么关系
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
//③:配置springmvc容器以及启动springmvc容器
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//这里省略了一部分代码,如果springmvc采用配置文件的方式会走这部分代码
…
//返回容器
return wac;
}
#### 7.5、FrameworkServlet#configureAndRefreshWebApplicationContext
>
> 为了让大家看清楚,如下代码,这里只提取了关键代码,主要干了 3 件事情
>
>
> 1. 代码 ①:向 springmvc 容器中添加了一个 ContextRefreshListener 监听器,这个监听器非常非常重要,springmvc 容器启动完毕之后会被调用,**会出现在阶段 8 中**
> 2. 代码 ②:给开发者预留的一个扩展点,通过 ApplicationContextInitializer#initialize 方法对容器进行定制
> 3. 代码 ③:启动容器
>
>
>
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//①:向springmvc容器中添加了一个监听器对象,这个监听器特别重要,稍后在
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//②:扩展点:循环遍历ApplicationContextInitializer列表,调用其initialize方法对容器进行定制
applyInitializers(wac);
//③:刷新容器,相当于启动容器
wac.refresh();
}
### 8、阶段 7:springmvc 容器启动过程中处理@WebMVC
#### 8.1、SpringMVC 配置类被处理
此时 springmvc 容器启动了,此时注意下`MvcConfig`这个类,由于其上有@Conguration 注解,所以会被当做一个配置类被处理,这个类有 2 个非常重要的特征。
>
> 1. 标注了@EnableWebMvc 注解
> 2. 实现了 WebMvcConfigurer 接口
>
>
>
![d9d007d7cb888ef1d4edc56b7a05c930.png](https://img-blog.csdnimg.cn/img_convert/d9d007d7cb888ef1d4edc56b7a05c930.png)
下面来说说这 2 个特征的作用。
#### 8.2、@EnableWebMvc:配置 springmvc 所需组件
看一下这个注解的源码,如下,重点在于它上面的`@Import(DelegatingWebMvcConfiguration.class)`注解,这个注解的功能不知道的,可以回头去看我的 spring 系列,从头看一遍。
![e6caffc1d224aef39a768f4624a88167.png](https://img-blog.csdnimg.cn/img_convert/e6caffc1d224aef39a768f4624a88167.png)
#### 8.3、进入 DelegatingWebMvcConfiguration 类
代码如下,先注意下面 3 个特征
>
> 1. 代码编号 ①:标注有@Configuration 注解,说明是一个配置类
> 2. 代码编号 ②:继承了 WebMvcConfigurationSupport 类,这个类中有很多@Bean 标注的方法,用来定义了 springmvc 需要的所有组件
> 3. 代码编号 ③:注入了`WebMvcConfigurer`列表,注意下,我们的 WebConfig 类就实现了 WebMvcConfigurer 这个接口,内部提供了很多方法可以用来对 springmvc 的组件进行自定义配置
>
>
>
![b703184841f78d5e3047edd783e155c5.png](https://img-blog.csdnimg.cn/img_convert/b703184841f78d5e3047edd783e155c5.png)
先来看看 WebMvcConfigurationSupport 这个类。
#### 8.4、WebMvcConfigurationSupport:配置 springmvc 所需所有组件
这个类中会定义 springmvc 需要的所有组件,比如:RequestMapping、HandlerAdapter、HandlerInterceptor、HttpMessageConverter、HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler 等等,所以如果你感觉@WebMVC 注解满足不了你的需求时,你可以直接继承这个类进行扩展。
这个类的源码我就不贴了,截几个图给大家看看
![d326312623c5736feea27dea3e7debfd.png](https://img-blog.csdnimg.cn/img_convert/d326312623c5736feea27dea3e7debfd.png)
#### 8.5、WebMvcConfigurer 接口
这个接口就是我们用来对 springmvc 容器中的组件进行定制的,WebMvcConfigurationSupport 中创建 springmvc 组件的时候,会自动调用 WebMvcConfigurer 中对应的一些方法,来对组件进行定制,比如可以在 WebMvcConfigurer 中添加拦截器、配置默认 servlet 处理器、静态资源处理器等等,这个接口的源码如下
public interface WebMvcConfigurer {
/**
* 配置PathMatchConfigurer
*/
default void configurePathMatch(PathMatchConfigurer configurer) {
}
/**
* 配置ContentNegotiationConfigurer
*/
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
/**
* 异步处理配置
*/
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
/**
* 配置默认servlet处理器
*/
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
/**
* 配置Formatter
*/
default void addFormatters(FormatterRegistry registry) {
}
/**
* 添加拦截器
*/
default void addInterceptors(InterceptorRegistry registry) {
}
/**
* 静态资源配置
*/
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* 跨越的配置
*/
default void addCorsMappings(CorsRegistry registry) {
}
/**
* 配置ViewController
*/
default void addViewControllers(ViewControllerRegistry registry) {
}
/**
* 注册视图解析器(ViewResolverRegistry)
*/
default void configureViewResolvers(ViewResolverRegistry registry) {
}
/**
* 注册处理器方法参数解析器(HandlerMethodArgumentResolver)
*/
default void addArgumentResolvers(List resolvers) {
}
/**
* 注册处理器方法返回值处理器(HandlerMethodReturnValueHandler)
*/
default void addReturnValueHandlers(List handlers) {
}
/**
* 注册http报文转换器(HttpMessageConverter)
*/
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* 扩展报文转换器
*/
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* 配置异常解析器(HandlerExceptionResolver)
*/
default void configureHandlerExceptionResolvers(List resolvers) {
}
/**
* 扩展异常解析器(HandlerExceptionResolver)
*/
default void extendHandlerExceptionResolvers(List resolvers) {
}
/**
* 获得验证器
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
s) {
}
/**
* 扩展报文转换器
*/
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* 配置异常解析器(HandlerExceptionResolver)
*/
default void configureHandlerExceptionResolvers(List resolvers) {
}
/**
* 扩展异常解析器(HandlerExceptionResolver)
*/
default void extendHandlerExceptionResolvers(List resolvers) {
}
/**
* 获得验证器
[外链图片转存中…(img-rJePBoF3-1715553819831)]
[外链图片转存中…(img-TsleWWRQ-1715553819832)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!