"/>
侧边栏壁纸
博主头像
PySuper 博主等级

千里之行,始于足下

  • 累计撰写 286 篇文章
  • 累计创建 17 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Java Spring Boot 二

PySuper
2023-02-25 / 0 评论 / 0 点赞 / 6 阅读 / 0 字
温馨提示:
所有牛逼的人都有一段苦逼的岁月。 但是你只要像SB一样去坚持,终将牛逼!!! ✊✊✊

详细内容可参考官方文档: Spring Boot 官网open in new window
我们分小节研究其相关特性

一、手写模拟 SpringBoot 核心流程

概要

参考:手写模拟 SpringBoot 核心流程open in new window

示例代码见 https://gitee.com/shj.com/sample-projects/tree/master/back/sample-sim-src-code/sample-sim-spring-boot-demo

目前示例代码实现了以下核心流程

  • 手写模拟SpringBoot启动过程

  • 手写模拟SpringBoot条件注解功能

  • 手写模拟SpringBoot自动配置功能

1、maven 依赖导入

导入 spring、servlet、tomcat 等相关依赖

直接见示例 pom.xml 文件

2、创建应用启动入口

即创建 SimSpringApplication

public class SimSpringApplication {

    /**
     * 在run方法中,我们要实现的逻辑如下:
     * 1.创建一个Spring容器
     * 2.创建容器对象,如 Tomcat
     * 3.生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
     * 4.将DispatcherServlet添加到Tomcat中
     * 5.启动容器
     * @param clazz
     */
    public static void run(Class clazz) {
        // 1. 创建 Spring 容器
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(clazz);
        applicationContext.refresh();

        // 2. 启动容器
        WebServer webServer = getWebServer(applicationContext);
        webServer.start(applicationContext);
    }

    public static WebServer getWebServer(AnnotationConfigWebApplicationContext applicationContext) {
        // key为beanName, value为Bean对象
        Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);

        if (webServers.isEmpty()) {
            throw new NullPointerException();
        }
        if (webServers.size() > 1) {
            throw new IllegalStateException();
        }

        // 返回唯一的一个
        return webServers.values().stream().findFirst().get();
    }

}

3、创建核心注解和核心类

创建 SimSpringBootApplication 注解

该注解作用是 applicationContext.register(clazz); 注册时,扫描到了 @ComponentScan 注解,复用 Spring 的扫描逻辑加载 bean 到容器中

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(SimImportSelect.class)
public @interface SimSpringBootApplication {

}

4、启动 Tomcat 逻辑实现

首先抽象个 Server 类,方便动态替换容器

然后定义对应的 TomcatWebServer 和 JettyWebServer

public interface WebServer {
    void start(WebApplicationContext applicationContext);
}

public class TomcatWebServer implements WebServer{
    @Override
    public void start(WebApplicationContext applicationContext) {
        System.out.println("启动Tomcat web server");
        //此处复用上方启动tomcat代码
        startTomcat(applicationContext);
    }

    // 启动 tomcat
    public static void startTomcat(WebApplicationContext applicationContext) {
        Tomcat tomcat = new Tomcat();
        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");
        Connector connector = new Connector();
        connector.setPort(8081);
        Engine engine = new StandardEngine();
        engine.setDefaultHost("localhost");
        Host host = new StandardHost();
        host.setName("localhost");
        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());
        host.addChild(context);
        engine.addChild(host);
        service.setContainer(engine);
        service.addConnector(connector);
        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

public class JettyWebServer implements WebServer {
    @Override
    public void start(WebApplicationContext applicationContext) {
        System.out.println("启动Jetty Web Server");
    }
}

然后模拟实现条件注入

即模拟实现条件注入主键及其自定义匹配逻辑

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(SimOnClassCondition.class)
public @interface SimConditionOnClass {

    String value() default "";

}

public class SimOnClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(SimConditionOnClass.class.getName());
        String className = (String) attributes.get("value");
        try{
            context.getClassLoader().loadClass(className);
            return true;
        }catch (ClassNotFoundException ex){
            return false;
        }
    }
}

/**
 * 模拟实现条件注入 
 */
public class WebServiceAutoConfiguration implements AutoConfiguration {

    @Bean
    @SimConditionOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }

    @Bean
    @SimConditionOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer(){
        return new JettyWebServer();
    }

}

5、发现自动配置类

使用 Spring SPI 发现自动配置类

META-INF/services 下新增 com.felix.sim.spring.boot.config.AutoConfiguration 文件,文件内容如下

com.felix.sim.spring.boot.config.WebServiceAutoConfiguration

再使用 Import 导入这些配置类

public class SimImportSelect implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        ServiceLoader<AutoConfiguration> loader = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();
        for (AutoConfiguration configuration : loader) {
            list.add(configuration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}

6、验证

启动如下 main 函数,controller 随便写个返回 String 类型的 Http 接口,访问测试即可

详见样例代码(想要支持返回其他类型,解析为 String 类型还得拓展功能)

@SimSpringBootApplication
public class SimSpringBootApplicationDemo {

    public static void main(String[] args) {
        SimSpringApplication.run(SimSpringBootApplicationDemo.class);
    }

}

二、SpringBoot 的 Spring MVC

1、概念

为了更好理解相应概念,也会例举部分源码进行分析。

官方文档说明:Servlet Web Applicationsopen in new window
要点翻译如下:
Spring Boot 为 Spring MVC 提供了自动配置,可以很好地与大多数应用程序配合使用

它取代了 @EnableWebMvc 的需要,两者不能一起使用

除了 Spring MVC 的默认设置外,自动配置还提供了以下功能:

  • 包含 ContentNegotiatingViewResolver 和 BeanNameViewResolver bean

  • 支持提供静态资源,包括支持 WebJars(本文稍后介绍)

  • Converter、GenericConverter 和格式化 bean 的自动注册

  • 支持 HttpMessageConverters(本文稍后介绍)

  • MessageCodesResolver 的自动注册(本文稍后介绍)

  • 静态 index.html 支持

  • 自动使用 ConfigurableWebBindingInitializer bean(本文稍后介绍)

 (本文稍后介绍)是翻译原话

我们这里参考源码、以及简单示例理解下部分功能,详细可以阅读官方文档:SpringBoot SpringMVC Demo 源码open in new window

2、Spring Boot 两种方式使用 SpringMVC

默认的自动配置类 WebMvcAutoConfiguration 是启用的,如下

@AutoConfiguration(
    after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
public class WebMvcAutoConfiguration {

  @Bean
  @ConditionalOnMissingBean
  public InternalResourceViewResolver defaultViewResolver() {
      InternalResourceViewResolver resolver = new InternalResourceViewResolver();
      resolver.setPrefix(this.mvcProperties.getView().getPrefix());
      resolver.setSuffix(this.mvcProperties.getView().getSuffix());
      return resolver;
  }

  @Bean
  @ConditionalOnBean({View.class})
  @ConditionalOnMissingBean
  public BeanNameViewResolver beanNameViewResolver() {
      BeanNameViewResolver resolver = new BeanNameViewResolver();
      resolver.setOrder(2147483637);
      return resolver;
  }

  @Bean
  @ConditionalOnBean({ViewResolver.class})
  @ConditionalOnMissingBean(
      name = {"viewResolver"},
      value = {ContentNegotiatingViewResolver.class}
  )
  public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
      ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
      resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class));
      resolver.setOrder(Integer.MIN_VALUE);
      return resolver;
  }
}

注意 @EnableWebMvc 启用时,导入了一个 WebMvcConfigurationSupport 类,因此 WebMvcAutoConfiguration 会失效,如下代码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {

}

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

}

3、相关核心类介绍

ViewResolver 都是 SpringMVC 内置的视图解析器

  • InternalResourceViewResolver

    • 内部资源视图解析器,通过类似 prefix=/WEB-INF/、suffix=jsp 配置支持如 jsp、html 文件的解析

  • BeanNameViewResolver

    • 简单示例见 MvcViewController.helloJavaView 和 HelloJavaView 对象代码

    • 要点:

      • 注意 Controller 不要有 @ResponseBody 注解,不然不会进入视图解析器

      • 找的 Bean 要实现了 View 接口

      • 是根据 Bean 的名称找的视图解析器

  • ContentNegotiatingViewResolver

    • ContentNegotiatingViewResolver 本身不解析视图,而是委托给其他 ViewResolver

    • 具体解析视图对应逻辑为 ContentNegotiatingViewResolver.resolveViewName() 方法

    • 关注以下要点:

      • 返回 Json 对象时,返回的不是视图

      • 所有视图解析器,都会根据返回的视图名称进行 resolveViewName

    • 源码如下,关注下逻辑:

      • 初始化视图解析器

      • 获得所有匹配的视图

      • 委托给其他 ViewResolver 处理

      • 获得最终匹配的视图

public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
		implements ViewResolver, Ordered, InitializingBean {

  @Override
	protected void initServletContext(ServletContext servletContext) {
		Collection<ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
		if (this.viewResolvers == null) {
      // 初始化视图解析器,可以打个断点看下有哪些数据
			this.viewResolvers = new ArrayList<>(matchingBeans.size());
			for (ViewResolver viewResolver : matchingBeans) {
				if (this != viewResolver) {
					this.viewResolvers.add(viewResolver);
				}
			}
		}
		else {
			for (int i = 0; i < this.viewResolvers.size(); i++) {
				ViewResolver vr = this.viewResolvers.get(i);
				if (matchingBeans.contains(vr)) {
					continue;
				}
				String name = vr.getClass().getName() + i;
				obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
			}

		}
		AnnotationAwareOrderComparator.sort(this.viewResolvers);
		this.cnmFactoryBean.setServletContext(servletContext);
	}

   public View resolveViewName(String viewName, Locale locale) throws Exception {
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
		List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
		if (requestedMediaTypes != null) {
      // 获得所有匹配的视图
			List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
      // 获取最终的视图
			View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
			if (bestView != null) {
				return bestView;
			}
		}

		String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
				" given " + requestedMediaTypes.toString() : "";

		if (this.useNotAcceptableStatusCode) {
			if (logger.isDebugEnabled()) {
				logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
			}
			return NOT_ACCEPTABLE_VIEW;
		}
		else {
			logger.debug("View remains unresolved" + mediaTypeInfo);
			return null;
		}
	}

  private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

		List<View> candidateViews = new ArrayList<>();
		if (this.viewResolvers != null) {
			Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
      // ContentNegotiatingViewResolver 本身不解析视图,而是委托给其他 ViewResolver
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					candidateViews.add(view);
				}
				for (MediaType requestedMediaType : requestedMediaTypes) {
					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
					for (String extension : extensions) {
						String viewNameWithExtension = viewName + '.' + extension;
						view = viewResolver.resolveViewName(viewNameWithExtension, locale);
						if (view != null) {
							candidateViews.add(view);
						}
					}
				}
			}
		}
		if (!CollectionUtils.isEmpty(this.defaultViews)) {
			candidateViews.addAll(this.defaultViews);
		}
		return candidateViews;
	}
}

4、支持提供静态资源

以前要访问 jps\css、js 等静态资源文件,需要在 web.xml 配置

在 SpringBoot 不需要配置,只需要放在约定文件夹中就可以(阅读大于配置)

源码如下,注意如下要点(各版本差别较大的话,源码和配置会略有不同)

  • webjars 是用 jar 包装了一些 js、css 的 jar 包,https://www.webjars.org/ 可以看到列表

  • 使用步骤是 pom.xml 添加对应 webjars 依赖,然后就可以使用了,见 Demo,使用 http://localhost:8081/webjars/jquery/3.5.1/jquery.js 访问验证

  • 静态资源地址,看源码有这些地址 /META-INF/resources/classpath:/resources/classpath:/static/classpath:/public/,可以通过配置覆盖 spring.web.resources.static-locations

  • 配置欢迎页面,WebMvcAutoConfiguration 搜 index.html 可以找到默认的欢迎页面名字,注意是找 staticLocations 下找欢迎页面

public class WebMvcAutoConfiguration {
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
    } else {
      // 去 webjars 里面去找资源
      this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
      this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (Consumer)((registration) -> {
        // this.resourceProperties 里面有静态资源地址
        registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (this.servletContext != null) {
            ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
            registration.addResourceLocations(new Resource[]{resource});
        }

      }));
    }
  }

  // 获取欢迎页
  private Resource getIndexHtmlResource() {
    for(String location : this.resourceProperties.getStaticLocations()) {
      Resource indexHtml = this.getIndexHtmlResource(location);
      if (indexHtml != null) {
        return indexHtml;
      }
    }

    ServletContext servletContext = this.getServletContext();
    if (servletContext != null) {
      return this.getIndexHtmlResource((Resource)(new ServletContextResource(servletContext, "/")));
    } else {
      return null;
    }
  } 

  private Resource getIndexHtmlResource(String location) {
    return this.getIndexHtmlResource(this.resourceLoader.getResource(location));
  }

  private Resource getIndexHtmlResource(Resource location) {
    try {
      Resource resource = location.createRelative("index.html");
      if (resource.exists() && resource.getURL() != null) {
        return resource;
      }
    } catch (Exception var3) {
    }
    return null;
  }
}

public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
  private final WebProperties.Resources resourceProperties;
}

@ConfigurationProperties("spring.web")
public class WebProperties {
  private final Resources resources;

  public WebProperties() {
      this.localeResolver = WebProperties.LocaleResolver.ACCEPT_HEADER;
      this.resources = new Resources();
  }

  public static class Resources {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

    public Resources() {
        this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
        this.addMappings = true;
        this.customized = false;
        this.chain = new Chain();
        this.cache = new Cache();
    }
  }

  public Resources getResources() {
        return this.resources;
    }
}

5、类型转换、数据格式化、数据验证

也可以了解下 WebMvcAutoConfiguration 默认的对象

6、支持 HttpMessageConverters

HttpMessageConverters 负责 Http 请求和响应报文的处理。比如数据接收和响应是用 XML 还是 JSON 处理
默认的 HttpMessageConverters 源码如下

@AutoConfiguration(
  after = {GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class}
)
@ConditionalOnClass({HttpMessageConverter.class})
@Conditional({NotReactiveWebApplicationCondition.class})
@Import({JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class, JsonbHttpMessageConvertersConfiguration.class})
public class HttpMessageConvertersAutoConfiguration {

}
@Configuration(
    proxyBeanMethods = false
)
class JacksonHttpMessageConvertersConfiguration {
  
}

@Configuration(
  proxyBeanMethods = false
)
@ConditionalOnClass({ObjectMapper.class})
@ConditionalOnBean({ObjectMapper.class})
@ConditionalOnProperty(
  name = {"spring.mvc.converters.preferred-json-mapper"},
  havingValue = "jackson",
  matchIfMissing = true
)
static class MappingJackson2HttpMessageConverterConfiguration {
  MappingJackson2HttpMessageConverterConfiguration() {
  }

  @Bean
  @ConditionalOnMissingBean(
    value = {MappingJackson2HttpMessageConverter.class},
    ignoredType = {"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter"}
  )
  MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
    return new MappingJackson2HttpMessageConverter(objectMapper);
  }
}

7、MessageCodesResolver 的自动注册

比如验证 Bean 报错时的提示信息格式(errorCode.objectName.field 或 objectName.field.errorCode)

8、静态 index.html 支持

即使用 InternalResourceViewResolver 默认的视图解析器,将后缀配置为 html,即可支持 html

9、自动使用 ConfigurableWebBindingInitializer bean

和 HttpMessageConverters 不同,这个其实是处理 json 的入参转换

如 @RequestBody

public class WebMvcAutoConfiguration {
  protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(FormattingConversionService mvcConversionService, Validator mvcValidator) {
    try {
      return (ConfigurableWebBindingInitializer)this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
    } catch (NoSuchBeanDefinitionException var4) {
      return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
    }
  }
}

// 跟踪代码,找到 getConfigurableWebBindingInitializer.requestMappingHandlerAdapter 时会使用
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
  @Bean
  public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcValidator") Validator validator) {
    RequestMappingHandlerAdapter adapter = this.createRequestMappingHandlerAdapter();
    adapter.setContentNegotiationManager(contentNegotiationManager);
    adapter.setMessageConverters(this.getMessageConverters());
    adapter.setWebBindingInitializer(this.getConfigurableWebBindingInitializer(conversionService, validator));
  }
}

使用示例

1、视图解析器简单验证

在 ContentNegotiatingViewResolver.getCandidateViews() 方法上打个断点,触发 Controller 请求,看下是否会进入视图解析器处理逻辑

  • 非视图解析器的验证

    • 代码见 CommonController,浏览器输入 http://localhost:8081/common/hello

    • 会正常的返回数据,不会进入 getCandidateViews() 方法

  • 进入视图解析器的验证

    • 浏览器输入不存在的 URL,如 http://localhost:8081/common/404Test

    • 页面报错: Whitelabel Error Page

  • 自定义视图解析器

    • 即使用 @Bean 注入自定义的 ViewResolver 对象即可

  • 自定义 Excel 视图解析器

    • 添加自定义 View,实现 AbstractXlsxView,编写自己的业务逻辑即可

    • 可以见 MyXlsxView 代码,数据对象应该是 Controller 的返回数据

2、定制 SpringMVC ViewController

以前写 SpringMVC 的时候,如果需要访问一个页面,必须要写 Controller 类,然后再写一个方法跳转到页面

感觉好麻烦,其实重写 WebMvcConfigurer 中的 addViewControllers 方法即可达到效果了

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/toLogin").setViewName("login");
}

3、定制 SpringMVC configurePathMatch

重写 WebMvcConfigurer 中的 configurePathMatch 方法并通过调用 setUseSuffixPatternMatch 和 setUseRegisteredSuffixPatternMatch 方法来设置 PathMatch 的属性

public void configurePathMatch(PathMatchConfigurer configurer) {
  configurer.setUseSuffixPatternMatch(false);
  configurer.setUseTrailingSlashMatch(true);
}
  • setUseSuffixPatternMatch(false):禁用后缀模式匹配,例如,/user 和 /user.json 将被视为不同的 URL

  • setUseTrailingSlashMatch(true): 启用尾部斜杠匹配,例如, /user 和 /user/ 将被视为相同的 URL

4、定制 SpringMVC 拦截器 Interceptor

自定义实现了 HandlerInterceptor 的拦截器类,并重写 WebMvcConfigurer.addInterceptors 到 Spring 容器中即可

5、定制 SpringMVC CORS 配置

 略。

6、定制 SpringMVC JSON

 略。

7、定制 SpringMVC 国际化

 略。

8、定制 SpringMVC 异常处理

 略。

WebMvcConfigure 原理

实现 WebMvcConfigure 接口可以拓展 MVC 实现,又既保留 SpringBoot 的自动配置

  1. 在 WebMvcAutoConfiguration 有一个实现了 WebMvcConfigure 的配置类 WebMvcAutoConfigurationAdapter

  2. WebMvcAutoConfigurationAdapter 它也是利用这种方式去进行扩展,所以我们通过查看这个类它帮我们实现了其他不常用的方法,帮助我们进行自动配置,我们只需定制(拦截器、视图控制器、CORS 在开发中需要额外定制的功能),这块主要逻辑为 @Import({EnableWebMvcConfiguration.class}) 配合处理的,代码如下


@Configuration(
  proxyBeanMethods = false
)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

}
  1. EnableWebMvcConfiguration 它的父类上 setConfigurers 使用 @Autowired 注解
    (1) 将容器中所有实现了 WebMvcConfigurer 接口的 Bean 都自动注入进来,添加到 configurers 中
    (2) 添加到 delegates 委派器中
    (3) 底层调用 WebMvcConfigure 对应的方法时,就是循环调用 delegates 处理
    (4) 注意:当使用了 @EnableWebMvc 注解时,就不会使用 SpringMVC 自动配置类的默认配置了

@Configuration(
  proxyBeanMethods = false
)
@EnableConfigurationProperties({WebProperties.class})
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
}

@Configuration(
    proxyBeanMethods = false
)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  @Autowired(required = false)
  public void setConfigurers(List<WebMvcConfigurer> configurers) {
    // (1) 将容器中所有实现了 WebMvcConfigurer 接口的 Bean 都自动注入进来,添加到 configurers 中。  
    if (!CollectionUtils.isEmpty(configurers)) {
      this.configurers.addWebMvcConfigurers(configurers);
    }
  }
}

class WebMvcConfigurerComposite implements WebMvcConfigurer {
  private final List<WebMvcConfigurer> delegates = new ArrayList();

  public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
    // (2) 添加到 delegates 委派器中。  
    if (!CollectionUtils.isEmpty(configurers)) {
      this.delegates.addAll(configurers);
    }
  }

  public void addInterceptors(InterceptorRegistry registry) {
    // (3) 底层调用 WebMvcConfigure 对应的方法时,就是循环调用 delegates 处理。
    for(WebMvcConfigurer delegate : this.delegates) {
      delegate.addInterceptors(registry);
    }
  }
}

参考文献

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区