Recommended reading

SpringCloud source code read 0-SpringCloud essential knowledge

SpringCloud source code read 1-EurekaServer source code secrets

1. The configuration class

The function of the configuration class is to configure the basic components of the framework, so understand the configuration class, but also into the door of the framework.

The Eureka client takes effect when we add @enableDiscoveryClient or @enableEurekaclient to the startup class.

The two annotations can eventually make, Eureka client corresponding configuration class EurekaClientAutoConfiguration effect. The configuration class is directly covered here, and the details of how annotations make it work are not covered here.

EurekaClientAutoConfiguration as EurekaClient automatic configuration class, match the EurekaClient running components you need.

1. Beans on annotations

@Configuration
@EnableConfigurationProperties// Start property mapping
@ConditionalOnClass(EurekaClientConfig.class)The EurekaClientConfig class is required to exist
@Import(DiscoveryClientOptionalArgsConfiguration.class)// Load the optional configuration class into the container,
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)// The switch needs to exist
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)// The eureka.client.enabled attribute is required
// Resolve before the three auto-injected classes
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
// Parse after the three auto-injected classes
@AutoConfigureAfter(name = {"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration"."org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration"."org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"})
public class EurekaClientAutoConfiguration {}Copy the code

As you can see, annotations are mostly doing EurekaClientAutoConfiguration effective configuration class condition judgment.

Among them@AutoConfigureAfterUnder the corresponding three configuration class needs to speak: RefreshAutoConfiguration: related to refresh the scope of the configuration class, EurekaDiscoveryClientConfiguration: * switch EurekaDiscoveryClientConfiguration injections into the container. The Marker * create RefreshScopeRefreshedEvent event listener, * when eureka. Client. Healthcheck. Enabled =true, injection EurekaHealthCheckHandler for health check AutoServiceRegistrationAutoConfiguration: about service automatic registration of related configuration.Copy the code

No important components are introduced in the annotations.

2. Beans inside the class

  • HasFeatures: Feature classes represent features of the Eureka client
  • EurekaClientConfigBean: EurekaClient configuration class that configures information about EurekaClient. We can override the default configuration by configuring properties in the configuration file.
  • ManagementMetadataProvider: metadata management class.
  • EurekaInstanceConfigBean: EurekaInstance configuration class to configure information about the current Eureka client application instance. Examples include instance hostname, which determines the application instance information to be registered in the Eureka registry.
  • DiscoveryClient: register a (DiscoveryClient)EurekaDiscoveryClient. Used to get a list of services from the registry. Register here for org. Springframework. Cloud. Client. Discovery. DiscoveryClient
  • Eureka service registry: Eureka service registry
  • EurekaAutoServiceRegistration: Eureka service registry automatically. This class implements SmartLifecycle, which means that the start() method is called after the Spring container is started. In plain English, it is a further encapsulation of EurekaServiceRegistry, providing automatic registration. By its parameters (EurekaServiceRegistry registry, EurekaRegistration registration) can see he is through EurekaServiceRegistry Register eureka stration information with the registry.

(3) the inner class Bean EurekaClientAutoConfiguration internal have two about EurekaClient configuration class,

  • EurekaClientConfiguration: ordinary EurekaClient configuration
  • RefreshableEurekaClientConfiguration: can refresh EurekaClient configuration

No matter which EurekaClient is used, three components are registered:

  • EurekaClient: Register a CloudEurekaClient for interacting with Eureka Server, for example, registering, renewing, logging off, etc.
  • ApplicationInfoManager: Manages and initializes the registration of the current Instance and provides Instance status monitoring
  • EurekaRegistration: Service registration information for Eureka instances

(4) Classification summary:

  • EurekaClient is created using the properties configured by EurekaClientConfigBean
  • EurekaAutoServiceRegistration through EurekaServiceRegistry registry, EurekaRegistration registration registration information

2.EurekaClient

EurekaClient can be thought of as the client’s context. His initialization and offloading methods cover the entire life cycle of the client

2.1EurekaClient constructor

As mentioned above, at this time EurekaClient is registered with CloudEurekaClient.

@Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) {
       	// Leave extension points where you can configure various processors with parameters
        if(args ! =null) {
            this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
            this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
            this.eventListeners.addAll(args.getEventListeners());
            this.preRegistrationHandler = args.preRegistrationHandler;
        } else {
            this.healthCheckCallbackProvider = null;
            this.healthCheckHandlerProvider = null;
            this.preRegistrationHandler = null;
        }
        
        // Assign the applicationInfoManager attribute
        this.applicationInfoManager = applicationInfoManager;
        // When applicationInfoManager is initialized,
        //new InstanceInfoFactory().create(config); The current instance information is created
        InstanceInfo myInfo = applicationInfoManager.getInfo();

        clientConfig = config;
        staticClientConfig = clientConfig;
        transportConfig = config.getTransportConfig();
        instanceInfo = myInfo;
        if(myInfo ! =null) {
            appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
        } else {
            logger.warn("Setting instanceInfo to a passed in null value");
        }
		// Alternate registration handler
        this.backupRegistryProvider = backupRegistryProvider;

        this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
        // The local area Application cache is initialized. Applications stores the Application list
        localRegionApps.set(new Applications());

        fetchRegistryGeneration = new AtomicLong(0);

        remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions());
        remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));
		
        if (config.shouldFetchRegistry()) {
            this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_".new long[] {15L.30L.60L.120L.240L.480L});
        } else {
            this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

        if (config.shouldRegisterWithEureka()) {
            this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_".new long[] {15L.30L.60L.120L.240L.480L});
        } else {
            this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }
		// Start initializing the default region
        logger.info("Initializing Eureka in region {}", clientConfig.getRegion());
		// If you do not register with eureka Server and do not get the list of services, do not initialize anything
        if(! config.shouldRegisterWithEureka() && ! config.shouldFetchRegistry()){ ....return;
        }

        try {
            // The container is designed for handling the timedcontainer task.
            scheduler = Executors.newScheduledThreadPool(2.new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());
			// Heartbeat thread pool, 2 threads
            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
			// Cache flushes the thread pool, 2 threads
            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
			// Initializes the communicator that actually communicates with EurekaServer.
            eurekaTransport = new EurekaTransport();
            scheduleServerEndpointTask(eurekaTransport, args);

			// Configure the area mapper. DNS mapping can be configured.
            AzToRegionMapper azToRegionMapper;
            if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
                azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
            } else {
                azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
            }
            if (null! = remoteRegionsToFetch.get()) { azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
            }
            // Create an instance area inspector.
            instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
        } catch (Throwable e) {
            throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
        }
		// If you need to fetch the list of services from eureka Server and try fetchRegistry(false) fails,
		/ / call BackupRegistry
        if(clientConfig.shouldFetchRegistry() && ! fetchRegistry(false)) {
            fetchRegistryFromBackup();
        }

        // Callback extension point handler. This handler is processed before registration.
        if (this.preRegistrationHandler ! =null) {
            this.preRegistrationHandler.beforeRegistration();
        }

        if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
            try {
                if(! register() ) {throw new IllegalStateException("Registration error at startup. Invalid server response."); }}catch (Throwable th) {
                logger.error("Registration error at startup: {}", th.getMessage());
                throw newIllegalStateException(th); }}// Initialize all scheduled tasks
        initScheduledTasks();

        try {
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register timers", e);
        }
        // Add client and clientConfig to DiscoveryManager so that DI can be used elsewhere.
        DiscoveryManager.getInstance().setDiscoveryClient(this);
        DiscoveryManager.getInstance().setEurekaClientConfig(config);
    }
Copy the code

Eureka initialization is complicated, so let’s focus on it.

2.1.1 EurekaTransport

EurekaTransport is initialized during DiscoveryClient initialization.

eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
Copy the code

EurekaTransport is the low-level communicator between client and server. There are five important attributes:

  • TransportClientFactory: the lowest level HTTP tool production factory. The default JerseyEurekaHttpClientFactory EurekaJerseyClient communications are used by default.
  • NewQueryClientFactory: The EurekaHttpClientFactory class that creates newQueryClient
  • NewQueryClient: EurekaHttpClient type HTTP connection tool, is responsible for obtaining the Server service list.
  • RegistrationClientFactory: create registrationClient EurekaHttpClientFactory factory class
  • RegistrationClient: EurekaHttpClient type HTTP connection tool used for registration and renewal.

ScheduleServerEndpointTask method EurekaTransport5 initialization properties

The transportClientFactory is a low-level HTTP factory. EurekaHttpClientFactory is a high-level HTTP factory. The transportClientFactory is decorated with layers of EurekaHttpClientFactory with different functions. Production HTTP tools also have different levels of functionality. Columns such as: the outermost SessionedEurekaHttpClient which has the function of the session EurekaHttpClient second EurekaHttpClient RetryableEurekaHttpClient retry function

2.1.2 fetchRegistry

private boolean fetchRegistry(boolean forceFullRegistryFetch) {
        Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

        try {
           	// Get the local cache
            Applications applications = getApplications();
			// If incremental pull is disabled or it is the first time to pull, fully pull the registered service instances on the server
            if(clientConfig.shouldDisableDelta() || (! Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress())) || forceFullRegistryFetch || (applications ==null)
                    || (applications.getRegisteredApplications().size() == 0)
                    || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
            {
              	// Get all instances
                getAndStoreFullRegistry();
            } else {
            	// Incrementally pull the service instance
                getAndUpdateDelta(applications);
            }
            applications.setAppsHashCode(applications.getReconcileHashCode());
            logTotalInstances();
        } catch (Throwable e) {
            return false;
        } finally {
            if(tracer ! =null) { tracer.stop(); }}// Refresh the local cache
        onCacheRefreshed();
        // Update the state of the remote instance based on the instance data in the cache.
        updateInstanceRemoteStatus();
       // Returns true on successful registry pull
        return true;
    }
Copy the code

Get the final call in full

EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
                ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
                : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
Copy the code

Incremental gain

EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
Copy the code

It can be seen that EurekaTransport provides support at the bottom of client-server communication.

2.1.3 initScheduledTasks

It’s worth saying before we do: Timedcontainer handling. The timedorTask is a periodic task that is designed to automatically adjust the interval, and when it is not timed out, it is executed at an initial interval. When a task times out, the interval for the next period is increased. Each timeout is increased by a corresponding multiple until the maximum parameter is set externally. Once new tasks no longer time out, the interval automatically returns to its default value.

In other words, it is a cyclical task with adaptive characteristics. (Great design)

private void initScheduledTasks(a) {
		//1. If the service list is obtained, a periodic cache update task is created
        if (clientConfig.shouldFetchRegistry()) {
        	// Initial interval (default 30 seconds)
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            // The maximum multiple is 10 times by default
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            It oversees the Execution of the CacheRefreshThread task.
            CacheRefreshExecutor. CacheRefreshThread
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new CacheRefreshThread()FetchRegistry () is called to fetch the list of services
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }
		//2. How to register, create periodic renewal task, maintain heartbeat.
        if (clientConfig.shouldRegisterWithEureka()) {
        	// Heartbeat interval, default 30 seconds.
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            // The maximum multiple is 10 times by default
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();

            // It is designed to handle the TimedorTask supervises the execution of the HeartbeatThread mission.
            // Execute thread pool heartbeatExecutor, specific task HeartbeatThread
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            //3. Create an application instance information replicator.
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize
			//4. Create a state change listener to listen for StatusChangeEvent
            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId(a) {
                    return "statusChangeListener";
                }

                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                            InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
                        // log at warn level if DOWN was involved
                        logger.warn("Saw local status change event {}", statusChangeEvent);
                    } else {
                        logger.info("Saw local status change event {}", statusChangeEvent);
                    }
                    // The status has changed. Using the information replicator, perform a task to update the status change to the registryinstanceInfoReplicator.onDemandUpdate(); }};// Whether to add listeners to applicationInfoManager to pay attention to state changes
            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            // Start InstanceInfo replicator
       instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration"); }}Copy the code

Summary of initScheduledTasks() : How to configure to get services

  1. Create a monitoring task threadcacheRefreshTo monitor service acquisitionCacheRefreshThread (DiscoveryClient - CacheRefreshExecutor - % d)The thread is executed every 30 seconds by default. Get service list updates to local cache. Content of the taskrefreshRegistry, local cachelocalRegionApps

How to configure registration,

  1. Create a monitoring task thread"heartbeat",Supervise the renewal taskHeartbeatThread (DiscoveryClient - HeartbeatExecutor - % d)Is executed every 30 seconds by default. The task content isrenew()
  2. Create an instance status listener that listens for changes in the state of the current instance and executes through the InstanceInfo replicatoronDemandUpdate()Method to update changes to the remote server
  3. Start the InstanceInfo replicator timing thread(DiscoveryClient - InstanceInfoReplicator - % d), check the DataCenterInfo, LeaseInfo, and InstanceStatus of the current instance periodically (default: 40 seconds). If there is any change, run the commandInstanceInfoReplicator.this.run()Method to synchronize change information to the server

Let’s take a look at these important tasks:

  • RefreshRegistry refreshes the cache: refreshRegistry finally calls fetchRegistry to get the list of services and update the local cache. FetchRegistry is actively obtained once during DiscoveryClient initialization. After that, it’s all timed.
  • 1. The country was renewedeurekaTransport.registrationClient.sendHeartBeatSends the current instance information to the server
  • InstanceInfoReplicator. OnDemandUpdate () status update: once a change of state, stopped time copy thread, immediately the status updates to the server. Final call is InstanceInfoReplicator. This. The run ()
  • InstanceInfoReplicator. This. The run () to copy the current instance information to the Server: state changes, effective immediately, This command is executed every 40 seconds.

2.1.4 InstanceInfoReplicator. The run ()

public void run(a) {
        try {
        	/ / 1. Refresh DataCenterInfo
        	//2. Refresh LeaseInfo lease information
        	//3. Obtain InstanceStatus from HealthCheckHandler
            discoveryClient.refreshInstanceInfo();
			// Return dirtyTimestamp if isInstanceInfoDirty=true indicates that an update is required.
            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if(dirtyTimestamp ! =null) {
                discoveryClient.register();/ / register.
                instanceInfo.unsetIsDirty(dirtyTimestamp);// The Settings are not updated.}}catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS); scheduledPeriodicRef.set(next); }}Copy the code

You can see that the instance is registered in DiscoveryClient.register ().

So when does the first registration take place?

InitScheduledTasks method, execution instanceInfoReplicator. Start, will first call the instanceInfo. SetIsDirty (), whether the initialization update marks are true, open threads, 40 seconds after launch to register for the first time. (Of course, if there is a status change within 40 seconds, the registration will be initiated immediately.)

2.1.4 discoveryClient. The register ()

boolean register(a) throws Throwable {
        logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
        EurekaHttpResponse<Void> httpResponse;
        try {
        	// Initiate registration
            httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
        } catch (Exception e) {
            logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
            throw e;
        }
        if (logger.isInfoEnabled()) {
            logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
        }
        return httpResponse.getStatusCode() == 204;
    }
Copy the code

As can be seen, the essence of the Register method is also through eurekaTransport to initiate communication with the server.

2.2EurekaClient Uninstallation Method

2.2.1 shutdown ()

The shutdown() modifier with the @predestroy annotation is executed before the Servlet is completely uninstalled.

public synchronized void shutdown(a) {
        if (isShutdown.compareAndSet(false.true)) {
            logger.info("Shutting down DiscoveryClient ...");
			// Remove the listener
            if(statusChangeListener ! =null&& applicationInfoManager ! =null) {
                applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
            }
			// Cancel the task
            cancelScheduledTasks();

            // If APPINFO was registered
            if(applicationInfoManager ! =null
                    && clientConfig.shouldRegisterWithEureka()
                    && clientConfig.shouldUnregisterOnShutdown()) {
                applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                / / offline
                unregister();
            }
			// Communication is interrupted
            if(eurekaTransport ! =null) {
                eurekaTransport.shutdown();
            }
            heartbeatStalenessMonitor.shutdown();
            registryStalenessMonitor.shutdown();
            logger.info("Completed shut down of DiscoveryClient"); }}Copy the code

3. EurekaAutoServiceRegistration callback

EurekaAutoServiceRegistration SmartLifecycle is achieved, in the spring after the start, call the start () method.

@Override
	public void start(a) {
		// Set the port
		if (this.port.get() ! =0) {
			if (this.registration.getNonSecurePort() == 0) {
				this.registration.setNonSecurePort(this.port.get());
			}

			if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
				this.registration.setSecurePort(this.port.get()); }}if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
			/ / register
			this.serviceRegistry.register(this.registration);
			// Publish a registration success event
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this.this.registration.getInstanceConfig()));
			this.running.set(true);//}}Copy the code

this.serviceRegistry.register(this.registration);

@Override
	public void register(EurekaRegistration reg) {
		maybeInitializeClient(reg);
		// Changing the state triggers the execution of the listener
		reg.getApplicationInfoManager()
				.setInstanceStatus(reg.getInstanceConfig().getInitialStatus());

		reg.getHealthCheckHandler().ifAvailable(healthCheckHandler ->
				reg.getEurekaClient().registerHealthCheck(healthCheckHandler));
	}
Copy the code

4. To summarize

1. Registration process:

EurekaAutoServiceRegistration.start()

–>EurekaServiceRegistry.register(EurekaRegistration)

. — — > ApplicationInfoManager setInstanceStatus state changes,

–>StatusChangeListener Listener listens for state changes

. — — > InstanceInfoReplicator onDemandUpdate () status updates to the server

–>InstanceInfoReplicator.run()

–>DiscoveryClient.register()

–>eurekaTransport.registrationClient.register(instanceInfo);

–>jerseyClient

2. Several scheduled tasks for client initialization:

  • Cache refresh task, 30 seconds by default
  • Heartbeat task, 30 seconds by default
  • Information update task, 40 seconds by default

3. Main operations on the client:

  • Service Register
  • Renew service
  • Unregister services
  • fetchRegistry
  • Cache refresh (refreshRegistry)

Due to lack of space, many details cannot be presented. This article aims at talking about some principles, specific details can read the source code, will find that the framework is really excellent ah.