Spring Cloud EurekaServer初始化和启动流程分析

718 阅读9分钟

理解@EnableEurekaServer

@EnableXXX编程模型是Spring Framework中Enable模块驱动,其对应的元注解@Import,用于导入配置类(Configuration Class)。

/**
 * Annotation to activate Eureka Server related configuration.
 * {@link EurekaServerAutoConfiguration}
 *
 * @author Dave Syer
 * @author Biju Kunjummen
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {

}

上面的英文注释说@EnableEurekaServer注解用于激活Eureka Server的相关配置。

接下来我们探索一下Eureka Server的配置是如何被激活的。

EurekaServerMarkerConfiguration

我们重点关注@Import注解导入的EurekaServerMarkerConfiguration类。

/**
 * Responsible for adding in a marker bean to activate
 * {@link EurekaServerAutoConfiguration}.
 *
 * @author Biju Kunjummen
 */
@Configuration
public class EurekaServerMarkerConfiguration {

	@Bean
	public Marker eurekaServerMarkerBean() {
		return new Marker();
	}

	class Marker {

	}
}

这是一个Configuration Class,注册了一个Bean : org.springframework.cloud.netflix.eureka.server.EurekaServerMarkerConfiguration.Marker,再顺着这个类上的英文注释,我们找到EurekaServerAutoConfiguration类看一下。

EurekaServerAutoConfiguration

@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
		InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
    ...
}

可以看到,EurekaServerAutoConfiguration类上有一行标注:@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),前面我们说到EurekaServerMarkerConfiguration类注册一个EurekaServerMarkerConfiguration.Marker类型的Bean ,使得这里的@ConditionalOnBean条件满足,从而激活了EurekaServerAutoConfiguration配置类。

spring-cloud-netflix-eureka-server jar包下META-INF/spring.factories文件中,我们找到了如下的配置:

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

在基于Spring Boot的应用中,我们知道应用启动时会扫描所有jar内部的META-INF/spring.factories,当发现org.springframework.boot.autoconfigure.EnableAutoConfiguration=XXX的配置时,会将其作为自动配置类加载并处理。这就是Spring Boot自动配置的基本原理,详细原理可以参考@EnableAutoConfiguration注解去理解。

有了上面的基础后,不难得知,激活了EurekaServerAutoConfiguration类,就等于激活了Eureka Server的配置。

EurekaServerInitializerConfiguration

EurekaServerAutoConfiguration类上的注解:@Import(EurekaServerInitializerConfiguration.class) 导入了EurekaServerInitializerConfiguration配置类。

@Configuration
public class EurekaServerInitializerConfiguration
		implements ServletContextAware, SmartLifecycle, Ordered 
    ...

类实现了ServletContextAwareSmartLifecycleOrdered接口。

@Autowired
private EurekaServerConfig eurekaServerConfig;
@Autowired
private ApplicationContext applicationContext;

@Autowired
private EurekaServerBootstrap eurekaServerBootstrap;

private boolean running;

private int order = 1;

上面是EurekaServerInitializerConfiguration类依赖注入的外部Bean 和自身的一些属性。

SmartLifecycle接口可以看做是对Spring应用上下文生命周期的补充,在常用的AbstractApplicationContext#refresh/close方法以外,还有AbstractApplicationContext#start方法。

上面这句话来自于偶像小马哥(Java劝退师,Dubbo PMC,Spring Cloud Alibaba架构师)在极客时间的《Spring编程思想核心篇》课程中讲过的一句话。:kissing_smiling_eyes:

public interface SmartLifecycle extends Lifecycle, Phased {
   
}
public interface Lifecycle {
  void start();
  void stop();
  boolean isRunning();
}    

AbstractApplicationContext

	@Override
	public void start() {
		getLifecycleProcessor().start();
		publishEvent(new ContextStartedEvent(this));
	}

	@Override
	public void stop() {
		getLifecycleProcessor().stop();
		publishEvent(new ContextStoppedEvent(this));
	}

简单概括一下就是:

  • AbstractApplicationContext#start
    • Lifecycle#start

EurekaServerInitializerConfiguration#start

当所有单实例 Bean 都初始化完成后,就会回调所有实现了 Lifecycle 接口的 Bean 的 start 方法!

    @Override
	public void start() {
        // 先创建一个线程 异步的方式
		new Thread(() -> {
			try {
				// TODO: is this class even needed now?
                // 初始化、启动EurekaServer
				eurekaServerBootstrap.contextInitialized(
						EurekaServerInitializerConfiguration.this.servletContext);
				log.info("Started Eureka Server");
                
				// 发布Eureka已注册的事件
				publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                
                // 修改EurekaServer的运行状态
				EurekaServerInitializerConfiguration.this.running = true;
                // 发布EurekaServer已启动的事件
				publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
			}
			catch (Exception ex) {
				// Help!
				log.error("Could not initialize Eureka servlet context", ex);
			}
		}).start();
	}

EurekaServerAutoConfiguration中,我们注册了EurekaServer相关的一些Bean:

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

@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
			EurekaServerContext serverContext) {
    
	return new EurekaServerBootstrap(this.applicationInfoManager,
		this.eurekaClientConfig, this.eurekaServerConfig, registry,serverContext);
}

EurekaServerBootstrap Bean通过依赖注入 注入给了EurekaServerInitializerConfiguration类。

我们先总结一下EurekaServerInitializerConfiguration#start方法的逻辑

  1. 初始化、启动EurekaServer
  2. 发布Eureka已注册的事件
  3. 修改EurekaServer的运行状态
  4. 发布EurekaServer已启动的事件

我们先看最重要的逻辑:初始化、启动EurekaServer

public class EurekaServerBootstrap {
    ...
    public void contextInitialized(ServletContext context) {
		try {
            // 初始化Eureka的运行环境
			initEurekaEnvironment();
            // 初始化Eureka的运行上下文
			initEurekaServerContext();

			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);
		}
	}   
    ...    
}

EurekaServer 本身应该是一个完整的 Servlet 应用,在原生的 EurekaServer 中,EurekaServer 的启动类 EurekaBootStrap 类会实现 ServletContextListener 接口( Servlet3.0 规范)来引导启动 EurekaServer 。而基于 SpringBoot 构建的应用一般使用嵌入式 Web 容器,没有所谓 Servlet3.0 规范作用的机会了,所以需要另外的启动方式,于是 SpringCloud 在整合这部分时,借助 ApplicationContext 中支持的 LifeCycle 机制,以此来触发 EurekaServer 的启动。

我们的EurekaServer在Spring Boot应用中要完成初始化和启动操作,不能通过监听Servlet容器来完成(内嵌式Web容器不支持),我们只能先注入外部ServletContext对象,然后通过ApplicationContext去触发LifeCycle的生命周期回调,来启动EurekaServer。这个LifeCycle对Spring应用中的生命周期作了补充,同时也触发了EurekaServer的启动。我们在Spring Boot应用中,可以采取这种方式来完成我们想要做的事情。(这是一个可扩展的点,主动牵强启动)

  1. 初始化Eureka的运行环境
   private static final String TEST = "test";

	private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment";

	private static final String EUREKA_ENVIRONMENT = "eureka.environment";

	private static final String DEFAULT = "default";

	private static final String ARCHAIUS_DEPLOYMENT_DATACENTER = "archaius.deployment.datacenter";

	private static final String EUREKA_DATACENTER = "eureka.datacenter";


protected void initEurekaEnvironment() throws Exception {
		log.info("Setting the eureka configuration..");
    
		// eureka.datacenter
        // Eureka数据中心
		String dataCenter = ConfigurationManager.getConfigInstance()
				.getString(EUREKA_DATACENTER);
		if (dataCenter == null) {
			log.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
            
			ConfigurationManager.getConfigInstance()
					.setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
		}
    
		else {
            // archaius.deployment.datacenter
			ConfigurationManager.getConfigInstance()
					.setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
		}
    	
    	// eureka.environment
        // Eureka 环境
		String environment = ConfigurationManager.getConfigInstance()
				.getString(EUREKA_ENVIRONMENT);
		if (environment == null) {
            // test
			ConfigurationManager.getConfigInstance()
					.setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
            
			log.info(
					"Eureka environment value eureka.environment is not set, defaulting to test");
		}
		else {
            // archaius.deployment.environment
			ConfigurationManager.getConfigInstance()
					.setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, environment);
		}
}
  1. 初始化EurekaServer运行的上下文
protected void initEurekaServerContext() throws Exception {
		// For backward compatibility 兼容低版本Eureka
		JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
				XStream.PRIORITY_VERY_HIGH);
		XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
				XStream.PRIORITY_VERY_HIGH);

		if (isAws(this.applicationInfoManager.getInfo())) {
			this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
					this.eurekaClientConfig, this.registry, this.applicationInfoManager);
			this.awsBinder.start();
		}
    
		// 1. 注册EurekaServerContextHolder,通过它可以很方便的获取EurekaServerContext
		EurekaServerContextHolder.initialize(this.serverContext);

		log.info("Initialized server context");

		// Copy registry from neighboring eureka node
        // 2. Eureka复制集群节点注册表
		int registryCount = this.registry.syncUp();
		this.registry.openForTraffic(this.applicationInfoManager, registryCount);

		// Register all monitoring statistics.
		EurekaMonitors.registerAllStats();
	}

这里很简单,后面很方便就可以获取EurekaServerContext

public class EurekaServerContextHolder {

    private final EurekaServerContext serverContext;

    private EurekaServerContextHolder(EurekaServerContext serverContext) {
        this.serverContext = serverContext;
    }

    public EurekaServerContext getServerContext() {
        return this.serverContext;
    }

    private static EurekaServerContextHolder holder;
    
	// 注册EurekaServerContextHolder
    public static synchronized void initialize(EurekaServerContext serverContext) {
        holder = new EurekaServerContextHolder(serverContext);
    }

    public static EurekaServerContextHolder getInstance() {
        return holder;
    }
}

这里我们没有说明Eureka复制集群节点注册表的逻辑,文章后面部分会详细说明

EurekaServerAutoConfiguration配置的核心组件

EurekaController

@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
	return new EurekaController(this.applicationInfoManager);
}
	private ApplicationInfoManager applicationInfoManager;

	public EurekaController(ApplicationInfoManager applicationInfoManager) {
		this.applicationInfoManager = applicationInfoManager;
	}

这个Controller是展示控制台页面的

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());
}

EurekaServer 集群中节点之间同步微服务实例注册表的核心组件

PeerEurekaNodes

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

PeerEurekaNodes是微服务实例的节点集合。一个 PeerEurekaNode 就是一个微服务节点实例的包装,PeerEurekaNodes 就是这组 PeerEurekaNode 的集合,这种节点是可以被 EurekaServer 集群中的各个注册中心节点共享的(PeerAwareInstanceRegistry)。

@Singleton
public class PeerEurekaNodes {
    // 集群间节点同步的核心组件
    protected final PeerAwareInstanceRegistry registry;
    
    ...
	// 节点集合
    private volatile List<PeerEurekaNode> peerEurekaNodes = Collections.emptyList();
    // 所有节点所在 url
    private volatile Set<String> peerEurekaNodeUrls = Collections.emptySet();
    
    // 执行定时任务的线程池
    private ScheduledExecutorService taskExecutor;
}

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);
                    }

                }
            };
            // 默认情况下每隔10分钟会同步一次集群节点
            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());
        }
    }

PeerEurekaNodes#shutdown

 public void shutdown() {
        // 停止线程池
        taskExecutor.shutdown();
        //移除所有节点信息
        List<PeerEurekaNode> toRemove = this.peerEurekaNodes;

        this.peerEurekaNodes = Collections.emptyList();
        this.peerEurekaNodeUrls = Collections.emptySet();

        for (PeerEurekaNode node : toRemove) {
            // Shuts down all resources used for peer replication.
            node.shutDown();
        }
  }

EurekaServerContext

@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
		PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
    // 本地服务器上下文,并将 getter 方法暴露给本地服务器的组件(例如注册表)
	return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
		registry, peerEurekaNodes, this.applicationInfoManager);
}
@Singleton
public class DefaultEurekaServerContext implements EurekaServerContext {
    
    @PostConstruct
    @Override
    public void initialize() {
        logger.info("Initializing ...");
        
        // 初始化PeerEurekaNodes,定时更新集群节点
        peerEurekaNodes.start();
        try {
            // 初始化PeerAwareInstanceRegistry
            registry.init(peerEurekaNodes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        
        logger.info("Initialized");
    }

    @PreDestroy
    @Override
    public void shutdown() {
        logger.info("Shutting down ...");
        registry.shutdown();
        peerEurekaNodes.shutdown();
        logger.info("Shut down");
    }
}

PeerAwareInstanceRegistryImpl#init

初始化PeerAwareInstanceRegistry

    private final MeasuredRate numberOfReplicationsLastMin;
    ...
    protected volatile PeerEurekaNodes peerEurekaNodes;
    ...

	@Override
    public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
        // 启动续订租约的频率统计器
        this.numberOfReplicationsLastMin.start();
        this.peerEurekaNodes = peerEurekaNodes;
        initializedResponseCache();
        // 开启续订租约最低阈值检查的定时任务
        scheduleRenewalThresholdUpdateTask();
        // 初始化远程分区注册中心
        initRemoteRegionRegistry();

        try {
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
        }
 }
  1. 启动续订租约的频率统计器

    private final long sampleInterval;
    private final Timer timer;
    
    private volatile boolean isActive; 
    
    private final AtomicLong lastBucket = new AtomicLong(0);
    private final AtomicLong currentBucket = new AtomicLong(0);
    
    public synchronized void start() {
            // 隔一段时间重置 lastBucket 和 currentBucket 的值为 0
            if (!isActive) {
                timer.schedule(new TimerTask() {
    
                    @Override
                    public void run() {
                        try {
                            // Zero out the current bucket.
                            lastBucket.set(currentBucket.getAndSet(0));
                        } catch (Throwable e) {
                            logger.error("Cannot reset the Measured Rate", e);
                        }
                    }
                }, sampleInterval, sampleInterval);
    
                isActive = true;
            }
     }
    

    这个计算次数会体现在 Eureka 的控制台,以及配合 Servo 完成续约次数监控

  2. 开启续订租约最低阈值检查的定时任务

    private void scheduleRenewalThresholdUpdateTask() {
            timer.schedule(new TimerTask() {
                               @Override
                               public void run() {
                                   updateRenewalThreshold();
                               }
                           }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
                    serverConfig.getRenewalThresholdUpdateIntervalMs());
    }
    

    配置项中的默认时间间隔可以发现是 15 分钟

    /**
         * Updates the <em>renewal threshold</em> based on the current number of
         * renewals. The threshold is a percentage as specified in
         * {@link EurekaServerConfig#getRenewalPercentThreshold()} of renewals
         * received per minute {@link #getNumOfRenewsInLastMin()}.
         */
        private void updateRenewalThreshold() {
            try {
                Applications apps = eurekaClient.getApplications();
                int count = 0;
                // 检查当前已经注册到本地的服务实例是否还保持连接
                for (Application app : apps.getRegisteredApplications()) {
                    for (InstanceInfo instance : app.getInstances()) {
                        // always return true
                        if (this.isRegisterable(instance)) {
                            ++count;
                        }
                    }
                }
                
                // 会检查统计好的数量是否比预期的多,如果统计好的服务实例数比预期的数量多,证明 出现了新的服务注册,要替换下一次统计的期望数量值,以及重新计算接下来心跳的数量统计
                synchronized (lock) {
                    // Update threshold only if the threshold is greater than the
                    // current expected threshold or if self preservation is disabled.
                    // 0.85 * 客户端数量
                    if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
                            || (!this.isSelfPreservationModeEnabled())) {
                        this.expectedNumberOfClientsSendingRenews = count;
                        updateRenewsPerMinThreshold();
                    }
                }
                logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
            } catch (Throwable e) {
                logger.error("Cannot update renewal threshold", e);
            }
    }
    
    private int expectedClientRenewalIntervalSeconds = 30;
    private double renewalPercentThreshold = 0.85;
    
     protected void updateRenewsPerMinThreshold() {
            this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
                    * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
                    * serverConfig.getRenewalPercentThreshold());
     }
    // 1*(60/30)*0.85
    

    每隔 30 秒发一次心跳(一分钟心跳两次),而且必须所有的服务实例的心跳总数要达到前面计算数量的 85% 才算整体微服务正常,其实这也就是 EurekaServer 的自我保护机制

  3. 初始化远程分区注册中心

    protected void initRemoteRegionRegistry() throws MalformedURLException {
            Map<String, String> remoteRegionUrlsWithName = serverConfig.getRemoteRegionUrlsWithName();
            if (!remoteRegionUrlsWithName.isEmpty()) {
                allKnownRemoteRegions = new String[remoteRegionUrlsWithName.size()];
                int remoteRegionArrayIndex = 0;
                for (Map.Entry<String, String> remoteRegionUrlWithName : remoteRegionUrlsWithName.entrySet()) {
                    RemoteRegionRegistry remoteRegionRegistry = new RemoteRegionRegistry(
                            serverConfig,
                            clientConfig,
                            serverCodecs,
                            remoteRegionUrlWithName.getKey(),
                            new URL(remoteRegionUrlWithName.getValue()));
                    regionNameVSRemoteRegistry.put(remoteRegionUrlWithName.getKey(), remoteRegionRegistry);
                    allKnownRemoteRegions[remoteRegionArrayIndex++] = remoteRegionUrlWithName.getKey();
                }
            }
            logger.info("Finished initializing remote region registries. All known remote regions: {}",
                    (Object) allKnownRemoteRegions);
        }
    

    处理在其他区域中运行的 eureka 服务上需要完成的所有注册表操作。主要操作包括从远程区域中获取注册表信息以及定期获取增量信息。

EurekaServerBootstrap

@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
			EurekaServerContext serverContext) {
	return new EurekaServerBootstrap(this.applicationInfoManager,
				this.eurekaClientConfig, this.eurekaServerConfig, registry,
				serverContext);
}

引导启动EurekaServer

ServletContainer

	/**
	 * Register the Jersey filter.
	 * @param eurekaJerseyApp an {@link Application} for the filter to be registered
	 * @return a jersey {@link FilterRegistrationBean}
	 */
	@Bean
	public FilterRegistrationBean jerseyFilterRegistration(
			javax.ws.rs.core.Application eurekaJerseyApp) {
		FilterRegistrationBean bean = new FilterRegistrationBean();
		bean.setFilter(new ServletContainer(eurekaJerseyApp));
		bean.setOrder(Ordered.LOWEST_PRECEDENCE);
		bean.setUrlPatterns(
                // "eureka/"
				Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));

		return bean;
	}
package com.sun.jersey.spi.container.servlet;
public class ServletContainer extends HttpServlet implements Filter {
    
}

这是jersy ,它是一个类似于 Spring Web MVC的框架,由于 Eureka 本身也是一个 Servlet 应用,只是它使用的 Web 层框架不是 SpringWebMvc 而是 Jersey 而已,Jersey 在 Eureka 的远程请求、心跳包发送等环节起到至关重要的作用,而这个 ServletContainer 的作用,可以理解为 SpringWebMvc 中的 DispatcherServlet ,以及 Struts2 中的 StrutsPrepareAndExecuteFilter

这里面有一个 DEFAULT_PREFIX ,翻过去发现前缀是 /eureka ,这也解释了为什么微服务注册到 EurekaServer 的时候,defaultZone 要在 ip:port 后面加上 /eureka ,以及后面在调用 EurekaServer 的一些接口时前面也要加上 /eureka

javax.ws.rs.core.Application

   @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;
	}

HttpTraceFilter

   @Bean
	public FilterRegistrationBean traceFilterRegistration(
			@Qualifier("httpTraceFilter") Filter filter) {
		FilterRegistrationBean bean = new FilterRegistrationBean();
		bean.setFilter(filter);
		bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
		return bean;
	}
public class HttpTraceAutoConfiguration {
   
    
    @Configuration
	@ConditionalOnWebApplication(type = Type.SERVLET)
	static class ServletTraceFilterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public HttpTraceFilter httpTraceFilter(HttpTraceRepository repository, HttpExchangeTracer tracer) {
			return new HttpTraceFilter(repository, tracer);
		}

	}
}

HttpTraceFilter Bean :记录所有请求日志的Servlet过滤器。

EurekaServerConfigBeanConfiguration

    @Configuration
	protected static class EurekaServerConfigBeanConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig)  {
            // 注册了默认的 EurekaServer 的配置模型 EurekaServerConfigBean 
			EurekaServerConfigBean server = new EurekaServerConfigBean();
			if (clientConfig.shouldRegisterWithEureka()) {
				// Set a sensible default if we are supposed to replicate
				server.setRegistrySyncRetries(5);
			}
			return server;
		}

}

总结