本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

Eureka Server 启动流程

本文基于上游最新 RELEASE 版, SpringBoot 2.2.5, SpringCloud Hoxton.SR8 撰写

1) Eureka Server 启动流程

通常我们的 Eureka Server 是以 SpringBootStarter 引入的, 如: spring-cloud-starter-netflix-eureka-server.
那么一个正常的 Starter 依赖, 会遵循 SpringBoot 自动装配的原则, 在 spring.factories 中定义对应的 AutoConfiguration 自动装配类, 那我们就从这里开始找吧~

spring-cloud-netflix-eureka-server 依赖中找到 spring.factories 文件, 打开康康:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

文件中只定义了一个自动装配类: EurekaServerAutoConfiguration, 即 EurekaServer 的启动流程关键类

1.1) EurekaServerAutoConfiguration 注解分析

// 1. 标注为配置类, 且关闭 Bean 的代理(提高启动速度)
@Configuration(proxyBeanMethods = false)
// 2. **1.1.2**
@Import(EurekaServerInitializerConfiguration.class)
// 3. 当容器中存在 EurekaServerMarkerConfiguration.Marker 时才加载此配置类, **1.1.1**
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {

看完注解信息, 我们将会分三节来讲 EurekaServer 的启动流程:

  • 1.2 开启自动装配条件
  • 1.3 EurekaServerAutoConfiguration 类分析
  • 1.4 自动装配结束后续操作 @Import(EurekaServerInitializerConfiguration.class)

1.2) Eureka Server 开启自动装配条件

EurekaServerAutoConfiguration 中定义了一个 @ConditionalOnBean, 自动装配的前提是容器中存在一个 Marker 对象, 哪儿来的呢? @EnableEurekaServer 注解中 Import 的:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}

所以我们正常在启动类上加的 @EnableEurekaServer 即开启自动装配的前提.

1.3) EurekaServerAutoConfiguration 自动装配类分析

本小节会讲一些自动装配类中重要或者可扩展的方法点:

  1. EurekaServer 仪表盘
    org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration#eurekaController

    @Bean
    @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled",
         matchIfMissing = true)
    public EurekaController eurekaController() {
     return new EurekaController(this.applicationInfoManager);
    }
    @RequestMapping("${eureka.dashboard.path:/}")
    public class EurekaController {
  2. EurekaController 是 Server 仪表盘的实现 Controller, 可以通过配置 eureka.dashboard=false 关闭

  3. 可以通过配置 eureka.dashboard.path 修改仪表盘的基路由

  4. PeerAwareInstanceRegistry 对等服务实例注册表
    用于获取其他 Server 实例的信息, 如用于注册表复制
    org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration#peerAwareInstanceRegistry

    @Bean
    public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
         ServerCodecs serverCodecs) {
     this.eurekaClient.getApplications(); // force initialization
     return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
             serverCodecs, this.eurekaClient,
             this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
             this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
    }
  5. PeerEurekaNodes 对等节点
    用于封装, 操作对等节点相关信息
    org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration#peerEurekaNodes

    @Bean
    @ConditionalOnMissingBean
    public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
         ServerCodecs serverCodecs,
         ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
     return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
             this.eurekaClientConfig, serverCodecs, this.applicationInfoManager,
             replicationClientAdditionalFilters);
    }

    其中的 start 方法可用于启动一个, 用于同步对等节点的 Task, 这里用到了一个单例线程池, 可以配置 peer-eureka-nodes-update-interval-ms=600000 来修改同步对等节点间隔时间
    com.netflix.eureka.cluster.PeerEurekaNodes#start

    public void start() {
     taskExecutor = Executors.newSingleThreadScheduledExecutor(
             new ThreadFactory() {
                 @Override
                 public Thread newThread(Runnable r) {
                     Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                     thread.setDaemon(true);
                     return thread;
                 }
             }
     );
     try {
         updatePeerEurekaNodes(resolvePeerUrls());
         Runnable peersUpdateTask = new Runnable() {
             @Override
             public void run() {
                 try {
                     updatePeerEurekaNodes(resolvePeerUrls());
                 } catch (Throwable e) {
                     logger.error("Cannot update the replica Nodes", e);
                 }
    
             }
         };
         taskExecutor.scheduleWithFixedDelay(
                 peersUpdateTask,
                 serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                 serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                 TimeUnit.MILLISECONDS
         );
     } catch (Exception e) {
         throw new IllegalStateException(e);
     }
     for (PeerEurekaNode node : peerEurekaNodes) {
         logger.info("Replica node URL:  {}", node.getServiceUrl());
     }
    }
  6. EurekaServerContext EurekaServer 上下文
    封装了 EurekaServer 的包括前面提到的各个组件, 默认使用 DefaultEurekaServerContext
    org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration#eurekaServerContext

    @Bean
    @ConditionalOnMissingBean
    public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
         PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
     return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
             registry, peerEurekaNodes, this.applicationInfoManager);
    }

    其内有一个 initialize() 方法会在 Spring Bean 创建完成后, 调用上小节中的定时任务 start 方法同步对等节点的信息.

  7. EurekaServerBootstrap EurekaServer 引导程序
    也封装了各个组件, 在 1.4 节中会使用到它进行一些结尾初始化配置
    org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap

    @Bean
    public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
         EurekaServerContext serverContext) {
     return new EurekaServerBootstrap(this.applicationInfoManager,
             this.eurekaClientConfig, this.eurekaServerConfig, registry,
             serverContext);
    }
  8. jerseyFilterRegistration/jerseyApplication Jersey 相关
    EurekaServer 使用 Jersey 代替 SpringMVC 发布 Restful 服务

    @Bean
     public FilterRegistrationBean<?> jerseyFilterRegistration(
             javax.ws.rs.core.Application eurekaJerseyApp) {
         FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<Filter>();
         bean.setFilter(new ServletContainer(eurekaJerseyApp));
         bean.setOrder(Ordered.LOWEST_PRECEDENCE);
         bean.setUrlPatterns(
                 Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
    
         return bean;
     }
    
     /**
      * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources
      * required by the Eureka server.
      * @param environment an {@link Environment} instance to retrieve classpath resources
      * @param resourceLoader a {@link ResourceLoader} instance to get classloader from
      * @return created {@link Application} object
      */
     @Bean
     public javax.ws.rs.core.Application jerseyApplication(Environment environment,
             ResourceLoader resourceLoader) {
    
         ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
                 false, environment);
    
         // Filter to include only classes that have a particular annotation.
         //
         provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
         provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
    
         // Find classes in Eureka packages (or subpackages)
         //
         Set<Class<?>> classes = new HashSet<>();
         for (String basePackage : EUREKA_PACKAGES) {
             Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
             for (BeanDefinition bd : beans) {
                 Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
                         resourceLoader.getClassLoader());
                 classes.add(cls);
             }
         }
    
         // Construct the Jersey ResourceConfig
         Map<String, Object> propsAndFeatures = new HashMap<>();
         propsAndFeatures.put(
                 // Skip static content used by the webapp
                 ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
                 EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");
    
         DefaultResourceConfig rc = new DefaultResourceConfig(classes);
         rc.setPropertiesAndFeatures(propsAndFeatures);
    
         return rc;
     }

1.4) @Import(EurekaServerInitializerConfiguration.class)

配置类上的 @Import 引入的类, 会在当前配置类加载结束后加载, 我们来康康自动装配加载后做的事儿:

  1. 注解以及接口实现
    org.springframework.cloud.netflix.eureka.server.EurekaServerInitializerConfiguration
    @Configuration(proxyBeanMethods = false)
    public class EurekaServerInitializerConfiguration
         implements ServletContextAware, SmartLifecycle, Ordered {

发现这也是一个配置类, 且实现了 SmartLifecycle 接口, 会在 Spring 初始化完成后执行 start 方法, 其他方法不太重要, 直接来看 start 的代码:
org.springframework.cloud.netflix.eureka.server.EurekaServerInitializerConfiguration#start

@Override
public void setServletContext(ServletContext servletContext) {
    1. 实现 ServletContextAware 的 setServletContext 方法, **注入了 ServletContext 供于使用**
    this.servletContext = servletContext;
}

public void start() {
    new Thread(() -> {
        try {
            // 2. 调用 eurekaServerBootstrap.contextInitialized 初始化完成方法
            eurekaServerBootstrap.contextInitialized(
                    EurekaServerInitializerConfiguration.this.servletContext);
            log.info("Started Eureka Server");
            // 3. 发布注册表可用事件
            publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
            EurekaServerInitializerConfiguration.this.running = true;
            // 4. 广播 EurekaServer 启动完成事件
            publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
        }
        catch (Exception ex) {
            log.error("Could not initialize Eureka servlet context", ex);
        }
    }).start();
}

可以看到 3 和 4 步, 发布了两个事件, 在 EurekaServer 中其实并没有针对这两个事件做捕获, 所以是两个扩展点, 可以用 Spring 的监听器捕获这两个事件扩展业务.

  1. 我们看看第 2 步中的 contextInitialized 方法, 将 servletContext 传入, 源码:
    org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap#contextInitialized

    public void contextInitialized(ServletContext context) {
     try {
         initEurekaEnvironment(); // 1. 初始化环境参数, 不重要不深入了
         initEurekaServerContext(); // 2. 初始化上下文
    
         context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
     }
     catch (Throwable e) {
         log.error("Cannot bootstrap eureka server :", e);
         throw new RuntimeException("Cannot bootstrap eureka server :", e);
     }
    }
  2. 我们接着来看看重点方法 initEurekaServerContext() 初始化:
    org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap#initEurekaServerContext

protected PeerAwareInstanceRegistry registry;

protected void initEurekaServerContext() throws Exception {
    // 1. 转换器兼容性参数配置
    JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
            XStream.PRIORITY_VERY_HIGH);
    XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
            XStream.PRIORITY_VERY_HIGH);
    // AWS 云主机相关配置
    if (isAws(this.applicationInfoManager.getInfo())) {
        this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
                this.eurekaClientConfig, this.registry, this.applicationInfoManager);
        this.awsBinder.start();
    }
    // 注入 serverContext
    EurekaServerContextHolder.initialize(this.serverContext);

    log.info("Initialized server context");

    // 2. 从其他节点复制注册表
    int registryCount = this.registry.syncUp();
    this.registry.openForTraffic(this.applicationInfoManager, registryCount);

    // 监控统计登记
    EurekaMonitors.registerAllStats();
}
  1. 同步注册表 syncUp:
    com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#syncUp
    @Override
    public int syncUp() {
     int count = 0;
     // 1. 通过 for 循环重试获取注册表, 配置文件中 registry-sync-retries=0 控制重试次数
     for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
         if (i > 0) { // 重试
             try {
                 // 配置文件中 registry-sync-retry-wait-ms 控制重试间隔
                 Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
             } catch (InterruptedException e) {
                 logger.warn("Interrupted during registry transfer..");
                 break;
             }
         }
         // 2. 调用 Client 获取配置节点的注册表, 注册到本节点中
         Applications apps = eurekaClient.getApplications();
         for (Application app : apps.getRegisteredApplications()) {
             for (InstanceInfo instance : app.getInstances()) {
                 try {
                     if (isRegisterable(instance)) {
                         // 3. 尝试复制注册表, 注册到本节点中
                         register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
                         count++;
                     }
                 } catch (Throwable t) {
                     logger.error("During DS init copy", t);
                 }
             }
         }
     }
     return count; # 返回 Server 节点数量
    }

复制注册表的方法 register 代码量有点多, 这里不深入了, 简单了解以下注册表的数据结构:
com.netflix.eureka.registry.AbstractInstanceRegistry#register

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
        = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
  1. openForTraffic 开启同步阀门方法, 其有两个实现(单/多实例), 这里只看多示例的实现:
    com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#openForTraffic
// 需要发送心跳的客户端数量
protected volatile int expectedNumberOfClientsSendingRenews;

@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
    // 1. 自我保护阈值计算
    this.expectedNumberOfClientsSendingRenews = count;
    updateRenewsPerMinThreshold();

    logger.info("Got {} instances from neighboring DS node", count);
    logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
    this.startupTime = System.currentTimeMillis();
    if (count > 0) {
        this.peerInstancesTransferEmptyOnStartup = false;
    }
    DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
    boolean isAws = Name.Amazon == selfName;
    if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
        logger.info("Priming AWS connections for all replicas..");
        primeAwsReplicas(applicationInfoManager);
    }
    logger.info("Changing status to UP");
    applicationInfoManager.setInstanceStatus(InstanceStatus.UP); // 2. 实例状态更改为 UP
    super.postInit(); // 3. 定时任务剔除服务
}

Eureka Server 的启动流程结束.

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

Spring Boot 2.2 后, spring-boot-starter-testJUnit 的版本升级至 JUnit 5, 传统的测试类写法就需要变变啦.

  1. 原来最常用的 RunWith 注解由 @ExtendWith 代替.
  2. 第二个最常用的 @SpringBootTest, 内部注解有点变化:
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @BootstrapWith(SpringBootTestContextBootstrapper.class)
    @ExtendWith({SpringExtension.class})
    public @interface SpringBootTest {

    有没有发现 ~ 内置了 @ExtendWith 注解, HiaHiaHia, 只需要写一个 @SpringBootTest 了, 真香!

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

SpringMVC 源码剖析

本文基于当前上游最新版 SpringBoot-2.3.4.RELEASE, 和 SpringMVC-5.2.9.RELEASE 讲解

1) DispatcherServlet

一个普通的 SpringMVC 项目中, 需要在 web.xml 中定义 DispatcherServlet顶层 Servlet, 那么代表 DispatcherServlet继承HttpServlet 的.
继承关系如下:

1.1) SpringMVC 初始化

1.1.1) SpringMVC 九大组件初始化链路

仔细翻阅源码之后发现, SpringMVC 的初始化入口是在父类的 init 方法中, 也就是 Servlet 第一次被请求才初始化调用的 init 方法, 被定义在父类 HttpServletBean 中.

    public final void init() throws ServletException {
        // 1. 启动参数加载
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        initServletBean();
    }

我们可以看到, 方法中都是一些启动参数配置的方法, 最后调用了 initServletBean(), 接着往下看看, 是在 FrameworkServlet中实现的:

    @Override
    protected final void initServletBean() throws ServletException {
        // ...
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        // ...
    }

去掉一些日志输出和 catch 代码, 最终 initServletBean 方法中剩下这两句, 看了一下第二行 initFrameworkServlet 方法中是一个模板方法, 但是没有子类实现, 所以就只剩下WebApplicationContext 的初始化逻辑, 也是在本类实现的, 进入康康:

    protected WebApplicationContext initWebApplicationContext() {
        // 1. 包装 ServletContext 为 wac
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            synchronized (this.onRefreshMonitor) {
                // 2. **核心方法**
                onRefresh(wac);
            }
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }

发现没有什么有价值的代码, 都是一些判断换包装, 最后有一句 onRefresh(wac), 像是 Spring 的命名, 应该是个重点方法, 发现是在子类 DispatcherServlet 中实现的, 进入康康:

    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

接着就能发现 SpringMVC 中, 9 大组件的初始化方法啦, 这个方法的调用链大概就是这样:

  1. HttpServletBean#init
  2. FrameworkServlet#initServletBean
  3. FrameworkServlet#initWebApplicationContext
  4. DispatcherServlet#onRefresh

1.1.2) SpringMVC 九大组件初始化

只挑两个最重要的讲, initHandlerMappingsinitHandlerAdapters 方法.

1.1.2.1) initHandlerMappings

    private boolean detectAllHandlerMappings = true;
    public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";

    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) { // 默认 True
            // 1. 从 Spring 容器中获取 HandlerMapping 接口的实现
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) { // 2. 如不为空则拿 value 转成 List
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                // 3. 再根据 @Order 注解排序
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else { // 可以在 web.xml 或 SpringBoot 配置文件中修改 detectAllHandlerMappings
            try {
                // 只获取一个 name 为 handlerMapping 的实现
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // 4. 如果未获取到 HandlerMapping 的实现, 则获取默认实现
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isTraceEnabled()) {
                logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                        "': using default strategies from DispatcherServlet.properties");
            }
        }
    }

方法上部没啥可以深入的代码, 最后获取默认实现的方法 getDefaultStrategies 我们进去康康:

    private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
    private static final Properties defaultStrategies;

    static {
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
        }
    }

    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName(); // 1. HandlerMapping
        String value = defaultStrategies.getProperty(key); // 2. 从 DispatcherServlet.properties 中获取 key 为 HandlerMapping 的值
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value); // 3. 分割成字符串数组
            // 4. 创建实例并加入容器和 List 集合
            List<T> strategies = new ArrayList<>(classNames.length);
            for (String className : classNames) {
                try {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz); // 使用 BeanFactory 创建实例
                    strategies.add((T) strategy); // 加入数组
                }
                catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Could not find DispatcherServlet's default strategy class [" + className +
                            "] for interface [" + key + "]", ex);
                }
                catch (LinkageError err) {
                    throw new BeanInitializationException(
                            "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                            className + "] for interface [" + key + "]", err);
                }
            }
            // 5. 返回
            return strategies;
        }
        else {
            return new LinkedList<>();
        }
    }

最终默认的 HandlerMapping 如下:

  • BeanNameUrlHandlerMapping: 处理配置文件中定义的映射
  • RequestMappingHandlerMapping: 处理 @RequestMapping 注解定义的映射
  • RouterFunctionMapping: 处理另一种配置文件中定义的映射

1.1.2.2) initHandlerAdapters

initHandlerAdapters 的代码和 initHandlerMapping 几乎一样, 直接上默认的 Adapters:

  • HttpRequestHandlerAdapter
  • SimpleControllerHandlerAdapter
  • RequestMappingHandlerAdapter
  • HandlerFunctionAdapter

1.2) 请求处理

我们知道, 正常一个请求发起, 首先会进入 Servlet 中的 service 方法, 然后被 service 方法根据请求的 Method 分发给 doGet, doPost, doPut 等方法, 我们试着康康继承关系中哪些子类重写了 service 方法:
好的, 在继承关系中, 只发现 FrameworkServlet 重写了它, 康康代码:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
            processRequest(request, response);
        }
        else { // 调用父类 HTTPServlet 的 service 方法
            super.service(request, response);
        }
    }

我们通过代码可以发现, SpringMVCServlet 的基础上, 额外实现了对 HTTP PATCH 的支持, 平常一般用的不多, 就不深入看了.
然后对于普通的请求, 还是交由父类处理, 那么我们还是得从 doGet, doPost 之类的方法下手, 看看子类实现.
发现还是由 FrameworkServlet 重写了它们:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

    @Override
    protected final void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        processRequest(request, response);
    }

    @Override
    protected final void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        processRequest(request, response);
    }
    // ...

最终都是由本类中 processRequest 方法处理, 康康代码:

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        // Locale 国际化相关
        //  获取 ThreadLocal 中 Locale 缓存
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        //  从请求中获取新的 Locale
        LocaleContext localeContext = buildLocaleContext(request);

        // 获取 request 域中的参数缓存
        //  获取 ThreadLocal 中 request 域参数缓存
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        //  从请求中获取新的 request 域参数
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        // 异步相关
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        // 将新的 Locale 和 request 域参数更新到 ThreadLocal 中
        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response); // **核心方法**
        }
        catch (ServletException | IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            logResult(request, response, failureCause, asyncManager);
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

最终我们发现, 调用了一个 doService 方法来实际处理请求响应, 本类方法中为抽象方法, 是子类 DispatcherServlet 实现的, 提到这个类就熟悉了吧:

public class DispatcherServlet extends FrameworkServlet {

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);
        // 保存请求快照, 可用于之后恢复
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
        // request 域中设置了些参数
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
        // 重定向相关
        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }
        // 请求域中 request_uri 处理
        RequestPath requestPath = null;
        if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
            requestPath = ServletRequestPathUtils.parseAndCache(request);
        }

        try {
            doDispatch(request, response); // **核心方法**
        }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
            if (requestPath != null) {
                ServletRequestPathUtils.clearParsedRequestPath(request);
            }
        }
    }

DispatcherServlet 中, 最后将请求响应交给了 doDispatch, 此方法即 SpringMVC 的核心方法:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                // 1. 文件上传判断
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // 2. 根据 request 获取 HandlerExecutionChain(包含 Controller 和拦截器链)
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) { // 404
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 3. 获取 HandlerAdapter(Controller)
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // 处理 Last-Modified 请求头返回 304
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                // 4. 拦截器链前置执行
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 5. Handler 实际处理, 返回 ModelAndView
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv); // 6. 如没指定视图名, 给一个默认的
                // 7. 拦截器链后置执行
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 8. 视图渲染
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

DispatcherServlet 中的 doDispatch 方法基本流程:

  1. 根据 getHandler 获取匹配的 HandlerExecutionChain(Handler + 拦截器链)
  2. 拦截器链执行
  3. Handler 执行
  4. 拦截器链执行
  5. 视图渲染响应

接着来一步一步的康康源码

1.1) getHandler

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

可以看到, getHandler 中会遍历 DispatcherServlet 中的 handlerMappings 处理器映射器, 尝试获取 HandlerExecutionChain 处理器执行链, 如果获取到则返回.
handlerMappings 是 Spring 容器初始化时构建的, 那么一般有哪几种呢:

  1. RequestMappingHandlerMapping: 映射通过 @RequestMapping 定义的 Handler
  2. BeanNameUrlHandlerMapping: 映射通过 Spring 配置文件定义的 Handler
  3. SimpleUrlHandlerMapping: 也可以通过 Spring 配置文件定义映射, 一般在 Spring 初始化时会往里加载 ResourceHttpRequestHandler 用于处理资源文件请求.
  4. WelcomePageHandlerMapping: 不常用, 一般用于处理映射 / 路径

1.2) getHandlerAdapter

1.2) 前置拦截器执行

1.3) Handler 执行

1.4) 后置拦截器执行

1.5) 视图渲染

1.6) 响应

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

SpringBoot 源码分析

本文基于 SpringBoot 2.3.4.STABLE 版本撰写

1) 依赖管理

2) Maven parent 依赖引入

2.1) spring-boot-starter-parent 分析

一个最基本的 SpringBoot 工程中, 会在 pom.xml 中引入一个 parent 依赖: spring-boot-starter-parent, 观察此依赖包内容如下:

  • spring-boot-starter-parent-2.3.4.RELEASE.pom
  1. 只有一个 pom 文件, 是一个 pom 类型的依赖, 打开: spring-boot-starter-parent-2.3.4.RELEASE.pom 后可以发现引入了一些 Plugin, 没有啥有用的信息.
  2. 翻到头部找找有无父依赖, 发现其还有一个父依赖 spring-boot-dependencies, 此依赖便是 spring-boot-starter-parent 的核心实现:
    <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-dependencies</artifactId>
     <version>2.3.4.RELEASE</version>
    </parent>
2.1.1) spring-boot-dependencies 分析

老样子先来研究一下目录结构:

  • spring-boot-dependencies-2.3.4.RELEASE.pom

发现也是一个 pom 依赖, 打开 spring-boot-dependencies-2.3.4.RELEASE.pom 后一幕明了了, 结构大致如下:

<!-->1. 首先定义了一些 properties 的版本号</-->
  <properties>
    <activemq.version>5.15.13</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.82</appengine-sdk.version>
    <!-->.......</-->
    <!-->.......</-->
  </properties>

<!-->2. 接着是基于 properties 中的版本号, 利用 maven-dependency-management 管理相应包的依赖版本</-->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-amqp</artifactId>
        <version>${activemq.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-blueprint</artifactId>
        <version>${activemq.version}</version>
      </dependency>
      <!-->.......</-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.3.4.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-activemq</artifactId>
        <version>2.3.4.RELEASE</version>
      </dependency>
      <!-->.......</-->
    </dependencies>
  </dependencyManagement>
<!-->3. 最后利用 maven-plugin-management 管理插件依赖的版本</-->
  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>build-helper-maven-plugin</artifactId>
          <version>${build-helper-maven-plugin.version}</version>
        </plugin>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>exec-maven-plugin</artifactId>
          <version>${exec-maven-plugin.version}</version>
        </plugin>
        <!-->.......</-->
        <!-->.......</-->
    </pluginManagement>
  </build>

总结: spring-boot-starter-parent 中, 利用 maven 的依赖管理, 管理了一些常用包以及 starter 的依赖版本号, 起到了版本号统一管理的作用, 版本号可以提供给 SpringBoot 的其他 starter使用, 起到了让我们引入一些基础依赖或 starter 时, 不需要手动填写版本号的作用.

2.2) spring-boot-starter-web 分析

上一节讲到 spring-boot-starter-parent 主要起到统一管理常用包的一些版本号, 我们拿最常用的依赖包 spring-boot-starter-web 来接着分析一下, 找到包目录中文件如下:

  • spring-boot-starter-web-2.3.4.RELEASE.jar
  • spring-boot-starter-web-2.3.4.RELEASE.pom

jar 包中只有一些 License 文件, 打开 pom 文件康康带进了啥依赖:

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.9.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.9.RELEASE</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

观察发现带入了以下几种传递依赖:

  1. SpringBoot 基础库
  2. Spring json 相关库
  3. Tomcat
  4. SpringMVC

总结: spring-boot-starter-web 将 WEB 相关的依赖做了一个打包, 简化项目中 pom 文件的长度, 增强可读性.

3) SpringBoot 自动配置

从一个最基础的启动类开始讲起吧, 本节先讲讲 @SpringBootApplication 注解, 其注解最主要的作用时自动配置, 或者叫自动装配:

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication application = new SpringApplication(Application.class);
    application.run(args);
  }
}

将不重要或基础的代码移除后, @SpringBootApplication 中代码大致如下, 将分为三个小节讲解:

// 注解作用域等
@SpringBootConfiguration // 3.1
@EnableAutoConfiguration // 3.2 
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  // 注解字段属性
}

3.1) @SpringBootConfiguration

在当前版本中, 此注解只是为我们的启动类, 加上了 @Configuration 注解, 标记为 SpringFramework 的配置类, 代码:

@Configuration
public @interface SpringBootConfiguration {
  // ...
}

3.2) @EnableAutoConfiguration

@EnableAutoConfiguration 中的代码就是重头戏了, SpringBoot 自动配置主要就是基于此注解实现, 来康康代码, 两句注解将通过 3.2.1, 3.2.2 两小节讲解:

@AutoConfigurationPackage // 3.2.1
@Import(AutoConfigurationImportSelector.class) // 3.2.2
public @interface EnableAutoConfiguration {
  // ...
}
3.2.1) @AutoConfigurationPackage

我们先来康康代码:

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
  // ...
}

代码量很少, @Import 注解可以将类导入 Spring 容器, 我们接着看看这个内部类 Registrar:

  // 实现了 ImportBeanDefinitionRegistrar, 代表此类使用个性化加载, 并不将 Registrar 本身注入容器
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      // 1. new PackageImports(metadata).getPackageNames().toArray(new String[0])
      // 返回了当前启动类所在的包的包名
      // 2. 接着将 registry Bean 注册表, 和上一步获取的包名传入 register 方法
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }

    // determineImports 方法在 SpringBoot 启动过程中没有调用, 国内外也基本查不到相关作用, 就不讲了
    // 官方 Doc 里注释此方法返回一个需要注入到容器的对象数组
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
    }

我们接着来康康 register 方法:

  // 1. org.springframework.boot.autoconfigure.AutoConfigurationPackages
    private static final String BEAN = AutoConfigurationPackages.class.getName();

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 2. Spring 容器中是否包含 AutoConfigurationPackages
        if (registry.containsBeanDefinition(BEAN)) { // 启动过程中并不包含, 继续走 else
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        }
        else {
      // 3. 定义一个标准 Bean, 相当于需要注入容器的 Bean 的包装类
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
      // 4. 定义需要注入容器的类
            beanDefinition.setBeanClass(BasePackages.class);
      // 5. 根据其第 1 个构造方法, 传入启动类所在包名作为构造器参数, 后续会根据此构造方法反射构建 Bean
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // 4. 表明此 Bean 为 Spring 后台注册而非人为注册
      // 5. 注入容器, name 为 org.springframework.boot.autoconfigure.AutoConfigurationPackages
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }

总结: 很多人以为 register 这个方法会扫描启动类所有子包, 并将 @Component 等注解类标注的类加入容器, 实际上只是将启动类所在包名的包装类, 加入了容器中, BeanName 为: org.springframework.boot.autoconfigure.AutoConfigurationPackages

是的, @Import(AutoConfigurationPackages.Registrar.class) 注解几乎啥也没做

3.2.1) @Import(AutoConfigurationImportSelector.class)

熟悉的 @Import, 我们进入 Selector 康康:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    // ...
}
  1. 发现此类实现的是 DeferredImportSelector, 它也是一个个性化加载接口, 实现它会在所有的 @Configuration 注解类处理完才执行导入操作.
  2. 接着实现了其他四个接口: BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, 从这四个接口实现的方法会比 selectImports 方法(DeferredImportSelector)先执行.
  3. 最后还有一个 Ordered, 实现了其 getOrder 方法: public int getOrder() { return Ordered.LOWEST_PRECEDENCE - 1; }, 这里也应证了第 1 点.

我们先来康康从第 2 点中实现下来的方法吧:

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

发现没啥信息, 很基础的实现, 没做啥特殊操作, 我们接着康康从 Selector 中实现的方法:

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 1. 默认为 True, 可以通过在 SpringBoot 配置文件中将 `spring.boot.enableautoconfiguration=false` 关闭自动装配
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

先进入 getAutoConfigurationEntry 康康:

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 1. 默认为 True, 可以通过在 SpringBoot 配置文件中将 `spring.boot.enableautoconfiguration=false` 关闭自动装配
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
    // 2. 获取 @EnableAutoConfiguration 注解中的属性
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 3.3 中讲解
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

代码行数过多, 3.3 中详细讲解:

3.3) getAutoConfigurationEntry

  1. getCandidateConfigurations
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    我们进入方法内部康康:
     protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
       // 加载 spring.factories 中, key 为 EnableAutoConfiguration 全类名的 value 们
       List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
           getBeanClassLoader());
       Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
           + "are using a custom packaging, make sure that file is correct.");
       return configurations;
     }
     protected Class<?> getSpringFactoriesLoaderFactoryClass() {
       return EnableAutoConfiguration.class;
     }

    阅读过 SpringBoot 启动源码的童鞋可能对这行代码比较熟悉, 这里不深入讲了, 稍微来看看 loadFactoryNames 的源码:

     public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
       String factoryTypeName = factoryType.getName(); // 拿到 EnableAutoConfiguration 的全类名
       // 从 spring.factories 中读取 key 为 EnableAutoConfiguration 的全类名的值
       return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
     }

    最后返回一个自动配置类的 List 集合.

  2. removeDuplicates
    configurations = removeDuplicates(configurations);
    进入方法内部看看:
     protected final <T> List<T> removeDuplicates(List<T> list) {
       return new ArrayList<>(new LinkedHashSet<>(list));
     }

    这就比较简单了 =.=, 用一个 HashSet 对 List 做了一个去重, 然后重新转成一个 List 集合, removeDuplicates 中主要是对我们的自动配置类做一个去重工作.

  3. getExclusions
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    上面说到, annotationMetadata 是我们启动类的一些元数据信息, attributes 是 @EnableAutoConfiguration 注解中的属性, 我们进入 getExclusions 方法康康:
    protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
         Set<String> excluded = new LinkedHashSet<>(); // 去重集合
     // 获取 @EnableAutoConfiguration 中的 exclude 属性
         excluded.addAll(asList(attributes, "exclude"));
     // 获取 @EnableAutoConfiguration 中的 excludeName 属性
         excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
     // 获取 SpringBoot 配置文件中的 spring.autoconfigure.exclude 配置的排除类, 这里不深入讲解了
         excluded.addAll(getExcludeAutoConfigurationsProperty());
         return excluded;
     }

    最后返回一个去重后自动配置类的排除类集合, 我们接着往下看.

  4. checkExcludeClasses
    checkExcludedClasses(configurations, exclusions);
    private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
         List<String> invalidExcludes = new ArrayList<>(exclusions.size());
         for (String exclusion : exclusions) {
       // 1. 排除类如果存在, 且从 spring.factories 中读取的配置类数组中**不包含**
             if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
                 invalidExcludes.add(exclusion); // 2. 塞入 invalidExcludes 异常排除类集合
             }
         }
     // 3. 如有异常排除类进入 handleInvalidExcludes 方法抛出一个异常, 这里应该会终止 SpringBoot 的启动
         if (!invalidExcludes.isEmpty()) {
             handleInvalidExcludes(invalidExcludes); // 不深入讲了
         }
     }
  5. removeALl
    configurations.removeAll(exclusions);
    从自动配置类集合中, 移除掉我们配置的排除类.
  6. getConfigurationClassFilter
    configurations = getConfigurationClassFilter().filter(configurations);
    先来看看 getConfigurationClassFilter() 方法
     private ConfigurationClassFilter getConfigurationClassFilter() {
         if (this.configurationClassFilter == null) {
       // 1. 获取 spring.factories 中的过滤器
             List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
             for (AutoConfigurationImportFilter filter : filters) {
                 invokeAwareMethods(filter); // 2. 遍历过滤器赋值注入一些环境参数
             }
       // 3. 将过滤器赋值到局部变量
             this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
         }
         return this.configurationClassFilter;
     }

    过滤器的初始化我们分三步来讲解:

  7. 我们先来康康 getAutoConfigurationImportFilters 的过程
    protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    // 还是熟悉的 loadFactories, 略过不讲了
    // 从 spring.factories 中读取 key 为 AutoConfigurationImportFilter.class 的过滤器
       return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
    }
  8. 然后是执行过滤器 invokeAwareMethods(filter);, 康康代码:
    private void invokeAwareMethods(Object instance) {
       if (instance instanceof Aware) {
           if (instance instanceof BeanClassLoaderAware) {
               ((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
           }
           if (instance instanceof BeanFactoryAware) {
               ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
           }
           if (instance instanceof EnvironmentAware) {
               ((EnvironmentAware) instance).setEnvironment(this.environment);
           }
           if (instance instanceof ResourceLoaderAware) {
               ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
           }
       }
    }

    发现只是判断过滤器实现了哪些接口, 注入了一些基本环境参数, 我们回到上一步代码接着往下看看

  9. new ConfigurationClassFilter
    this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
       ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
           this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
           this.filters = filters;
       }

发现只是将 filters 集合包装了一下, 还执行了一个 loadMetadata 方法:

  protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, PATH); // 1. 调用了下方的重载方法
    }

  static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        try {
      // 2. 加载 spring-autoconfigure-metadata.properties 文件
            Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
                    : ClassLoader.getSystemResources(path);
      // 3. 将 metadata 文件转成 Properties 格式对象
            Properties properties = new Properties();
            while (urls.hasMoreElements()) {
                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
            }
            return loadMetadata(properties); // 4. 再次调用重载方法
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
        }
    }

  static AutoConfigurationMetadata loadMetadata(Properties properties) {
    // 5. 最后创建了一个 porperties 的包装类返回
        return new PropertiesAutoConfigurationMetadata(properties);
    }

  private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {

    private final Properties properties;

    PropertiesAutoConfigurationMetadata(Properties properties) {
      this.properties = properties;
    }
    // ...
  }

稍微总结一下过滤器的初始化 getConfigurationClassFilter, 将 spring.factories 中的 filter, 和 spring-autoconfigure-metadata.properties 中的每一行数据读取了出来, 包装成了一个对象.
7. filter
configurations = getConfigurationClassFilter().filter(configurations);
那我们接着康康 filter 这个方法:

    List<String> filter(List<String> configurations) {
            long startTime = System.nanoTime();
            String[] candidates = StringUtils.toStringArray(configurations); // 1. 将自动配置类转成 String 数组
            boolean skipped = false;
            for (AutoConfigurationImportFilter filter : this.filters) { // 2. 遍历每一个 filter
        // 3. 将每个 filter 中的 match 方法, 判断自动配置类是否满足启动条件
        //   filter 是一个过滤器, metadata 则是过滤器过滤自动配置类需要依赖的数据
        //   结合过滤器和 metadata 则可以得出符合条件的自动配置类
                boolean[] match = filter.match(candidates, this.autoConfigurationMetadata); // 4. 返回自动配置类是否满足条件的 bool 数组
                for (int i = 0; i < match.length; i++) {
                    if (!match[i]) {
                        candidates[i] = null; // 5. 将不满足自动配置条件的自动配置类删除
                        skipped = true;
                    }
                }
            }
            if (!skipped) {
                return configurations;
            }
      // 6. 最后将满足条件的自动配置类返回
            List<String> result = new ArrayList<>(candidates.length);
            for (String candidate : candidates) {
                if (candidate != null) {
                    result.add(candidate);
                }
            }
            if (logger.isTraceEnabled()) {
                int numberFiltered = configurations.size() - result.size();
                logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                        + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
            }
            return result;
        }

看完简介, 我们结合 spring.factoriesspring-autoconfigure-metadata.properties 接着分析一下 3 和 4 之间的代码流程
factoriesmetadata 中的自动配置类数据起到一一对应, 从 factories 中读取出的配置类, 在 metadata 中会有一条匹配规则:

  org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration=
  org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplaten
  org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration=
  org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitMessagingTemplate

比如我需要找 RabbitAutoConfiguration 自动配置类的启动条件, 会从 metadata 中读取出这一行:

  org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplaten

匹配规则大概格式这样: 自动配置类全类名.匹配条件=匹配条件参数
所以, RabbitAutoConfiguraion 需要满足以下条件才加载: ClassPath 中包含 com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplaten 这两个类.
匹配条件不止一种, 一共如下:

  • ConditionalOnBean: Spring 容器中是否包含 Bean
  • ConditionalOnClass: ClassPath 中是否包含 Bean
  • ConditionalOnCloudPlatform: 是否运行在指定的云平台上
  • ConditionalOnExpression: SpEL 表达式结果为 true 时
  • ConditionalOnJava: 是否满足指定的 java 版本
  • ConditionalOnJndi: 满足指定的 Jndi
  • ConditionalOnMissingBean: Spring 容器中不存在某些 Bean 时生效
  • ConditionalOnMissingClass: ClassPath 中不存在某些 Class 时生效
  • ConditionalOnNotWebApplication: 非 Web 环境下 SpringBoot 运行时生效
  • ConditionalOnProperty: SpringBoot 参数设置值相等生效
  • ConditionalOnResource: 指定的文件存在时生效
  • ConditionalOnSingleCandidate: 当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • ConditionalOnWarDeployment: 传统 WAR 包部署时生效
  • ConditionalOnWebApplication: SpringBoot 以 Web type 运行时生效
    深入 match 的代码就不讲了, 太过底层繁杂, 略过.
  1. fireAutoConfigurationImportEvents
     private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
     // 1. 从 spring.factories 中读取 key 为 AutoConfigurationImportListener 的监听器集合
         List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
         if (!listeners.isEmpty()) {
       // 2. 将已过滤的自动配置类集合, 和排除类集合(没错,第3次了)构建一个 Spring 事件
             AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
             for (AutoConfigurationImportListener listener : listeners) {
                 invokeAwareMethods(listener); // 3. 监听器环境参数注入
                 listener.onAutoConfigurationImportEvent(event); // 4. 让监听器执行事件, 没有广播
             }
         }
     }
  2. return
    最后返回自动配置类和排除类集合的包装类.
     return new AutoConfigurationEntry(configurations, exclusions);

    最终向 Spring 返回了过滤后自动配置类的 String 数组, 交由 Spring 执行自动配置类.

         return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

SpringBoot 自动配置/装配的源码大概就这样写完了.

4) 总结

  • Maven 相关
    • parent 中定义了基础包和官方 starter 的版本号
    • 一个 starter 中传递依赖了很多子包
  • @SpringApplication
    1. @SpringBootConfiguration: 传递注解 @Configuration
    2. @EnableAutoConfiguration:
      1. @AutoConfigurationPackage: 将启动类所在包名包装成 BasePackages, 加入 DI 容器中.
      2. @Import(AutoConfigurationImportSelector.class):
        1. spring.factories 中加载 keyEnableAutoConfiguration 的自动配置类 String 集合.
        2. 对自动配置类 String 集合去重, 排除.
        3. spring.factories 中加载 keyAutoConfigurationImportFilter 的过滤器.
        4. 结合过滤器, 和 META-INF 下的 metadata 中的条件数据, 对自动配置类做一个去重.
        5. 将过滤后的自动配置类交由 Spring 容器执行.
    3. @ScanComponent 基于上一步中得到的 BasePackage, 扫描启动类路径下的所有组件加入容器.

本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

SpringCloud 优化

1) Eureka Server 优化

1.1) 自我保护

有两个 EurekaClient 集群, A 集群服务量 10, B 集群服务量 1000.
A B 集群中各有 3 台机器失去心跳.
A 集群服务少网络抖动概率相对少, 自我保护可能把宕机节点保护下来导致服务不可用;
B 集群大, 保护阈值高, 一般不会出现大量服务宕机, 且由于集群大网络抖动概率大, 建议开启.
总结:

  • 服务关闭自我保护
  • 服务开启自我保护
    eureka:
    server:
      enable-self-preservation: false # 关闭自我保护
      renewal-parcent-thresshold: 0.85 # 自我保护阈值
      eviction-interval-timer-in-ms: 1000 # 剔除服务时间间隔
      use-read-only-response-cache: false # 关闭三层缓存
      response-cache-update-interval-ms: 1000 # 设置 readWrite 和 readOnly 同步时间间隔

1.2) 服务下线监控

阅读 EurekaServer 源码可得知, 发生服务下线时, 会发送 EurekaInstanceCanceledEvent 事件, 我们可以监听这个 Spring 事件, 来发送做一些处理(如发邮件)

1.3) Eureka 三级缓存

这块具体可以参考 com.netflix.eureka.registry.ResponseCacheImpl 的源码.

  • register(ConcurrentHashMap)
  • readWriteCacheMap(Guava LoadingCache)
  • readOnlyCacheMap(ConcurrentHashMap)

注册流程:

  1. client 发起注册请求时, 首先注册至 register, readWriteCacheMap 二级缓存失效
  2. Timmer 定时器任务每隔 response-cache-update-interval-ms 毫秒, 三级缓存从二级缓存中更新已存在的数据, 如为二级缓存失效状态, 触发 guava 回调从同步 register

Get 服务流程:

  1. 从三级缓存找, 找不到就从二级缓存取并更新, 若二级缓存失效, 则触发 Guava 回调从一级缓存同步.
Title - Artist
0:00