/** Spark SQL源码分析系列文章*/

前面几篇文章讲解了Spark SQL的核心执行流程和Spark SQL的Catalyst框架的Sql Parser是怎样接受用户输入sql,经过解析生成Unresolved Logical Plan的。我们记得Spark SQL的执行流程中另一个核心的组件式Analyzer,本文将会介绍Analyzer在Spark SQL里起到了什么作用。

Analyzer位于Catalyst的analysis package下,主要职责是将Sql Parser 未能Resolved的Logical Plan 给Resolved掉。

一、Analyzer构造

Analyzer会使用Catalog和FunctionRegistry将UnresolvedAttribute和UnresolvedRelation转换为catalyst里全类型的对象。

Analyzer里面有fixedPoint对象,一个Seq[Batch].

  1. class Analyzer(catalog: Catalog, registry: FunctionRegistry, caseSensitive: Boolean)
  2. extends RuleExecutor[LogicalPlan] with HiveTypeCoercion {
  3. // TODO: pass this in as a parameter.
  4. val fixedPoint = FixedPoint(100)
  5. val batches: Seq[Batch] = Seq(
  6. Batch("MultiInstanceRelations", Once,
  7. NewRelationInstances),
  8. Batch("CaseInsensitiveAttributeReferences", Once,
  9. (if (caseSensitive) Nil else LowercaseAttributeReferences :: Nil) : _*),
  10. Batch("Resolution", fixedPoint,
  11. ResolveReferences ::
  12. ResolveRelations ::
  13. NewRelationInstances ::
  14. ImplicitGenerate ::
  15. StarExpansion ::
  16. ResolveFunctions ::
  17. GlobalAggregates ::
  18. typeCoercionRules :_*),
  19. Batch("AnalysisOperators", fixedPoint,
  20. EliminateAnalysisOperators)
  21. )

Analyzer里的一些对象解释:

FixedPoint:相当于迭代次数的上限。

  1. /** A strategy that runs until fix point or maxIterations times, whichever comes first. */
  2. case class FixedPoint(maxIterations: Int) extends Strategy

Batch: 批次,这个对象是由一系列Rule组成的,采用一个策略(策略其实是迭代几次的别名吧,eg:Once)

  1. /** A batch of rules. */,
  2. protected case class Batch(name: String, strategy: Strategy, rules: Rule[TreeType]*)

Rule:理解为一种规则,这种规则会应用到Logical Plan 从而将UnResolved 转变为Resolved

  1. abstract class Rule[TreeType <: TreeNode[_]] extends Logging {
  2. /** Name for this rule, automatically inferred based on class name. */
  3. val ruleName: String = {
  4. val className = getClass.getName
  5. if (className endsWith "$") className.dropRight(1) else className
  6. }
  7. def apply(plan: TreeType): TreeType
  8. }

Strategy:最大的执行次数,如果执行次数在最大迭代次数之前就达到了fix point,策略就会停止,不再应用了。

  1. /**
  2. * An execution strategy for rules that indicates the maximum number of executions. If the
  3. * execution reaches fix point (i.e. converge) before maxIterations, it will stop.
  4. */
  5. abstract class Strategy { def maxIterations: Int }

Analyzer解析主要是根据这些Batch里面定义的策略和Rule来对Unresolved的逻辑计划进行解析的。

这里Analyzer类本身并没有定义执行的方法,而是要从它的父类RuleExecutor[LogicalPlan]寻找,Analyzer也实现了HiveTypeCosercion,这个类是参考Hive的类型自动兼容转换的原理。如图:

RuleExecutor:执行Rule的执行环境,它会将包含了一系列的Rule的Batch进行执行,这个过程都是串行的。

具体的执行方法定义在apply里:

可以看到这里是一个while循环,每个batch下的rules都对当前的plan进行作用,这个过程是迭代的,直到达到Fix Point或者最大迭代次数。

  1. def apply(plan: TreeType): TreeType = {
  2. var curPlan = plan
  3. batches.foreach { batch =>
  4. val batchStartPlan = curPlan
  5. var iteration = 1
  6. var lastPlan = curPlan
  7. var continue = true
  8. // Run until fix point (or the max number of iterations as specified in the strategy.
  9. while (continue) {
  10. curPlan = batch.rules.foldLeft(curPlan) {
  11. case (plan, rule) =>
  12. val result = rule(plan) //这里将调用各个不同Rule的apply方法,将UnResolved Relations,Attrubute和Function进行Resolve
  13. if (!result.fastEquals(plan)) {
  14. logger.trace(
  15. s"""
  16. |=== Applying Rule ${rule.ruleName} ===
  17. |${sideBySide(plan.treeString, result.treeString).mkString("\n")}
  18. """.stripMargin)
  19. }
  20. result //返回作用后的result plan
  21. }
  22. iteration += 1
  23. if (iteration > batch.strategy.maxIterations) { //如果迭代次数已经大于该策略的最大迭代次数,就停止循环
  24. logger.info(s"Max iterations ($iteration) reached for batch ${batch.name}")
  25. continue = false
  26. }
  27. if (curPlan.fastEquals(lastPlan)) { //如果在多次迭代中不再变化,因为plan有个unique id,就停止循环。
  28. logger.trace(s"Fixed point reached for batch ${batch.name} after $iteration iterations.")
  29. continue = false
  30. }
  31. lastPlan = curPlan
  32. }
  33. if (!batchStartPlan.fastEquals(curPlan)) {
  34. logger.debug(
  35. s"""
  36. |=== Result of Batch ${batch.name} ===
  37. |${sideBySide(plan.treeString, curPlan.treeString).mkString("\n")}
  38. """.stripMargin)
  39. } else {
  40. logger.trace(s"Batch ${batch.name} has no effect.")
  41. }
  42. }
  43. curPlan //返回Resolved的Logical Plan
  44. }

二、Rules介绍

    目前Spark SQL 1.0.0的Rule都定义在了Analyzer.scala的内部类。
    在batches里面定义了4个Batch。
    MultiInstanceRelations、CaseInsensitiveAttributeReferences、Resolution、AnalysisOperators 四个。
    这4个Batch是将不同的Rule进行归类,每种类别采用不同的策略来进行Resolve。
    

2.1、MultiInstanceRelation

如果一个实例在Logical Plan里出现了多次,则会应用NewRelationInstances这儿Rule
  1. Batch("MultiInstanceRelations", Once,
  2. NewRelationInstances)
  1. trait MultiInstanceRelation {
  2. def newInstance: this.type
  3. }
  1. object NewRelationInstances extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = {
  3. val localRelations = plan collect { case l: MultiInstanceRelation => l} //将logical plan应用partial function得到所有MultiInstanceRelation的plan的集合
  4. val multiAppearance = localRelations
  5. .groupBy(identity[MultiInstanceRelation]) //group by操作
  6. .filter { case (_, ls) => ls.size > 1 } //如果只取size大于1的进行后续操作
  7. .map(_._1)
  8. .toSet
  9. //更新plan,使得每个实例的expId是唯一的。
  10. plan transform {
  11. case l: MultiInstanceRelation if multiAppearance contains l => l.newInstance
  12. }
  13. }
  14. }

2.2、LowercaseAttributeReferences

同样是partital function,对当前plan应用,将所有匹配的如UnresolvedRelation的别名alise转换为小写,将Subquery的别名也转换为小写。
总结:这是一个使属性名大小写不敏感的Rule,因为它将所有属性都to lower case了。
  1. object LowercaseAttributeReferences extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = plan transform {
  3. case UnresolvedRelation(databaseName, name, alias) =>
  4. UnresolvedRelation(databaseName, name, alias.map(_.toLowerCase))
  5. case Subquery(alias, child) => Subquery(alias.toLowerCase, child)
  6. case q: LogicalPlan => q transformExpressions {
  7. case s: Star => s.copy(table = s.table.map(_.toLowerCase))
  8. case UnresolvedAttribute(name) => UnresolvedAttribute(name.toLowerCase)
  9. case Alias(c, name) => Alias(c, name.toLowerCase)()
  10. case GetField(c, name) => GetField(c, name.toLowerCase)
  11. }
  12. }
  13. }

2.3、ResolveReferences

将Sql parser解析出来的UnresolvedAttribute全部都转为对应的实际的catalyst.expressions.AttributeReference AttributeReferences
这里调用了logical plan 的resolve方法,将属性转为NamedExepression。
  1. object ResolveReferences extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = plan transformUp {
  3. case q: LogicalPlan if q.childrenResolved =>
  4. logger.trace(s"Attempting to resolve ${q.simpleString}")
  5. q transformExpressions {
  6. case u @ UnresolvedAttribute(name) =>
  7. // Leave unchanged if resolution fails.  Hopefully will be resolved next round.
  8. val result = q.resolve(name).getOrElse(u)//转化为NamedExpression
  9. logger.debug(s"Resolving $u to $result")
  10. result
  11. }
  12. }
  13. }

2.4、 ResolveRelations

这个比较好理解,还记得前面Sql parser吗,比如select * from src,这个src表parse后就是一个UnresolvedRelation节点。
这一步ResolveRelations调用了catalog这个对象。Catalog对象里面维护了一个tableName,Logical Plan的HashMap结果。
通过这个Catalog目录来寻找当前表的结构,从而从中解析出这个表的字段,如UnResolvedRelations 会得到一个tableWithQualifiers。(即表和字段) 
这也解释了为什么流程图那,我会画一个catalog在上面,因为它是Analyzer工作时需要的meta data。
  1. object ResolveRelations extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = plan transform {
  3. case UnresolvedRelation(databaseName, name, alias) =>
  4. catalog.lookupRelation(databaseName, name, alias)
  5. }
  6. }

2.5、ImplicitGenerate

如果在select语句里只有一个表达式,而且这个表达式是一个Generator(Generator是一个1条记录生成到N条记录的映射)
当在解析逻辑计划时,遇到Project节点的时候,就可以将它转换为Generate类(Generate类是将输入流应用一个函数,从而生成一个新的流)。
  1. object ImplicitGenerate extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = plan transform {
  3. case Project(Seq(Alias(g: Generator, _)), child) =>
  4. Generate(g, join = false, outer = false, None, child)
  5. }
  6. }

2.6 StarExpansion

在Project操作符里,如果是*符号,即select * 语句,可以将所有的references都展开,即将select * 中的*展开成实际的字段。
  1. object StarExpansion extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = plan transform {
  3. // Wait until children are resolved
  4. case p: LogicalPlan if !p.childrenResolved => p
  5. // If the projection list contains Stars, expand it.
  6. case p @ Project(projectList, child) if containsStar(projectList) =>
  7. Project(
  8. projectList.flatMap {
  9. case s: Star => s.expand(child.output) //展开,将输入的Attributeexpand(input: Seq[Attribute]) 转化为Seq[NamedExpression]
  10. case o => o :: Nil
  11. },
  12. child)
  13. case t: ScriptTransformation if containsStar(t.input) =>
  14. t.copy(
  15. input = t.input.flatMap {
  16. case s: Star => s.expand(t.child.output)
  17. case o => o :: Nil
  18. }
  19. )
  20. // If the aggregate function argument contains Stars, expand it.
  21. case a: Aggregate if containsStar(a.aggregateExpressions) =>
  22. a.copy(
  23. aggregateExpressions = a.aggregateExpressions.flatMap {
  24. case s: Star => s.expand(a.child.output)
  25. case o => o :: Nil
  26. }
  27. )
  28. }
  29. /**
  30. * Returns true if `exprs` contains a [[Star]].
  31. */
  32. protected def containsStar(exprs: Seq[Expression]): Boolean =
  33. exprs.collect { case _: Star => true }.nonEmpty
  34. }
  35. }

2.7 ResolveFunctions

这个和ResolveReferences差不多,这里主要是对udf进行resolve。
将这些UDF都在FunctionRegistry里进行查找。
  1. object ResolveFunctions extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = plan transform {
  3. case q: LogicalPlan =>
  4. q transformExpressions {
  5. case u @ UnresolvedFunction(name, children) if u.childrenResolved =>
  6. registry.lookupFunction(name, children) //看是否注册了当前udf
  7. }
  8. }
  9. }

2.8 GlobalAggregates

全局的聚合,如果遇到了Project就返回一个Aggregate.

  1. object GlobalAggregates extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = plan transform {
  3. case Project(projectList, child) if containsAggregates(projectList) =>
  4. Aggregate(Nil, projectList, child)
  5. }
  6. def containsAggregates(exprs: Seq[Expression]): Boolean = {
  7. exprs.foreach(_.foreach {
  8. case agg: AggregateExpression => return true
  9. case _ =>
  10. })
  11. false
  12. }
  13. }

2.9 typeCoercionRules

这个是Hive里的兼容SQL语法,比如将String和Int互相转换,不需要显示的调用cast xxx  as yyy了。如StringToIntegerCasts。
  1. val typeCoercionRules =
  2. PropagateTypes ::
  3. ConvertNaNs ::
  4. WidenTypes ::
  5. PromoteStrings ::
  6. BooleanComparisons ::
  7. BooleanCasts ::
  8. StringToIntegralCasts ::
  9. FunctionArgumentConversion ::
  10. CastNulls ::
  11. Nil

2.10 EliminateAnalysisOperators

将分析的操作符移除,这里仅支持2种,一种是Subquery需要移除,一种是LowerCaseSchema。这些节点都会从Logical Plan里移除。
 
  1. object EliminateAnalysisOperators extends Rule[LogicalPlan] {
  2. def apply(plan: LogicalPlan): LogicalPlan = plan transform {
  3. case Subquery(_, child) => child //遇到Subquery,不反悔本身,返回它的Child,即删除了该元素
  4. case LowerCaseSchema(child) => child
  5. }
  6. }

三、实践

  补充昨天DEBUG的一个例子,这个例子证实了如何将上面的规则应用到Unresolved Logical Plan:
  当传递sql语句的时候,的确调用了ResolveReferences将mobile解析成NamedExpression。
  可以对照这看执行流程,左边是Unresolved Logical Plan,右边是Resoveld Logical Plan。
  先是执行了Batch Resolution,eg: 调用ResovelRalation这个RUle来使 Unresovled Relation 转化为 SparkLogicalPlan并通过Catalog找到了其对于的字段属性。
  然后执行了Batch Analysis Operator。eg:调用EliminateAnalysisOperators来将SubQuery给remove掉了。
  可能格式显示的不太好,可以向右边拖动下滚动轴看下结果。 :) 

  1. val exec = sqlContext.sql("select mobile as mb, sid as id, mobile*2 multi2mobile, count(1) times from (select * from temp_shengli_mobile)a where pfrom_id=0.0 group by mobile, sid,  mobile*2")
  2. 14/07/21 18:23:32 DEBUG SparkILoop$SparkILoopInterpreter: Invoking: public static java.lang.String $line47.$eval.$print()
  3. 14/07/21 18:23:33 INFO Analyzer: Max iterations (2) reached for batch MultiInstanceRelations
  4. 14/07/21 18:23:33 INFO Analyzer: Max iterations (2) reached for batch CaseInsensitiveAttributeReferences
  5. 14/07/21 18:23:33 DEBUG Analyzer$ResolveReferences$: Resolving 'pfrom_id to pfrom_id#5
  6. 14/07/21 18:23:33 DEBUG Analyzer$ResolveReferences$: Resolving 'mobile to mobile#2
  7. 14/07/21 18:23:33 DEBUG Analyzer$ResolveReferences$: Resolving 'sid to sid#1
  8. 14/07/21 18:23:33 DEBUG Analyzer$ResolveReferences$: Resolving 'mobile to mobile#2
  9. 14/07/21 18:23:33 DEBUG Analyzer$ResolveReferences$: Resolving 'mobile to mobile#2
  10. 14/07/21 18:23:33 DEBUG Analyzer$ResolveReferences$: Resolving 'sid to sid#1
  11. 14/07/21 18:23:33 DEBUG Analyzer$ResolveReferences$: Resolving 'mobile to mobile#2
  12. 14/07/21 18:23:33 DEBUG Analyzer:
  13. === Result of Batch Resolution ===
  14. !Aggregate ['mobile,'sid,('mobile * 2) AS c2#27], ['mobile AS mb#23,'sid AS id#24,('mobile * 2) AS multi2mobile#25,COUNT(1) AS times#26L]   Aggregate [mobile#2,sid#1,(CAST(mobile#2, DoubleType) * CAST(2, DoubleType)) AS c2#27], [mobile#2 AS mb#23,sid#1 AS id#24,(CAST(mobile#2, DoubleType) * CAST(2, DoubleType)) AS multi2mobile#25,COUNT(1) AS times#26L]
  15. ! Filter ('pfrom_id = 0.0)                                                                                                                   Filter (CAST(pfrom_id#5, DoubleType) = 0.0)
  16. Subquery a                                                                                                                                 Subquery a
  17. !   Project [*]                                                                                                                                Project [data_date#0,sid#1,mobile#2,pverify_type#3,create_time#4,pfrom_id#5,p_status#6,pvalidate_time#7,feffect_time#8,plastupdate_ip#9,update_time#10,status#11,preserve_int#12]
  18. !    UnresolvedRelation None, temp_shengli_mobile, None                                                                                         Subquery temp_shengli_mobile
  19. !                                                                                                                                                SparkLogicalPlan (ExistingRdd [data_date#0,sid#1,mobile#2,pverify_type#3,create_time#4,pfrom_id#5,p_status#6,pvalidate_time#7,feffect_time#8,plastupdate_ip#9,update_time#10,status#11,preserve_int#12], MapPartitionsRDD[4] at mapPartitions at basicOperators.scala:174)
  20. 14/07/21 18:23:33 DEBUG Analyzer:
  21. === Result of Batch AnalysisOperators ===
  22. !Aggregate ['mobile,'sid,('mobile * 2) AS c2#27], ['mobile AS mb#23,'sid AS id#24,('mobile * 2) AS multi2mobile#25,COUNT(1) AS times#26L]   Aggregate [mobile#2,sid#1,(CAST(mobile#2, DoubleType) * CAST(2, DoubleType)) AS c2#27], [mobile#2 AS mb#23,sid#1 AS id#24,(CAST(mobile#2, DoubleType) * CAST(2, DoubleType)) AS multi2mobile#25,COUNT(1) AS times#26L]
  23. ! Filter ('pfrom_id = 0.0)                                                                                                                   Filter (CAST(pfrom_id#5, DoubleType) = 0.0)
  24. !  Subquery a                                                                                                                                 Project [data_date#0,sid#1,mobile#2,pverify_type#3,create_time#4,pfrom_id#5,p_status#6,pvalidate_time#7,feffect_time#8,plastupdate_ip#9,update_time#10,status#11,preserve_int#12]
  25. !   Project [*]                                                                                                                                SparkLogicalPlan (ExistingRdd [data_date#0,sid#1,mobile#2,pverify_type#3,create_time#4,pfrom_id#5,p_status#6,pvalidate_time#7,feffect_time#8,plastupdate_ip#9,update_time#10,status#11,preserve_int#12], MapPartitionsRDD[4] at mapPartitions at basicOperators.scala:174)
  26. !    UnresolvedRelation None, temp_shengli_mobile, None

四、总结

    本文从源代码角度分析了Analyzer在对Sql Parser解析出的UnResolve Logical Plan 进行analyze的过程中,所执行的流程。
    流程是实例化一个SimpleAnalyzer,定义一些Batch,然后遍历这些Batch在RuleExecutor的环境下,执行Batch里面的Rules,每个Rule会对Unresolved Logical Plan进行Resolve,有些可能不能一次解析出,需要多次迭代,直到达到max迭代次数或者达到fix point。这里Rule里比较常用的就是ResolveReferences、ResolveRelations、StarExpansion、GlobalAggregates、typeCoercionRules和EliminateAnalysisOperators。
 
 
——EOF——

原创文章,转载请注明:

转载自:OopsOutOfMemory盛利的Blog,作者: OopsOutOfMemory

本文链接地址:http://blog.csdn.net/oopsoom/article/details/38025185

注:本文基于署名-非商业性使用-禁止演绎 2.5 中国大陆(CC BY-NC-ND 2.5 CN)协议,欢迎转载、转发和评论,但是请保留本文作者署名和文章链接。如若需要用于商业目的或者与授权方面的协商,请联系我。

转自:http://blog.csdn.net/oopsoom/article/details/38025185

第三篇:Spark SQL Catalyst源码分析之Analyzer的更多相关文章

  1. 第五篇:Spark SQL Catalyst源码分析之Optimizer

    /** Spark SQL源码分析系列文章*/ 前几篇文章介绍了Spark SQL的Catalyst的核心运行流程.SqlParser,和Analyzer 以及核心类库TreeNode,本文将详细讲解 ...

  2. 第六篇:Spark SQL Catalyst源码分析之Physical Plan

    /** Spark SQL源码分析系列文章*/ 前面几篇文章主要介绍的是spark sql包里的的spark sql执行流程,以及Catalyst包内的SqlParser,Analyzer和Optim ...

  3. 第四篇:Spark SQL Catalyst源码分析之TreeNode Library

    /** Spark SQL源码分析系列文章*/ 前几篇文章介绍了Spark SQL的Catalyst的核心运行流程.SqlParser,和Analyzer,本来打算直接写Optimizer的,但是发现 ...

  4. 第八篇:Spark SQL Catalyst源码分析之UDF

    /** Spark SQL源码分析系列文章*/ 在SQL的世界里,除了官方提供的常用的处理函数之外,一般都会提供可扩展的对外自定义函数接口,这已经成为一种事实的标准. 在前面Spark SQL源码分析 ...

  5. 第二篇:Spark SQL Catalyst源码分析之SqlParser

    /** Spark SQL源码分析系列文章*/ Spark SQL的核心执行流程我们已经分析完毕,可以参见Spark SQL核心执行流程,下面我们来分析执行流程中各个核心组件的工作职责. 本文先从入口 ...

  6. (转)spring boot实战(第三篇)事件监听源码分析

    原文:http://blog.csdn.net/liaokailin/article/details/48194777 监听源码分析 首先是我们自定义的main方法: package com.lkl. ...

  7. Spark Scheduler模块源码分析之TaskScheduler和SchedulerBackend

    本文是Scheduler模块源码分析的第二篇,第一篇Spark Scheduler模块源码分析之DAGScheduler主要分析了DAGScheduler.本文接下来结合Spark-1.6.0的源码继 ...

  8. Spark RPC框架源码分析(三)Spark心跳机制分析

    一.Spark心跳概述 前面两节中介绍了Spark RPC的基本知识,以及深入剖析了Spark RPC中一些源码的实现流程. 具体可以看这里: Spark RPC框架源码分析(二)运行时序 Spark ...

  9. Spark Scheduler模块源码分析之DAGScheduler

    本文主要结合Spark-1.6.0的源码,对Spark中任务调度模块的执行过程进行分析.Spark Application在遇到Action操作时才会真正的提交任务并进行计算.这时Spark会根据Ac ...

随机推荐

  1. pandas 读取文件

    import pandas as pd import matplotlib.pyplot as plt data = pd.read_csv('G:timeCompare.txt', sep=' ', ...

  2. 【BZOJ4503】两个串 FFT

    [BZOJ4503]两个串 Description 兔子们在玩两个串的游戏.给定两个字符串S和T,兔子们想知道T在S中出现了几次, 分别在哪些位置出现.注意T中可能有“?”字符,这个字符可以匹配任何字 ...

  3. zookeeper简单操作

    接下来主要讲述了通过zookeeper服务器自带的zkCli.sh工具模拟客户端访问和操作zookeeper服务器(包括集群服务器). 当成功启动zookeeper服务后,切换到server1/bin ...

  4. Flutter入门之无状态组件

    Flutter核心理念 flutter组件采用函数式响应框架构建,它的灵感来自于React.它设计的核心思想是组件外构建UI,简单解释一下就是组件鉴于它当前的配置和状态来描述它的视图应该是怎样的,当组 ...

  5. Word Formation

    构词 Word Formation 1.派生Derivation 2.合成Compounding 3.截短Clipping 4.混合Blending 1派生Derivation 1).前缀 除少数英语 ...

  6. Windows服务的调试

    1.服务为其他程序调用的情况:首先停止服务,在项目中设置断点,重新启动服务,点击项目中工具,附加到进程,运行调用服务的程序,即可进入之前设置的断点,进而进行调试. 2.服务内方法为自动执行的情况:首先 ...

  7. 转!!ftp学习

    转自:http://blog.csdn.net/wave_1102/article/details/50651433 FTP (File Transfer Protocol) 可说是最古老的协议之一了 ...

  8. 我的Android进阶之旅------>解决Error: specified for property 'mergedManifest' does not exist.

    错误描述 刚运行从Github上面下载而来的代码时,爆了如下所示的bug,错误描述如下所示: Error:A problem was found with the configuration of t ...

  9. JAVA三框架工作原理是什么?

    一.struts的工作原理: 1.初始化,读取struts-config.xml.web.xml等配置文件(所有配置文件的初始化) 2.发送HTTP请求,客户端发送以.do结尾的请求 3.填充Form ...

  10. React官网首页demo(单文件实现版)

    本博客实现React官网首页上展示的demo, 为了方便直接采用单文件的形式, 如果想完整集成 在自己的项目中, 可以参考React官网的安装指南, 安装Create React App. hello ...