SpringCloud集成了很多第三方框架,把它的全部源碼拿出來解析幾本書都講不完,也不太現實,本文帶領讀者分析其中一小部分源碼(其餘源碼讀者有興趣可以繼續跟進),包括eureka-server、config、zuul的starter部分,分析其啓動原理。
如果我們開發出一套框架,要和SpringBoot集成,就需要放到它的starter裏。因此我們分析啓動原理,直接從每個框架的starter開始分析即可。
Eureka-Server源碼解析
我們知道,要實現註冊與發現,需要在啓動類加上@EnableEurekaServer註解,我們進入其源碼:
@EnableDiscoveryClient//表示eurekaserver也是一個客戶端服務
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
注意看@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 {
}
}
通過上面的註釋,我們繼續查看EurekaServerAutoConfiguration類的源碼:
@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
/**
* List of packages containing Jersey resources required by the Eureka server
*/
private static String[] EUREKA_PACKAGES = new String[] { "com.netflix.discovery",
"com.netflix.eureka" };
@Autowired
private ApplicationInfoManager applicationInfoManager;
@Autowired
private EurekaServerConfig eurekaServerConfig;
@Autowired
private EurekaClientConfig eurekaClientConfig;
@Autowired
private EurekaClient eurekaClient;
@Autowired
private InstanceRegistryProperties instanceRegistryProperties;
public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson();
@Bean
public HasFeatures eurekaServerFeature() {
return HasFeatures.namedFeature("Eureka Server",
EurekaServerAutoConfiguration.class);
}
//如果eureka.client.registerWithEureka=true,則把自己註冊進去
@Configuration
protected static class EurekaServerConfigBeanConfiguration {
@Bean
@ConditionalOnMissingBean
public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
EurekaServerConfigBean server = new EurekaServerConfigBean();
if (clientConfig.shouldRegisterWithEureka()) {
// Set a sensible default if we are supposed to replicate
server.setRegistrySyncRetries(5);
}
return server;
}
}
//實例化eureka-server的界面
@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
return new EurekaController(this.applicationInfoManager);
}
static {
CodecWrappers.registerWrapper(JACKSON_JSON);
EurekaJacksonCodec.setInstance(JACKSON_JSON.getCodec());
}
@Bean
public ServerCodecs serverCodecs() {
return new CloudServerCodecs(this.eurekaServerConfig);
}
private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {
CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());
return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
}
private static CodecWrapper getFullXml(EurekaServerConfig serverConfig) {
CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getXmlCodecName());
return codec == null ? CodecWrappers.getCodec(CodecWrappers.XStreamXml.class)
: codec;
}
class CloudServerCodecs extends DefaultServerCodecs {
public CloudServerCodecs(EurekaServerConfig serverConfig) {
super(getFullJson(serverConfig),
CodecWrappers.getCodec(CodecWrappers.JacksonJsonMini.class),
getFullXml(serverConfig),
CodecWrappers.getCodec(CodecWrappers.JacksonXmlMini.class));
}
}
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
ServerCodecs serverCodecs) {
this.eurekaClient.getApplications(); // force initialization
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
serverCodecs, this.eurekaClient,
this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}
@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
ServerCodecs serverCodecs) {
return new PeerEurekaNodes(registry, this.eurekaServerConfig,
this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
}
@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);
}
/**
* Register the Jersey filter
*/
@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(
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.
*/
@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<Class<?>>();
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<String, Object>();
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;
}
@Bean
public FilterRegistrationBean traceFilterRegistration(
@Qualifier("webRequestLoggingFilter") Filter filter) {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(filter);
bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return bean;
}
}
這個類上有一個註解:@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),這後面指定的類就是剛纔那個類,而@ConditionalOnBean這個註解的作用是:僅僅在當前上下文中存在某個對象時,纔會實例化一個Bean。
因此,啓動時就會實例化EurekaServerAutoConfiguration這個類。
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
這個註解就是定義了一些eureka的配置項。
Config源碼解析
通過上面的方法,我們找到了ConfigServerAutoConfiguration類:
@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class, TransportConfiguration.class })
public class ConfigServerAutoConfiguration {
}
可以發現這個類是空的,只是多了幾個註解,
@EnableConfigurationProperties(ConfigServerProperties.class)表示開啓config配置屬性。
最核心的註解是:@Import,他將其他一些配置類導入進這個類,其中:
EnvironmentRepositoryConfiguration:環境配置類,內置了以下幾種環境配置:
1.Native
@Configuration
@Profile("native")
protected static class NativeRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Bean
public NativeEnvironmentRepository nativeEnvironmentRepository() {
return new NativeEnvironmentRepository(this.environment);
}
}
2.git
@Configuration
@Profile("git")
protected static class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {}
3.subversion
@Configuration
@Profile("subversion")
protected static class SvnRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Autowired
private ConfigServerProperties server;
@Bean
public SvnKitEnvironmentRepository svnKitEnvironmentRepository() {
SvnKitEnvironmentRepository repository = new SvnKitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
4.vault
@Configuration
@Profile("subversion")
protected static class SvnRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Autowired
private ConfigServerProperties server;
@Bean
public SvnKitEnvironmentRepository svnKitEnvironmentRepository() {
SvnKitEnvironmentRepository repository = new SvnKitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
//從代碼可以看到Git是配置中心默認環境
@Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository() {
MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment);
repository.setTransportConfigCallback(this.transportConfigCallback);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
我們進入MultipleJGitEnvironmentRepository類:
@ConfigurationProperties("spring.cloud.config.server.git")
public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository {
}
這個類表示可以支持配置多個Git倉庫,它繼承自JGitEnvironmentRepository類:
public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
implements EnvironmentRepository, SearchPathLocator, InitializingBean {
/**
* Get the working directory ready.
*/
public String refresh(String label) {
Git git = null;
try {
git = createGitClient();
if (shouldPull(git)) {
fetch(git, label);
// checkout after fetch so we can get any new branches, tags,
// ect.
checkout(git, label);
if (isBranch(git, label)) {
// merge results from fetch
merge(git, label);
if (!isClean(git)) {
logger.warn("The local repository is dirty. Resetting it to origin/" + label + ".");
resetHard(git, label, "refs/remotes/origin/" + label);
}
}
} else {
// nothing to update so just checkout
checkout(git, label);
}
// always return what is currently HEAD as the version
return git.getRepository().getRef("HEAD").getObjectId().getName();
} catch (RefNotFoundException e) {
throw new NoSuchLabelException("No such label: " + label, e);
} catch (NoRemoteRepositoryException e) {
throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
} catch (GitAPIException e) {
throw new NoSuchRepositoryException("Cannot clone or checkout repository: " + getUri(), e);
} catch (Exception e) {
throw new IllegalStateException("Cannot load environment", e);
} finally {
try {
if (git != null) {
git.close();
}
} catch (Exception e) {
this.logger.warn("Could not close git repository", e);
}
}
}
}
refresh方法的作用就是configserver會從我們配置的git倉庫拉取配置下來。
Zuul源碼解析
同理,我們找到Zuul的配置類ZuulProxyAutoConfiguration:
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
@SuppressWarnings("rawtypes")
@Autowired(required = false)
private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();
@Autowired
private DiscoveryClient discovery;
@Autowired
private ServiceRouteMapper serviceRouteMapper;
@Override
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Discovery)", ZuulProxyAutoConfiguration.class);
}
@Bean
@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
public DiscoveryClientRouteLocator discoveryRouteLocator() {
return new DiscoveryClientRouteLocator(this.server.getServletPrefix(), this.discovery, this.zuulProperties,
this.serviceRouteMapper);
}
//以下是過濾器,也就是之前zuul提到的實現的ZuulFilter接口
// pre filters
//路由之前
@Bean
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties,
proxyRequestHelper);
}
// route filters
// 路由時
@Bean
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory<?> ribbonCommandFactory) {
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers);
return filter;
}
@Bean
@ConditionalOnMissingBean(SimpleHostRoutingFilter.class)
public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) {
return new SimpleHostRoutingFilter(helper, zuulProperties);
}
@Bean
public ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener() {
return new ZuulDiscoveryRefreshListener();
}
@Bean
@ConditionalOnMissingBean(ServiceRouteMapper.class)
public ServiceRouteMapper serviceRouteMapper() {
return new SimpleServiceRouteMapper();
}
@Configuration
@ConditionalOnMissingClass("org.springframework.boot.actuate.endpoint.Endpoint")
protected static class NoActuatorConfiguration {
@Bean
public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
ProxyRequestHelper helper = new ProxyRequestHelper();
helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders());
helper.setTraceRequestBody(zuulProperties.isTraceRequestBody());
return helper;
}
}
@Configuration
@ConditionalOnClass(Endpoint.class)
protected static class RoutesEndpointConfiguration {
@Autowired(required = false)
private TraceRepository traces;
@Bean
public RoutesEndpoint zuulEndpoint(RouteLocator routeLocator) {
return new RoutesEndpoint(routeLocator);
}
@Bean
public RoutesMvcEndpoint zuulMvcEndpoint(RouteLocator routeLocator, RoutesEndpoint endpoint) {
return new RoutesMvcEndpoint(endpoint, routeLocator);
}
@Bean
public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
TraceProxyRequestHelper helper = new TraceProxyRequestHelper();
if (this.traces != null) {
helper.setTraces(this.traces);
}
helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders());
helper.setTraceRequestBody(zuulProperties.isTraceRequestBody());
return helper;
}
}
private static class ZuulDiscoveryRefreshListener implements ApplicationListener<ApplicationEvent> {
private HeartbeatMonitor monitor = new HeartbeatMonitor();
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof InstanceRegisteredEvent) {
reset();
}
else if (event instanceof ParentHeartbeatEvent) {
ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
resetIfNeeded(e.getValue());
}
else if (event instanceof HeartbeatEvent) {
HeartbeatEvent e = (HeartbeatEvent) event;
resetIfNeeded(e.getValue());
}
}
private void resetIfNeeded(Object value) {
if (this.monitor.update(value)) {
reset();
}
}
private void reset() {
this.zuulHandlerMapping.setDirty(true);
}
}
}
通過@Import註解可以找到幾個類:
RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration、RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration、RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration
我們知道zuul提供網關能力,通過上面這幾個類就能分析到,它內部其實也是通過接口請求,找到每個服務提供的接口地址。
進入RibbonCommandFactoryConfiguration類:
public class RibbonCommandFactoryConfiguration {
//以下提供了3個不同的請求模式
@Configuration
@ConditionalOnRibbonRestClient
protected static class RestClientRibbonConfiguration {
@Autowired(required = false)
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
@Bean
@ConditionalOnMissingBean
public RibbonCommandFactory<?> ribbonCommandFactory(
SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
return new RestClientRibbonCommandFactory(clientFactory, zuulProperties,
zuulFallbackProviders);
}
}
@Configuration
@ConditionalOnRibbonOkHttpClient
@ConditionalOnClass(name = "okhttp3.OkHttpClient")
protected static class OkHttpRibbonConfiguration {
@Autowired(required = false)
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
@Bean
@ConditionalOnMissingBean
public RibbonCommandFactory<?> ribbonCommandFactory(
SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
return new OkHttpRibbonCommandFactory(clientFactory, zuulProperties,
zuulFallbackProviders);
}
}
@Configuration
@ConditionalOnRibbonHttpClient
protected static class HttpClientRibbonConfiguration {
@Autowired(required = false)
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
@Bean
@ConditionalOnMissingBean
public RibbonCommandFactory<?> ribbonCommandFactory(
SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
return new HttpClientRibbonCommandFactory(clientFactory, zuulProperties, zuulFallbackProviders);
}
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRibbonHttpClientCondition.class)
@interface ConditionalOnRibbonHttpClient { }
private static class OnRibbonHttpClientCondition extends AnyNestedCondition {
public OnRibbonHttpClientCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@Deprecated //remove in Edgware"
@ConditionalOnProperty(name = "zuul.ribbon.httpclient.enabled", matchIfMissing = true)
static class ZuulProperty {}
@ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true)
static class RibbonProperty {}
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRibbonOkHttpClientCondition.class)
@interface ConditionalOnRibbonOkHttpClient { }
private static class OnRibbonOkHttpClientCondition extends AnyNestedCondition {
public OnRibbonOkHttpClientCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@Deprecated //remove in Edgware"
@ConditionalOnProperty("zuul.ribbon.okhttp.enabled")
static class ZuulProperty {}
@ConditionalOnProperty("ribbon.okhttp.enabled")
static class RibbonProperty {}
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRibbonRestClientCondition.class)
@interface ConditionalOnRibbonRestClient { }
private static class OnRibbonRestClientCondition extends AnyNestedCondition {
public OnRibbonRestClientCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@Deprecated //remove in Edgware"
@ConditionalOnProperty("zuul.ribbon.restclient.enabled")
static class ZuulProperty {}
@ConditionalOnProperty("ribbon.restclient.enabled")
static class RibbonProperty {}
}
}
總結
前面帶領大家分析了一小段源碼,SpringCloud很龐大,不可能一一分析,文本的主要目的就是教大家如何分析源碼,從何處下手,以便大家可以按照這種思路繼續跟蹤下去。