一.UML图
二.源码解析
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@RestController
@EnableHystrix
@EnableHystrixDashboard
@EnableCircuitBreaker
public class ServiceHiApplication {
/**
* 访问地址 http://localhost:8762/actuator/hystrix.stream
* @param args
*/
public static void main(String[] args) {
SpringApplication.run( ServiceHiApplication.class, args );
}
@Value("${server.port}")
String port;
@RequestMapping("/hi")
@HystrixCommand(fallbackMethod = "hiError")
public String home(@RequestParam(value = "name", defaultValue = "forezp") String name) {
return "hi " + name + " ,i am from port:" + port;
}
public String hiError(String name) {
return "hi,"+name+",sorry,error!";
}
}
1.客户端配置
从以上代码进入@EnableDiscoveryClient注解如下
看到这个注解上使用@Import注解,表示在使用注解时候导入那些bean
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
boolean autoRegister() default true;
}
进入EnableDiscoveryClientImportSelector.class
@Order(2147483547)
public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector {
public EnableDiscoveryClientImportSelector() {
}
public String[] selectImports(AnnotationMetadata metadata) {
//得到@Import这个注解注释@EnableDiscoveryClient 的属性
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(this.getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
if(autoRegister) {
//如果是自动注册就是实例化AutoServiceRegistrationConfiguration
ArrayList env = new ArrayList(Arrays.asList(imports));
env.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = (String[])env.toArray(new String[0]);
} else {
Environment env1 = this.getEnvironment();
if(ConfigurableEnvironment.class.isInstance(env1)) {
ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env1;
LinkedHashMap map = new LinkedHashMap();
map.put("spring.cloud.service-registry.auto-registration.enabled", Boolean.valueOf(false));
MapPropertySource propertySource = new MapPropertySource("springCloudDiscoveryClient", map);
configEnv.getPropertySources().addLast(propertySource);
}
}
return imports;
}
进入父类SpringFactoryImportSelector
并使用SpringFactoriesLoader加载需要的bean
public abstract class SpringFactoryImportSelector implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware {
private ClassLoader beanClassLoader;
private Class annotationClass = GenericTypeResolver.resolveTypeArgument(this.getClass(), SpringFactoryImportSelector.class);
private Environment environment;
private final Log log = LogFactory.getLog(SpringFactoryImportSelector.class);
protected SpringFactoryImportSelector() {
}
public String[] selectImports(AnnotationMetadata metadata) {
if(!this.isEnabled()) {
return new String[0];
} else {
//annotionClass为父类的泛型即是EnableDiscoveryClient
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(this.annotationClass.getName(), true));
Assert.notNull(attributes, "No " + this.getSimpleName() + " attributes found. Is " + metadata.getClassName() + " annotated with @" + this.getSimpleName() + "?");
//并使用SpringFactoriesLoader加载需要的bean
ArrayList factories = new ArrayList(new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
if(factories.isEmpty() && !this.hasDefaultFactory()) {
throw new IllegalStateException("Annotation @" + this.getSimpleName() + " found, but there are no implementations. Did you forget to include a starter?");
} else {
if(factories.size() > 1) {
this.log.warn("More than one implementation of @" + this.getSimpleName() + " (now relying on @Conditionals to pick one): " + factories);
}
return (String[])factories.toArray(new String[factories.size()]);
}
}
}
进入SpringFactoriesLoader,发现它加载了META-INF/spring.factories下的文件
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = (MultiValueMap)cache.get(classLoader);
if(result != null) {
return result;
} else {
try {
//加载了META-INF/spring.factories下的文件
Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result1 = new LinkedMultiValueMap();
while(ex.hasMoreElements()) {
URL url = (URL)ex.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry entry = (Entry)var6.next();
List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
result1.addAll((String)entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result1);
return result1;
} catch (IOException var9) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
}
}
}
全局搜索META-INF/spring.factories,并在EurakeClient的jar包找到这个配置文件如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
进入EurekaClientConfigServerAutoConfiguration类,客户端的配置就就在EurekaInstanceConfigBean和EurekaClient中
@Configuration
@EnableConfigurationProperties
//这里表示在实例化这个类之前需要其他类也实例化
@ConditionalOnClass({EurekaInstanceConfigBean.class, EurekaClient.class, ConfigServerProperties.class})
public class EurekaClientConfigServerAutoConfiguration {
@Autowired(
required = false
)
private EurekaInstanceConfig instance;
@Autowired(
required = false
)
private ConfigServerProperties server;
public EurekaClientConfigServerAutoConfiguration() {
}
@PostConstruct
public void init() {
if(this.instance != null && this.server != null) {
String prefix = this.server.getPrefix();
if(StringUtils.hasText(prefix)) {
this.instance.getMetadataMap().put("configPath", prefix);
}
}
}
}
进入EurekaClient就能看到配置文件中配置的很多信息,从这里主要使用google诞生的更轻巧的DI容器……Guice! 也是依赖注入的的框架,降低代码的耦合度。主要使用@Inject @ImplementedBy @ProvidedBy注解!
@ImplementedBy(DiscoveryClient.class)
public interface EurekaClient extends LookupService {
Applications getApplicationsForARegion(@Nullable String var1);
Applications getApplications(String var1);
从上可以看出这个发现客服端的类,指定了是由DiscoveryClient实现的。
进入DiscoveryClient
@Singleton
public class DiscoveryClient implements EurekaClient {
//其他的构造函数都是被打上了废弃注解的,只有这个是可用
//在构造这个实例时,还需要注入ApplicationInfoManager , EurekaClientConfig
//这就是客户端主要的配置项
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider backupRegistryProvider) {
this.RECONCILE_HASH_CODES_MISMATCH = Monitors.newCounter("DiscoveryClient_ReconcileHashCodeMismatch");
this.FETCH_REGISTRY_TIMER = Monitors.newTimer("DiscoveryClient_FetchRegistry");
this.REREGISTER_COUNTER = Monitors.newCounter("DiscoveryClient_Reregister");
this.localRegionApps = new AtomicReference();
this.fetchRegistryUpdateLock = new ReentrantLock();
this.remoteRegionVsApps = new ConcurrentHashMap();
this.lastRemoteInstanceStatus = InstanceStatus.UNKNOWN;
this.eventListeners = new CopyOnWriteArraySet();
this.registrySize = 0;
this.lastSuccessfulRegistryFetchTimestamp = -1L;
this.lastSuccessfulHeartbeatTimestamp = -1L;
this.isShutdown = new AtomicBoolean(false);
}
}
首先进入EurekaClientConfig 类
@ImplementedBy(DefaultEurekaClientConfig.class)
public interface EurekaClientConfig {
//
List getEurekaServerServiceUrls(String var1);
String getEurekaServerPort();
boolean shouldUseDnsForFetchingServiceUrls();
boolean shouldRegisterWithEureka();
boolean shouldFetchRegistry();
int getRegistryFetchIntervalSeconds();
int getInstanceInfoReplicationIntervalSeconds();
int getInitialInstanceInfoReplicationIntervalSeconds();
int getEurekaServiceUrlPollIntervalSeconds();
@Nullable
String fetchRegistryForRemoteRegions();
}
这是一个接口,然后指定了实现类。都只有get方法,实现类的get方法再去获取具体的属性。
上图代码有删减,找到了我们平时配置熟悉的一些属性。
进入DefaultEurekaClientConfig
@Singleton
@ProvidedBy(DefaultEurekaClientConfigProvider.class)
public class DefaultEurekaClientConfig implements EurekaClientConfig {
/** @deprecated */
@Deprecated
public static final String DEFAULT_NAMESPACE = "eureka.";
public static final String DEFAULT_ZONE = "defaultZone";
private final String namespace;
private final DynamicPropertyFactory configInstance;
private final EurekaTransportConfig transportConfig;
public DefaultEurekaClientConfig() {
this("eureka");
}
public DefaultEurekaClientConfig(String namespace) {
this.namespace = namespace.endsWith(".")?namespace:namespace + ".";
this.configInstance = Archaius1Utils.initConfig("eureka-client");
this.transportConfig = new DefaultEurekaTransportConfig(namespace, this.configInstance);
}
//对应我们配置eureka.client.server.port: 8762
public String getEurekaServerPort() {
return this.configInstance.getStringProperty(this.namespace + "eurekaServer.port", this.configInstance.getStringProperty(this.namespace + "port", (String)null).get()).get();
}
public boolean shouldRegisterWithEureka() {
return this.configInstance.getBooleanProperty(this.namespace + "registration.enabled", true).get();
}
public String[] getAvailabilityZones(String region) {
return this.configInstance.getStringProperty(this.namespace + region + "." + "availabilityZones", "defaultZone").get().split(",");
}
//对应我们配置eureka.client.serviceUrl.defaultZone: http://localhost:8761/eureka/
public List getEurekaServerServiceUrls(String myZone) {
String serviceUrls = this.configInstance.getStringProperty(this.namespace + "serviceUrl" + "." + myZone, (String)null).get();
if(serviceUrls == null || serviceUrls.isEmpty()) {
serviceUrls = this.configInstance.getStringProperty(this.namespace + "serviceUrl" + ".default", (String)null).get();
}
return (List)(serviceUrls != null?Arrays.asList(serviceUrls.split(",")):new ArrayList());
}
public boolean shouldFetchRegistry() {
return this.configInstance.getBooleanProperty(this.namespace + "shouldFetchRegistry", true).get();
}
}
从代码中就可以找到对应配置属性,代码也是有删减的。所有的配置通过 Archaius1Utils.initConfig("eureka-client");这里获取configInstance来获取。这里使用了Netflix Archaius,想看看的移步。它大概就是一个动态不用重启服务就能获取多种配置的分布式管理配置的框架。然后后这里使用DefaultEurekaClientConfigProvider,作为提供者。
进入DefaultEurekaClientConfigProvider
public class DefaultEurekaClientConfigProvider implements Provider {
@Inject(
optional = true
)
@EurekaNamespace
private String namespace;
//上面这个命名空间不知道哪里来的,以后再看看
private DefaultEurekaClientConfig config;
public DefaultEurekaClientConfigProvider() {
}
public synchronized EurekaClientConfig get() {
if(this.config == null) {
this.config = this.namespace == null?new DefaultEurekaClientConfig():new DefaultEurekaClientConfig(this.namespace);
//这个DiscoveryManager应该是废弃了,点入发现类上有废弃注解
DiscoveryManager.getInstance().setEurekaClientConfig(this.config);
}
return this.config;
}
}
然后框架回去调用get方法注入EurekaClientConfig 。其他的ApplicationInfoManager 等都类似不再赘述了。
2.注册客服端
上面已经知道配置,然后就要开始注册了。回到DiscoveryClient 这个类。
在这个类的构造函数中有这样的代码
//这里我们在配置的配置项,如果没有配置默认是true,默认注册
if(this.clientConfig.shouldRegisterWithEureka() && this.clientConfig.shouldEnforceRegistrationAtInit()) {
try {
if(!this.register()) {
throw new IllegalStateException("Registration error at startup. Invalid server response.");
}
} catch (Throwable var8) {
logger.error("Registration error at startup: {}", var8.getMessage());
throw new IllegalStateException(var8);
}
}
进入register方法
boolean register() throws Throwable {
logger.info("DiscoveryClient_{}: registering service...", this.appPathIdentifier);
EurekaHttpResponse httpResponse;
try {
//这里instanceInfo来DiscoveryClient 构造函数中的ApplicationInfoManager
//可以跟上一点的描述类似
httpResponse = this.eurekaTransport.registrationClient.register(this.instanceInfo);
} catch (Exception var3) {
logger.warn("DiscoveryClient_{} - registration failed {}", new Object[]{this.appPathIdentifier, var3.getMessage(), var3});
throw var3;
}
if(logger.isInfoEnabled()) {
logger.info("DiscoveryClient_{} - registration status: {}", this.appPathIdentifier, Integer.valueOf(httpResponse.getStatusCode()));
}
return httpResponse.getStatusCode() == 204;
}
返回结果中HTTP 状态码204表示:
等同于请求执行成功,但是没有数据,浏览器不用刷新页面.也不用导向新的页面。如何理解这段话呢。还是通过例子来说明吧,假设页面上有个form,提交的url为http-204.htm,提交form,正常情况下,页面会跳转到http-204.htm,但是如果http-204.htm的相应的状态码是204,此时页面就不会发生转跳,还是停留在当前页面。另外对于a标签,如果链接的页面响应码为204,页面也不会发生跳转。
再来看看这instanceInfo注册那些信息
@ProvidedBy(EurekaConfigBasedInstanceInfoProvider.class)
@Serializer("com.netflix.discovery.converters.EntityBodyConverter")
@XStreamAlias("instance")
@JsonRootName("instance")
public class InstanceInfo {
private static final String VERSION_UNKNOWN = "unknown";
private static final Logger logger = LoggerFactory.getLogger(InstanceInfo.class);
public static final int DEFAULT_PORT = 7001;
public static final int DEFAULT_SECURE_PORT = 7002;
public static final int DEFAULT_COUNTRY_ID = 1;
private volatile String instanceId;
private volatile String appName;
@Auto
private volatile String appGroupName;
private volatile String ipAddr;
private volatile int port;
private volatile int securePort;
@Auto
private volatile String homePageUrl;
@Auto
private volatile String statusPageUrl;
@Auto
private volatile String healthCheckUrl;
@Auto
private volatile String secureHealthCheckUrl;
@Auto
private volatile String vipAddress;
@Auto
private volatile String secureVipAddress;
@XStreamOmitField
private String statusPageRelativeUrl;
@XStreamOmitField
private String statusPageExplicitUrl;
@XStreamOmitField
private String healthCheckRelativeUrl;
@XStreamOmitField
private String healthCheckSecureExplicitUrl;
@XStreamOmitField
private String vipAddressUnresolved;
@XStreamOmitField
private String secureVipAddressUnresolved;
@XStreamOmitField
private String healthCheckExplicitUrl;
/** @deprecated */
@Deprecated
private volatile int countryId;
private volatile boolean isSecurePortEnabled;
private volatile boolean isUnsecurePortEnabled;
private volatile DataCenterInfo dataCenterInfo;
private volatile String hostName;
private volatile InstanceInfo.InstanceStatus status;
private volatile InstanceInfo.InstanceStatus overriddenStatus;
private volatile String asgName;
private String version;
注解中将这个类进行了序列化的传输到注册中心。
三.总结
1.配置是通过Netflix Archaius动态获取
2.编写代码可以模仿定义一个没有变量的接口,然后使用@Inject @ImplementedBy @ProvidedBy注解!
让代码结构好一些