SpringCloud部分源碼解析

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很龐大,不可能一一分析,文本的主要目的就是教大家如何分析源碼,從何處下手,以便大家可以按照這種思路繼續跟蹤下去。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章