189 8069 5689

SpringBoot自动装配原理详解-创新互联

目录

我们注重客户提出的每个要求,我们充分考虑每一个细节,我们积极的做好成都网站建设、做网站服务,我们努力开拓更好的视野,通过不懈的努力,创新互联赢得了业内的良好声誉,这一切,也不断的激励着我们更好的服务客户。 主要业务:网站建设,网站制作,网站设计,微信小程序,网站开发,技术开发实力,DIV+CSS,PHP及ASP,ASP.Net,SQL数据库的技术开发工程师。

1.环境和依赖

1.1.spring boot版本

1.2.依赖管理

2.自动装配

2.1.流程概述

2.2.三大步前的准备工作

2.2.1.注解入口

2.2.2.获取所有配置类

2.3.获取过滤列表

2.3.1.目的

2.3.2.过程

2.4.装载

2.4.1.目的

2.4.2.过程

2.5.自动配置

3.启动过程

3.1.整体流程

3.2.创建环境信息对象

3.3.创建应用上下文对象

3.4.刷新应用上下文对象

3.4.1.准备刷新

3.4.2.刷新


1.环境和依赖 1.1.spring boot版本

springboot 2.2.X版本采用的maven构建,2.3.X采用gradle构建,因此采用2.2.X,mavan构建的便于源码阅读。本文以2.2.9为例进行Spring Boot自动装配原理的解析。

1.2.依赖管理

引入Spring Boot的方式有两种

  • 引入spring-boot-dependencies的pom文件
  • 将spring-boot-starter-parent作为父级pom

这两种方式的底层都是都是一样的,都是引入了spring-boot-dependencies这个pom文件来管理Spring Boot的所有依赖。

SpringBoot中将一类场景要用到的依赖封装成一个starter,spring-boot-dependencies中包含了J2EE中所有场景(starter)的依赖,并声明了依赖的版本号。

2.自动装配 2.1.流程概述

首先所有JAVA程序的入口都是main方法,Spring Boot也不例外,只有main方法执行时,所有流程步骤才会执行,此处我们只是从启动流程中剥离出和自动装配相关的流程来进行单独解析。只需要大致知道自动装配流程有几步即可,如果有其它疑惑看后文的启动过程解析,就能豁然开朗。

自动装配的整个流程可以分为三大步

  1. 获取过滤列表
  2. 获取自动配置类列表
  3. 比对移除、封装返回

1.获取条件列表

获取类自动装载的条件列表。

2.获取自动配置列表

获取自动装载类的列表。

3.比对移除、封装返回

按照条件列表,将不满足被自动装载条件的类移除掉,返回满足条件的类列表。

2.2.三大步前的准备工作 2.2.1.注解入口

@SpringBootApplication

该注解是个复合注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@EnableAutoConfiguration启动自动装配:

@Import(AutoConfigurationImportSelector.class)  ,AutoConfigurationImportSelector会完成所有配置类的获取以及相关的准备工作。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
2.2.2.获取所有配置类

AutoConfigurationImportSelector被加载后,经过层层调用,最终会调用到DeferredImportSelector中:

会去扫描所有@Configuration封装成一个列表返回。

public IterablegetImports() {
            Iterator var1 = this.deferredImports.iterator();

            while(var1.hasNext()) {
                ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
                this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
            }
			//将得到的自动配置类按照@order进行排序
            return this.group.selectImports();
        }
2.3.获取过滤列表 2.3.1.目的

获取过滤列表,即去获取META-INF/spring-autoconfigure-metadata.properties这一文件。这个文件中会详细记录Spring Boot自带的各大J2EE场景的自动配置类(@Configuration)各自被自动装载生效的前提条件是什么。

2.3.2.过程

DeferredImportSelector.Group.process()中会首先获取自动装配的过滤条件列表,该列表中记录了待装配的类的装配条件。获取的核心方法是getAutoConfigurationMetadata(),该方法会根据传过来的ClassLoader去遍历加载classpath下的所有依赖,获取依赖中的META-INF/spring-autoconfigure-metadata.properties文件。

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() ->String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			//获取自动配置类
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
    		//解析存放自动配置类
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

private AutoConfigurationMetadata getAutoConfigurationMetadata() {
			if (this.autoConfigurationMetadata == null) {
				this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
			}
			return this.autoConfigurationMetadata;
		}
final class AutoConfigurationMetadataLoader {

	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			Enumerationurls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

过滤列表中会以KV键值对的方式记录装配条件,例如:

org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit

2.4.装载 2.4.1.目的
  • 获取自动配置列表

  • 对比过滤列表,移除不满足自动装载的类

  • 封装返回

2.4.2.过程

process()方法中会调用getAutoConfigurationEntry()方法,并将过滤列表传和ClassLoader传过去,在getCandidateConfigurations()方法中通过传递的ClassLoader获取自动装配的列表"META-INF/spring.factories",然后比对过滤列表,将满足条件的待装配类的全路径记录在AutoConfigurationImportSelector.AutoConfigurationGroup的一个叫autoConfigurationEntries的List中。

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //从spring.factories中加载所有自动配置类
   Listconfigurations = getCandidateConfigurations(annotationMetadata, attributes);
   //移除重复配置类
   configurations = removeDuplicates(configurations);
   //得到指定要移除的类(@SpringBootApplication(exclude=FreeMarkerAutoConfiguration.class))
   Setexclusions = getExclusions(annotationMetadata, attributes);
   //检查指定要移除的类,如果不是配置类,抛出异常
   checkExcludedClasses(configurations, exclusions);
   //移除指定要移除的自动配置类
   configurations.removeAll(exclusions);
   //获取满足条件的自动配置类列表
   configurations = filter(configurations, autoConfigurationMetadata);
   //记录下符合条件的对象,并封装在实体中返回
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

一切执行完毕后会回到入口出继续向下执行this.group.selectImports(),最终会调用到AutoConfigurationImportSelector的selectImports()方法,在该方法中会根据@order对自动配置类进行排序。

public IterableselectImports() {
   if (this.autoConfigurationEntries.isEmpty()) {
      return Collections.emptyList();
   }
   SetallExclusions = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
   SetprocessedConfigurations = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
         .collect(Collectors.toCollection(LinkedHashSet::new));
   processedConfigurations.removeAll(allExclusions);

   return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
         .map((importClassName) ->new Entry(this.entries.get(importClassName), importClassName))
         .collect(Collectors.toList());
}
2.5.自动配置

在自动装载步骤中已经获得需要加载的自动配置类的全路径,接下来就是自动配置。

以随便一个AutoConfiguration类为例:

头上的一大串@Conditional注解其实就是过滤时的过滤条件,过滤列表其实就是通过这些条件注解生成的。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {}

这个@Configuration满足条件后其中的@Bean都会被自动装入IOC。

3.启动过程 3.1.整体流程

Spring boot的启动过程就是围绕上下文的创建、准备、刷新(填充)展开的。

spring应用上下文和servletContext不是一个东西,servlet上下文用来维系当前应用的一块共享空间,目的是实现资源和数据在应用中的全局共享。spring的上下文是一个维护Bean定义以及对象之间协作关系的高级接口,目的是维护好整个spring中的资源,如配置文件、Bean对象等,其涵盖了IOC,但不只有IOC,可以理解为Spring应用的一个抽象。

在SpringApplication的run()方法中创建应用上下文,整个SpringApplication的run方法主要完成四个核心动作:

  1. prepareEnvironment

    创建环境信息对象,解析环境参数,包含配置文件、命令行传参等。

  2. createApplicationContext

    创建应用上下文对象

  3. prepareContext

    准备应用上下文对象

  4. refreshContext

    刷新应用上下文对象

// 类 SpringApplication 代码片段
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		CollectionexceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
          // 包装通过命令行传入的名命令行参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
          // 结合命令行参数 准备环境对象,该环境对象将会被设置到应用上下文对象 ApplicationContext 上  ,
          // 环境对象通常包含如下信息 : 
          // 1. profile
          // 2. system properties
          // 3. system environment
          // 4. commandline arguments
          // 5. spring 配置文件
          // 6. 一个随机值属性源 random
          // 对于当前 WebFlux 应用来讲,这里实现类会使用 StandardReactiveWebEnvironment
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
          // 创建应用上下文对象 ApplicationContext  
          // 实现类会采用 : AnnotationConfigReactiveWebServerApplicationContext
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
          // 准备应用上下文对象 ApplicationContext          
          // 1. 关联环境信息对象到应用上下文对象
          // 2. 对象创建后置处理 : 设置容器的类型转换服务
          // 3. 初始化应用上下文对象:调用各个 ApplicationContextInitializer
          // 4. 广播事件 : ApplicationContextInitializedEvent
          // 5. 将应用程序参数作为一个 bean 注册到容器 : springApplicationArguments
          // 6. 将应用程序入口类作为 bean 注册到容器 (load)
          // 7. 上下文加载完成生命周期事件回调,为各个实现了 接口 ApplicationContextAware 的 
          //    ApplicationListener 设置应用上下文对象属性, 并广播事件 : ApplicationPreparedEvent
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
          // 刷新应用上下文对象  ApplicationContext 
          // 主要是调用应用上下文对象  ApplicationContext  自身的 refresh 方法
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
            
          // 应用程序上下文对象 ApplicationContext 已经准备就绪,
          // 现在调用各种开发人员或者框架其他部分定义的 
          // ApplicationRunner 或者 CommandLineRunner
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
3.2.创建环境信息对象
// SpringApplication 代码片段
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
       // 创建环境信息对象 environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
       // 将应用程序参数关联到环境信息对象 environment
		configureEnvironment(environment, applicationArguments.getSourceArgs());
       // 发布应用程序事件 : 环境信息对象准备好了 ,
       // 同步调用各个事件监听器
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}       
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

普通web应用和reactive创建的环境信息对象类型不同,但是实际功能相同,并没有什么太大区别。

// SpringApplication 代码片段
    private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}
3.3.创建应用上下文对象

根据之前环境推断中得到的当前应用的环境类型来创建不同类型的应用上下文。

// SpringApplication 代码片段
	protected ConfigurableApplicationContext createApplicationContext() {
		ClasscontextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
             // 根据 this.webApplicationType 确定应用上下文实现类
				switch (this.webApplicationType) {
				case SERVLET:
            // DEFAULT_SERVLET_WEB_CONTEXT_CLASS 常量值为 : 
            // org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
            // 对应 Spring MVC Servlet Web 环境
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
            // DEFAULT_REACTIVE_WEB_CONTEXT_CLASS 常量值为 : 
            // org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
            // 对应 Spring WebFlux Reactive Web 环境
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
            // DEFAULT_CONTEXT_CLASS 常量值为 : 
            // org.springframework.context.annotation.AnnotationConfigApplicationContext
            // 不对应任何 Web 环境
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
			"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
			ex);
			}
		}
        
       // 确定应用上下文实现类之后,实例化应用上下文对象 
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
3.4.刷新应用上下文对象 3.4.1.准备刷新

这一步主要是完成刷新前的准备工作,将除IOC相关的一切context中的东西全部赋值初始化好。

主要完成以下动作:

  • 关联环境信息

  • 查找调用各种前置、后置处理器(自定义的、自带的)

  • 调用各种回调

  • 获取主启动类的路径,将主启动类封装成一个BeanDefinition

// SpringApplication 代码片段
	private void prepareContext(ConfigurableApplicationContext context, 
			ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, 
			ApplicationArguments applicationArguments, 
			Banner printedBanner) {
       // 1. 关联环境信息对象到应用上下文对象     
		context.setEnvironment(environment);
       // 2. 对象创建后置处理 : 设置容器的类型转换服务 
		postProcessApplicationContext(context);
       // 3. 初始化应用上下文对象:调用各个 ApplicationContextInitializer 
		applyInitializers(context);
       // 4. 广播事件 : ApplicationContextInitializedEvent 
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
       // 5. 将应用程序参数作为一个 bean 注册到容器 : springApplicationArguments 
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
        
		// 获取主启动类的路径
		Setsources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
       // 6. 将主启动类封装成一个BeanDefinition 
		load(context, sources.toArray(new Object[0]));
       // 7. 上下文加载完成生命周期事件回调,为各个实现了 接口 ApplicationContextAware 的 
       //    ApplicationListener 设置应用上下文对象属性, 并广播事件 : ApplicationPreparedEvent        
		listeners.contextLoaded(context);
	}3.4.2.刷新

主要是调用应用上下文对象 ApplicationContext 自身的 refresh 方法,这是上下文对象的初始化中最关键的一步,该步骤中会完成几个核心动作:

  • 初始化IOC容器(即BeanFactory)

    该步骤中就会扫描解析注解,触发自动装配.

  • 初始化WebServer容器

刷新应用上下文的动作其实是在spring相关的jar中,因此首先要有个概念,在这一步之前spring boot的动作已经完成,真正与IOC相关的动作还是由spring来完成,所以说spring boot是对spring的二次封装。

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 做一些初始化动作
			prepareRefresh();
			// 获取bean factory
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// 初始化bean factory,为其成员属性赋一些值
			prepareBeanFactory(beanFactory);
			try {
				// 获取所有bean后置处理器
				postProcessBeanFactory(beanFactory);
				// **最核心的方法,注解的扫描,自动配置类的装载,IOC的初始化等全在这个方法中
				invokeBeanFactoryPostProcessors(beanFactory);
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				// Initialize message source for this context.
				initMessageSource();
				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();
				// Initialize other special beans in specific context subclasses.
				onRefresh();
				// Check for listener beans and register them.
				registerListeners();
				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);
				// Last step: publish corresponding event.
				finishRefresh();
			}
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

初始化IOC:

创建容器其实没有什么说的,就是new一个web server(tomcat、netty或者jetty)出来。这里着重要说一下初始化IOC。

入口在invokeBeanFactoryPostProcessors(beanFactory)。

IOC容器的初始化分为三步:

  • Resource定位

    定位到需要的各种路径:

    • BasePackage

      这一步在准备刷新的时候就已经完成,并在封装在了主启动类封装为的BeanDefinition中。

      基于BasePackage去扫描通过注解自定义的需要注入IOC的Bean。

    • 自动配置类的全路径

      这一步在刷新应用上下文的时候进行,即去获取factory.properties。

      基于自动配置类的全路径去将对应自动配置类注入IOC。

  • BeanDefinition载入

    将定位到的Resource记录的Class分别封装为一个个的Definition。

  • BeanDefinition注册

    将Definition注册进IOC中。其实就是注入到一个ConcurrentHashMap中,IOC就是通过这个Map来持有这些BeanDefinition的。

IOC涉及的两个核心概念:

  • BeanDefinition

  • BeanFactory

    IOC容器其实就是BeanFactory,BeanFactory就是IOC容器的规范接口,有多个实现,最典型的就是DefalutListableBeanFactory,IOC容器中有一个成员Map(BeanDefinitionMap),该Map持有所有的BeanDefinition,用来维护Bean的基本信息(class、作用域等)

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


本文名称:SpringBoot自动装配原理详解-创新互联
文章位置:http://cdxtjz.cn/article/gsdpo.html

联系我们

您好HELLO!
感谢您来到成都网站建设公司,若您有合作意向,请您为我们留言或使用以下方式联系我们, 我们将尽快给你回复,并为您提供真诚的设计服务,谢谢。
  • 电话:028- 86922220 18980695689
  • 商务合作邮箱:631063699@qq.com
  • 合作QQ: 532337155
  • 成都网站设计地址:成都市青羊区锣锅巷31号五金站写字楼6楼

小谭建站工作室

成都小谭网站建设公司拥有多年以上互联网从业经验的团队,始终保持务实的风格,以"帮助客户成功"为已任,专注于提供对客户有价值的服务。 我们已为众企业及上市公司提供专业的网站建设服务。我们不只是一家网站建设的网络公司;我们对营销、技术、管理都有自己独特见解,小谭建站采取“创意+综合+营销”一体化的方式为您提供更专业的服务!

小谭观点

相对传统的成都网站建设公司而言,小谭是互联网中的网站品牌策划,我们精于企业品牌与互联网相结合的整体战略服务。
我们始终认为,网站必须注入企业基因,真正使网站成为企业vi的一部分,让整个网站品牌策划体系变的深入而持久。