springcloud源码分析(一)之采用redis实现注册中心
注册中心
在分布式架构中注册中心起到了管理各种服务功能包括服务的注册、发现、熔断、负载、降级等功能,在分布式架构中起到了不可替代的作用。常见的注册中心有eureka,zookeeper等等,在springcloud中,它封装了Netflix公司开发的Eureka模块来实现服务的注册与发现,简单的来说注册中心里会存放着我们的ip、端口、业务,如果是只是存储我们可以想到很多,数据库,文件,内存,redis都是可以的存的。那么今天小编这里就把redis当成注册中心来实现。
配置与分析
在springcloud中我们创建一个业务模块去找注册中心时,一般会@EnableDiscoveryClient、@EnableEurekaClient、@EnableEurekaServer,不过@EnableEurekaClient和@EnableEurekaServer都是Eureka给我提供的。这里我们就要SpringCloud给我们提供的@EnableDiscoveryClient,点击进入@EnableDiscoveryClient源码中。
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.cloud.client.discovery; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /**
* Annotation to enable a DiscoveryClient implementation.
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient { /**
* If true, the ServiceRegistry will automatically register the local server.
*/
boolean autoRegister() default true;
}
我们可以看到autoRegister()方法,我们可以看上面的注释,如果为true,将把本地服务注册到ServiceRegistry中。ServiceRegistry可以理解为将服务注册到注册中心的一个通道,那么我们想把服务注册到注册中心你个方法就必须返回true。
可以看到注解上的注解@Import(EnableDiscoveryClientImportSelector.class)
这个注解引入了这个类,那我们再进入这个类
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.cloud.client.discovery; import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.cloud.commons.util.SpringFactoryImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.type.AnnotationMetadata; import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List; /**
* @author Spencer Gibb
*/
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> { @Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata); AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true)); boolean autoRegister = attributes.getBoolean("autoRegister"); if (autoRegister) {
List<String> importsList = new ArrayList<>(Arrays.asList(imports));
importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
} else {
Environment env = getEnvironment();
if(ConfigurableEnvironment.class.isInstance(env)) {
ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env;
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("spring.cloud.service-registry.auto-registration.enabled", false);
MapPropertySource propertySource = new MapPropertySource(
"springCloudDiscoveryClient", map);
configEnv.getPropertySources().addLast(propertySource);
} } return imports;
} @Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
} @Override
protected boolean hasDefaultFactory() {
return true;
} }
我们可以看到selectImports方法里的判断,autoRegister的判断,当我autoRegister()为true时才会执行List的添加,添加的内容我们可以发现Lis添加的内容,只不过是个配置类,不过我们顺藤摸瓜就可以找到serviceregistry的包,我们可以发现包下有许多包是做配置的,那么我们找到最核心的类AbstractAutoServiceRegistration类找到他的父类AbstractDiscoveryLifecycle,在这个类中我们找到抽象register()方法,看一下上面的注释我们就可以发现这个才是用来注册的方法,接下我们找到重写的start()方法可以看到register()方法在这里被调用。
AbstractAutoServiceRegistration源码
package org.springframework.cloud.client.serviceregistry; import org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle; /**
* Lifecycle methods that may be useful and common to {@link ServiceRegistry} implementations.
*
* TODO: document the lifecycle
*
* @param <R> registration type passed to the {@link ServiceRegistry}.
*
* @author Spencer Gibb
*/
@SuppressWarnings("deprecation")
public abstract class AbstractAutoServiceRegistration<R extends Registration> extends AbstractDiscoveryLifecycle implements AutoServiceRegistration { private final ServiceRegistry<R> serviceRegistry;
private AutoServiceRegistrationProperties properties; @Deprecated
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry) {
this.serviceRegistry = serviceRegistry;
} protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) {
this.serviceRegistry = serviceRegistry;
this.properties = properties;
} protected ServiceRegistry<R> getServiceRegistry() {
return this.serviceRegistry;
} protected abstract R getRegistration(); protected abstract R getManagementRegistration(); /**
* Register the local service with the {@link ServiceRegistry}
*/
@Override
protected void register() {
this.serviceRegistry.register(getRegistration());
} /**
* Register the local management service with the {@link ServiceRegistry}
*/
@Override
protected void registerManagement() {
R registration = getManagementRegistration();
if (registration != null) {
this.serviceRegistry.register(registration);
}
} /**
* De-register the local service with the {@link ServiceRegistry}
*/
@Override
protected void deregister() {
this.serviceRegistry.deregister(getRegistration());
} /**
* De-register the local management service with the {@link ServiceRegistry}
*/
@Override
protected void deregisterManagement() {
R registration = getManagementRegistration();
if (registration != null) {
this.serviceRegistry.deregister(registration);
}
} @Override
public void stop() {
if (this.getRunning().compareAndSet(true, false) && isEnabled()) {
deregister();
if (shouldRegisterManagement()) {
deregisterManagement();
}
this.serviceRegistry.close();
}
} @Override
protected boolean shouldRegisterManagement() {
if (this.properties == null || this.properties.isRegisterManagement()) {
return super.shouldRegisterManagement();
}
return false;
}
}
AbstractDiscoveryLifecycle源码
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.cloud.client.discovery; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PreDestroy; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment; /**
* Lifecycle methods that may be useful and common to various DiscoveryClient implementations.
*
* @deprecated use {@link org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration} instead. This class will be removed in the next release train.
*
* @author Spencer Gibb
*/
@Deprecated
public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle,
ApplicationContextAware, ApplicationListener<EmbeddedServletContainerInitializedEvent> { private static final Log logger = LogFactory.getLog(AbstractDiscoveryLifecycle.class); private boolean autoStartup = true; private AtomicBoolean running = new AtomicBoolean(false); private int order = 0; private ApplicationContext context; private Environment environment; private AtomicInteger port = new AtomicInteger(0); protected ApplicationContext getContext() {
return context;
} @Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.context = applicationContext;
this.environment = this.context.getEnvironment();
} @Deprecated
protected Environment getEnvironment() {
return environment;
} @Deprecated
protected AtomicInteger getPort() {
return port;
} @Override
public boolean isAutoStartup() {
return this.autoStartup;
} @Override
public void stop(Runnable callback) {
try {
stop();
} catch (Exception e) {
logger.error("A problem occurred attempting to stop discovery lifecycle", e);
}
callback.run();
} @Override
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
} // only set the port if the nonSecurePort is 0 and this.port != 0
if (this.port.get() != 0 && getConfiguredPort() == 0) {
setConfiguredPort(this.port.get());
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get() && getConfiguredPort() > 0) {
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(new InstanceRegisteredEvent<>(this,
getConfiguration()));
this.running.compareAndSet(false, true);
}
} @Deprecated
protected abstract int getConfiguredPort();
@Deprecated
protected abstract void setConfiguredPort(int port); /**
* @return if the management service should be registered with the {@link ServiceRegistry}
*/
protected boolean shouldRegisterManagement() {
return getManagementPort() != null && ManagementServerPortUtils.isDifferent(this.context);
} /**
* @return the object used to configure the registration
*/
@Deprecated
protected abstract Object getConfiguration(); /**
* Register the local service with the DiscoveryClient
*/
protected abstract void register(); /**
* Register the local management service with the DiscoveryClient
*/
protected void registerManagement() {
} /**
* De-register the local service with the DiscoveryClient
*/
protected abstract void deregister(); /**
* De-register the local management service with the DiscoveryClient
*/
protected void deregisterManagement() {
} /**
* @return true, if the {@link DiscoveryLifecycle} is enabled
*/
protected abstract boolean isEnabled(); /**
* @return the serviceId of the Management Service
*/
@Deprecated
protected String getManagementServiceId() {
// TODO: configurable management suffix
return this.context.getId() + ":management";
} /**
* @return the service name of the Management Service
*/
@Deprecated
protected String getManagementServiceName() {
// TODO: configurable management suffix
return getAppName() + ":management";
} /**
* @return the management server port
*/
@Deprecated
protected Integer getManagementPort() {
return ManagementServerPortUtils.getPort(this.context);
} /**
* @return the app name, currently the spring.application.name property
*/
@Deprecated
protected String getAppName() {
return this.environment.getProperty("spring.application.name", "application");
} @Override
public void stop() {
if (this.running.compareAndSet(true, false) && isEnabled()) {
deregister();
if (shouldRegisterManagement()) {
deregisterManagement();
}
}
} @PreDestroy
public void destroy() {
stop();
} @Override
public boolean isRunning() {
return this.running.get();
} protected AtomicBoolean getRunning() {
return running;
} @Override
public int getOrder() {
return this.order;
} @Override
public int getPhase() {
return 0;
} @Override
@Deprecated
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
// TODO: take SSL into account
// Don't register the management port as THE port
if (!"management".equals(event.getApplicationContext().getNamespace())) {
this.port.compareAndSet(0, event.getEmbeddedServletContainer().getPort());
this.start();
}
}
}
回到AbstractAutoServiceRegistration,我们可以参考AbstractAutoServiceRegistration中重新的regiter()方法调用了serviceRegistry的register()方法,那么我们进入register()方法参考一下注释我们可以发现传入的参数类,主要传入我们的ip 端口 业务等等,那么方法的参数是它的一个方法,那么我们重写他AbstractAutoServiceRegistration类看一下这个方法我们需要返回一个什么类进去。
package com.rk.ytl.config; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.annotation.Configuration; /**
* @author 杨天乐
* @date 2018/4/20 16:26
*/
@Configuration
public class RedisAutoServiceRegistration extends AbstractAutoServiceRegistration { @Autowired
private MyServiceInstance myServiceInstance; @Autowired
private AutoServiceRegistrationProperties autoServiceRegistrationProperties; @Value("${server.port}")
private Integer port; protected RedisAutoServiceRegistration(ServiceRegistry serviceRegistry) {
super(serviceRegistry);
} @Override
protected Registration getRegistration() {
return myServiceInstance;
} @Override
protected Registration getManagementRegistration() {
return null;
} @Override
protected int getConfiguredPort() {
return port;
} @Override
protected void setConfiguredPort(int port) { } @Override
protected Object getConfiguration() {
return null;
} @Override
protected boolean isEnabled() {
return autoServiceRegistrationProperties.isEnabled();
}
}
这个是我写好的,我们重写完之后我们可以看到返回了一个Registration对象,那么可以根据源码找到Registration对象继承了ServiceInstance,我们看一下源码
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.cloud.client; import java.net.URI;
import java.util.Map; /**
* Represents an instance of a Service in a Discovery System
* @author Spencer Gibb
*/
public interface ServiceInstance { /**
* @return the service id as registered.
*/
String getServiceId(); /**
* @return the hostname of the registered ServiceInstance
*/
String getHost(); /**
* @return the port of the registered ServiceInstance
*/
int getPort(); /**
* @return if the port of the registered ServiceInstance is https or not
*/
boolean isSecure(); /**
* @return the service uri address
*/
URI getUri(); /**
* @return the key value pair metadata associated with the service instance
*/
Map<String, String> getMetadata();
}
(注意:这里一定要把isEnable()方法设置为true,不然是不可以的,至于为什么,大家可以参考AbstractDiscoveryLifecycle的start()方法如果不为true是执行不了register()方法的)
端口,ip,业务都在这里,那么我们就是找他了,再写一个类去实现它。
package com.rk.ytl.config; import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.stereotype.Component; import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Map; /**
* @author 杨天乐
* @date 2018/4/20 16:29
*/
@Component
public class MyServiceInstance implements Registration { @Value("${spring.application.name}")
private String applicationName; @Value("${server.port}")
private Integer port; @Override
public String getServiceId() {
return applicationName;
} @Override
public String getHost() {
String host = "";
try {
InetAddress inetAddress = InetAddress.getLocalHost();
host =inetAddress.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return host;
} @Override
public int getPort() {
return port;
} @Override
public boolean isSecure() {
return false;
} @Override
public URI getUri() {
return null;
} @Override
public Map<String, String> getMetadata() {
return null;
}
}
然后把他注入进来就可以了。参数完成了那么我们回头看是哪个类的register()方法是serviceRegistry类的,那么上面说到了serviceRegistry类是一个把服务中到服务中心的通道,那么我们就可以自己实现通过
serviceRegistry通向redis的通道。
package com.rk.ytl.config; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component; /**
* @author 杨天乐
* @date 2018/4/20 17:02
*/
@Component
public class MyServiceRegistry implements ServiceRegistry { @Autowired
private StringRedisTemplate stringRedisTemplate; @Override
public void register(Registration registration) {
ValueOperations<String, String> redis = stringRedisTemplate.opsForValue();
String hostname= registration.getHost()+":"+registration.getPort();
String serviceId = registration.getServiceId();
redis.set(serviceId,hostname);
} @Override
public void deregister(Registration registration) {
stringRedisTemplate.delete(registration.getServiceId());
} @Override
public void close() { } @Override
public void setStatus(Registration registration, String status) { } @Override
public Object getStatus(Registration registration) {
return null;
}
}
接下来配置一个启动类就可以了
package com.rk.ytl; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /**
* @author 杨天乐
* @date 2018/4/20 16:12
*/
@SpringBootApplication
@EnableDiscoveryClient
public class RedisRegisterApplication { public static void main(String[] args) {
SpringApplication.run(RedisRegisterApplication.class,args);
}
}
yml配置:
spring:
redis:
host: redis地址
application:
name: redis-register-center
server:
port: 9090
运行后我们在redis里就可以看见我们的本机的ip端口

springcloud源码分析(一)之采用redis实现注册中心的更多相关文章
- [源码分析] OpenTracing之跟踪Redis
[源码分析] OpenTracing之跟踪Redis 目录 [源码分析] OpenTracing之跟踪Redis 0x00 摘要 0x01 总体逻辑 1.1 相关概念 1.2 埋点插件 1.3 总体逻 ...
- 插件开发之360 DroidPlugin源码分析(五)Service预注册占坑
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52264977 在了解系统的activity,service,broa ...
- 插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52258434 在了解系统的activity,service,broa ...
- Dubbo(三):深入理解Dubbo源码之如何将服务发布到注册中心
一.前言 前面有说到Dubbo的服务发现机制,也就是SPI,那既然Dubbo内部实现了更加强大的服务发现机制,现在我们就来一起看看Dubbo在发现服务后需要做什么才能将服务注册到注册中心中. 二.Du ...
- Spring源码分析(六)解析和注册BeanDefinitions
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 当把文件转换为Document后,接下来的提取及注册bean就是我们的重 ...
- motan源码分析一:服务发布及注册
motan是新浪微博开源的服务治理框架,具体介绍请看:http://tech.sina.com.cn/i/2016-05-10/doc-ifxryhhh1869879.shtml. 本系列的文章将分析 ...
- Redis 内存管理 源码分析
要想了解redis底层的内存管理是如何进行的,直接看源码绝对是一个很好的选择 下面是我添加了详细注释的源码,需要注意的是,为了便于源码分析,我把redis为了弥补平台差异的那部分代码删了,只需要知道有 ...
- Redis 专栏(使用介绍、源码分析、常见问题...)
一.介绍相关 说Redis : 介绍Redis特性,使用场景,使用Jedis操作Redis等. 二.源码分析 1. 数据结构 Redis源码分析(sds):Redis自己封装的C语言字符串类型. Re ...
- Fabric2.2中的Raft共识模块源码分析
引言 Hyperledger Fabric是当前比较流行的一种联盟链系统,它隶属于Linux基金会在2015年创建的超级账本项目且是这个项目最重要的一个子项目.目前,与Hyperledger的另外几个 ...
随机推荐
- linux--解决anaconda升级pip问题
Anaconda指的是一个开源的Python发行版本,其包含了conda.Python等180多个科学包及其依赖项. 在用pip install命令安装东西时,有时会提示如下错误:升级pip You ...
- IT兄弟连 Java语法教程 数据类型3
字符型 在Java中,用于存储字符串的数据类型是char.然而,C/C++程序员要当心:Java中的char与C或C++中的char是不同的.在C/C++中,char的宽度是8位.而在Java中不是这 ...
- 【分布式存储】Glusterfs快速搭建
目录 环境准备 步骤1,保证至少有三台服务器 步骤2,格式化和配置硬盘 步骤3,安装GlusterFS 步骤4,配置防火墙 步骤5,配置 trusted pool 步骤6,设置GlusterFS卷 步 ...
- apt-get failed:The following signatures were invalid: BADSIG
参考如下链接: https://askubuntu.com/questions/131601/gpg-error-release-the-following-signatures-were-inval ...
- python库的tkinter带你进入GUI世界(计算器简单功能)
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 一个处女座的程序猿 PS:如有需要Python学习资料的小伙伴可以加 ...
- Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战
Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台- 什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ...
- Linux域名服务DNS
什么是 DNS DNS 全称是 Domain Name System,大意是域名解析系统,它的职责是把域名翻译成一个一个可以识别的 IP 供不同的计算机设备连接. linux 有关 DNS 解析的配置 ...
- Android 安全攻防(一):SEAndroid的编译
转自:http://blog.csdn.net/yiyaaixuexi/article/details/8330645 SEAndroid概述 SEAndroid(Security-Enhance ...
- [UIApplication sharedApplication].keyWindow.rootViewController
一般来说 [UIApplication sharedApplication].keyWindow.rootViewController 会在 appDelegate 中初始化,并且整个应用运行过程中都 ...
- Android 工程的创建
还望支持个人博客站:http://www.enjoytoday.cn 本章节主要介绍如何开始Android工程的创建和android开发过程中需要的一些简单的技巧和知识.首篇文章主要介绍如何开始And ...