我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎么进行ActionInvoker源码分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎么进行ActionInvoker源码分析

本篇内容介绍了“怎么进行ActionInvoker源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

throw new RenderTemplate(template, templateBinding.data);

这里用RenderTemplate的构造方法new了一个RenderTemplate对象,

然后.......抛出去了。

看看这里抛出去的是什么,先进去看看RenderTemplate类的实现:

public class RenderTemplate extends Result {

再看看Result

public abstract class Result extends RuntimeException {

原来RenderTemplate是个RuntimeException

@

 关于RuntimeException
http://java.sun.com/docs/books/tutorial/essential/exceptions/runtime.html
提到RuntimeException,我们最熟悉的可能就是NullPointerException,由于程序编写疏漏,造成访问空指针时,就会抛出此异常。
我们写个最简单的代码就是这样
public class TestString {
public static void main(String[] args){
testNull(null);
}
public static void testNull(String a){
a.charAt(0);
}
}
main方法里对testNull传了个null,但是charAt方法需要String obj,此时却是null,触发NullPointerException退出程序。
在看看console的信息:
Exception in thread "main" java.lang.NullPointerException
at com.langtest.TestString.testNull(TestString.java:9)
at com.langtest.TestString.main(TestString.java:6)
main 线程引发空指针异常,程序到main后也没对此异常处理的逻辑,导致程序退出,并在控制台打印出错信息。
throw之后的异常对象沿着方法栈往回扔(即调用此方法的方法),如果一直没有截获异常,则一直扔到栈底。

既然是个异常,下一步则是抛向上级调用者,往下走,我们找这个“不是异常的异常”是在何处被截获的。。。 (对比JAVA官网那篇对运行时异常小心翼翼的陈述,这种做法简直有点#_#,要么是因为我太菜,不能理解这么用的高明之处吧)。

Debug F6后,程序转至play.mvc.ActionInvoker的invoke方法中的catch语句。

Result actionResult = null;  ControllerInstrumentation.initActionCall();  try {      Java.invokeStatic(actionMethod, getActionMethodArgs(actionMethod));  } catch (InvocationTargetException ex) {      // It's a Result ? (expected)      if (ex.getTargetException() instanceof Result) {          actionResult = (Result) ex.getTargetException();      } else {          // @Catch          Object[] args = new Object[]{ex.getTargetException()};          List<Method> catches = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Catch.class);          Collections.sort(catches, new Comparator<Method>() {

try... catch块中:

try块:用JAVA的反射机制invoke静态方法,这里其实就是invoke了我们在控制器中写的index方法。

@

 如果不理解反射,一定得去先弄明白,任何框架的源码及要实现一些共同的对象实例化或方法调用的通用接口,基本都得用到反射= =#
自编最土最俗的方式理解反射帮助同学理解反射(可能不一定准确):
反射说:“我是个万能的对象生成器和方法调用器,只要你给我类名,方法名以及相应的参数,我就能帮你new出对象或者调用相应的方法”
不过任何事情都是双刃剑,反射的能力过于强大,可以任意的生成和操作对象,所以就赋予程序员Do evil的可能.
而JAVA本身的设计是个尽量安全的语言,没有指针运算,虚拟机帮着整理内存,异常机制让做出健壮性的程序变得简化不少,所以我觉得反射机制对于JAVA的总体设计思想来说,还是不小的challenge。
此处的invokeStatic方法的两个参数,用debug工具的watch功能可以看到
actionMethod就是通过反射被invoke的方法。

catch块:拦截InvocationTargetException,这个exception是当通过反射的方式invoke的方法throw异常时,反射机制会触发这个异常,并将上一级throw出的异常存为这个异常的taget变量。

本例的过程是这样的,Play框架通过反射的方式invoke 控制器中的index方法(Application.index()),然后进入render(),在render方法里调用renderTemplate方法,在此方法将RenderTemplate这个异常(再次汗)抛出,反射机制发现有异常抛出,随后抛出InvocationTargetException异常,并将RenderTemplate存入InvocationTargetException的target变量..Play在使用反射invoke方法处catch了此异常,然后把target引用的RenderTemplate取出,则得到了render完成的模板。

from Java Api

InvocationTargetException is a checked exception that wraps an exception thrown by an invoked method or constructor.

@

 此处虽然搞明白了这段诡异代码的用途,其实也只是个学习的开始,我觉得学习的入门是读懂,能应用是其次,能对某个事物有批判性思维(知道好坏,知道适用何处才是更高层次)。
无论何时,我们都得有追本溯源的精神,下面是几个疑问:
①为什么框架这么设计
②如果可以,自己想一个替代的实现方式,对比此方式看看为何要用这么怪的设计

ActionInvoker源码分析

既然现在Debug走到ActionInvoker,不妨看看这个类:

由类上的注释:

Invoke an action after an HTTP request.

这个类是根据Http request Invoke相应的action。

这个类没有成员变量和函数,只有三个共有的静态方法,这三个方法分别是(用附加注释的方法解释):

public class ActionInvoker {          //响应请求的主函数,其实ActionInvoker这个类主要用途就是放置这个方法,因此这个类也同样也不具备面向对象特性的类,这个类注重的是响应HTTP请求的逻辑          public static void invoke(Http.Request request, Http.Response response) {          //通过传入的action(ie:Application.index),得到对应的method,以便反射时invoke使用          public static Object[] getActionMethod(String fullAction) {          //从method中取出方法的参数,这两个get方法都是为反射调用服务的。          public static Object[] getActionMethodArgs(Method method) throws Exception {  }

可见invoke是Play框架的运行的核心控件(说是核心是因为web框架的主要职责就是完成处理HTTP请求的过程)。

为了了解Play的核心运行机制,我们断开debug线程,在invoke方法设上断点,重新跑Debug。

进入方法,传入该方法的两个参数是由上一层调用者HttpHandler的内部类MinaInvocation的execute方法传入的。由于HttpHandler里做的工作比ActionInvoker更加基础(Mina应用服务器下的http协议处理及session管理),我们到后面再研究。

public static void invoke(Http.Request request, Http.Response response) {         Monitor monitor = null;          try {              if (!Play.started) {                  return;              }               Http.Request.current.set(request);              Http.Response.current.set(response);               Scope.Params.current.set(new Scope.Params());              Scope.RenderArgs.current.set(new Scope.RenderArgs());              Scope.Session.current.set(Scope.Session.restore());              Scope.Flash.current.set(Scope.Flash.restore());  ......  }

先new一个Monitor ,用来监控。

然后判断Play是否启动。

随后的是一系列xxx.xxx.current.set方法:

这里的current变量都是ThreadLocal:

public static ThreadLocal<Request> current = new ThreadLocal<Request>();

@

//对于Java开发,ThreadLocal是必须要了解的概念。   //ThreadLocal虽然是个对象,但是ThreadLocal的set方法存的东西并不是放在ThreadLocal对象里      public void set(T value) {   Thread t = Thread.currentThread();   ThreadLocalMap map = getMap(t);   if (map != null)   map.set(this, value);   else   createMap(t, value);   }   //由上可见,set方法首先取得当前的Thread对象,然后取得该线程的ThreadLocalMap ,如果map不为空,则写入map,以当前的ThreadLocal对象为key,将传入的value存入map。   //这里也只是个引子,没概念的可能很难理解清楚,毕竟ThreadLocal也不是我这么三言两语能说清的,建议同学多谷哥一下,多看多用多体会。

将Request,Response以及Scope的引用放入当前线程后,实际上是完成了线程的初始化过程。

// 1. Route and resolve format if not already done  if (request.action == null) {      for (PlayPlugin plugin : Play.plugins) {          plugin.routeRequest(request);      }      Router.route(request);  }  request.resolveFormat();

Router.route(request); 根据请求的URL找到router中相应的action,并将action的名字赋值给request.action。

request.resolveFormat();此时request中format为html,如果request中format为null,则根据http头来取得相应的format。

往下走:

// 2. Find the action method   Method actionMethod = null;   try {       Object[] ca = getActionMethod(request.action);       actionMethod = (Method) ca[1];       request.controller = ((Class<?>) ca[0]).getName().substring(12);       request.actionMethod = actionMethod.getName();       request.action = request.controller + "." + request.actionMethod;       request.invokedMethod = actionMethod;   } catch (ActionNotFoundException e) {       throw new NotFound(String.format("%s action not found", e.getAction()));   }

声明一个Method变量,供后面反射Invoke。

getActionMethod(request.action) 前面提到过了,通过request.action这个String得到存有application.index()方法相应Method对象的obj数组。

得到Method对象(ca[1],ca[0]存放的是对应controllers.Application的Class对象)后,将request对象中与Action相关的成员变量赋值。

此处:request.controller值为Application,request.actionMethod值为index,后面两个变量,一个是照前两个拼出来的action,另一个传入的是Method对象。

继续:下面的代码为合并action用到的参数:

// 3. Prepare request params  Scope.Params.current().__mergeWith(request.routeArgs);  // add parameters from the URI query string   Scope.Params.current()._mergeWith(UrlEncodedParser.parseQueryString(new ByteArrayInputStream(request.querystring.getBytes("utf-8"))));  Lang.resolvefrom(request);

routeArgs是在route中附加的http参数:

 public Map<String, String> routeArgs;

除此之外还将QueryString中的参数也合并进来。

后面的Lang.resolvefrom(request)没仔细看实现,看Lang的包名中与i18n有关,这部分等以后专门看国际化的实现单独写吧(继续欠账)。

下面的代码,又看到雷人的片段了...

// 4. Easy debugging ...  if (Play.mode == Play.Mode.DEV) {      Controller.class.getDeclaredField("params").set(null, Scope.Params.current());      Controller.class.getDeclaredField("request").set(null, Http.Request.current());      Controller.class.getDeclaredField("response").set(null, Http.Response.current());      Controller.class.getDeclaredField("session").set(null, Scope.Session.current());      Controller.class.getDeclaredField("flash").set(null, Scope.Flash.current());      Controller.class.getDeclaredField("renderArgs").set(null, Scope.RenderArgs.current());      Controller.class.getDeclaredField("validation").set(null, Java.invokeStatic(Validation.class, "current"));  }

!!!!!!

Controller.class.getDeclaredField("xxx").set(null,xxx);

这里Play用反射的方式将Controller中受保护的静态变量强行赋值!!!

@

 如果之前将程序处理结果实现为运行时异常并在产生结果后直接抛出可以暂时理解为一种策略,那这种做法简直就是简单粗暴了,和我们读的各种经典书籍中的教诲大相径庭。

而且还是自己设置了变量的访问权限后又自己暴力破解赋值......

不过作者在注释里加了句// 4. Easy debugging ...,可能是对此做法无奈的解释吧。

让我不禁又想起那句"Java中的关键字只是给编译器看的"...

不过仔细想想,这可能也是开源项目的特色之一,如果在企业里写这种代码直接破坏框架,不知道老板的脸会怎么黑- -|||..

这里废话这么多,是因为我们学生在校时往往喜欢比较另类的代码,所以学弟学妹需要醒目一下:
仔细想想
我们初涉编程是不是很佩服算出i=3,k=(i++)+(++i)+(i++)的同学
我们是不是佩服过能看懂16层嵌套并且变量名没任何意义的if else
我们是不是对于能写出令人头晕的指针运算的同学无比崇敬过。

自己玩玩可以,但是以后工作,一份代码的生命期有可能是伴随着企业的生命期。
写一些另类代码有很多恶果,最明显的是难以维护。
至于类似修改私有变量的危险行为,虽然用起来比较cool,但是对于个人学习来说,不提倡,尽量不要给自己灌输用非正常途径解决问题的思想。不过这里,没说play不好的意思,只是对于咱们来说,学生阶段对语言的理解力掌控力还太差,需要不断深入学习,假以时日,能有技术方面的批判性思维,能把这些java特性用的恰到好处当然是好事。

又跑题了,回到主题....这部分是判断play的模式(play有两种运行模式DEV和实际运行模式,在config里文件配置切换),在开发模式下,直接将request,response和scope等赋值给Cotroller类相应的静态变量。

可能便于实际invoke控制器时访问这些值。

#遍历各个PlugIn看在action invoke前做些动作。

ControllerInstrumentation.stopActionCall();  for (PlayPlugin plugin : Play.plugins) {      plugin.beforeActionInvocation(actionMethod);  }

ControllerInstrumentation这个类的作用是对allow这个标志位进行操作,allow是个ThreadLocal<Boolean>,对其set值则将其引用存入当前Thread内,换句话说,其实是对Thread做了标记。

public static class ControllerInstrumentation {              public static boolean isActionCallAllowed() {           return allow.get();       }              public static void initActionCall() {           allow.set(true);       }              public static void stopActionCall() {           allow.set(false);       }              static ThreadLocal<Boolean> allow = new ThreadLocal<Boolean>();                 }

beforeActionInvocation方法则是在action前Plugin做的事情,这里我看了一下都是空的实现。

#打开monitor

// Monitoring  monitor = MonitorFactory.start(request.action + "()");

#找到标记@Before Annotation的方法,并先于action invoke执行。

// 5. Invoke the action  try {      // @Before      List<Method> befores = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Before.class);      Collections.sort(befores, new Comparator<Method>() {           public int compare(Method m1, Method m2) {              Before before1 = m1.getAnnotation(Before.class);              Before before2 = m2.getAnnotation(Before.class);              return before1.priority() - before2.priority();          }      });

Controller.getControllerClass()方法返回class controllers.Application。

Java.findAllAnnotatedMethods()找到所有带有@Before Annotation的方法。

再根据各个方法的优先级,来对befores中的Method排序。

此处实现比较器用了匿名内部类,按Before的priority进行排序。

@Retention(RetentionPolicy.RUNTIME)  @Target(ElementType.METHOD)  public @interface Before {                 String[] unless() default {};                 int priority() default 0;        }

Annotation Before除了成员变量priority外,还有一个String数组变量unless,存放的是action的名字,表示不拦截这些action。

看看这部分的实现。

ControllerInstrumentation.stopActionCall();     //遍历包含Before Annotation的方法  for (Method before : befores) {         //取出当前Before action的unless数组      String[] unless = before.getAnnotation(Before.class).unless();         //设置标志位      boolean skip = false;         //遍历unless数组      for (String un : unless) {          if (!un.contains(".")) {              un = before.getDeclaringClass().getName().substring(12) + "." + un;          }            //如果unless与当前被调用的action名字相同,标志位skip设为true,退出循环          if (un.equals(request.action)) {              skip = true;              break;          }      }        //如果skip为false,调用before方法      if (!skip) {             //加个保护,判断被调用方法是否为静态,因为下面用到得是invokeStatic..          if (Modifier.isStatic(before.getModifiers())) {              before.setAccessible(true);              Java.invokeStatic(before, getActionMethodArgs(before));          }      }  }

通过Before拦截器后,再往下就是我们前面看到的实际执行Action的地方:

//声明一个Result变量用来保存方法调用的结构  Result actionResult = null;        //与之前stopActionCall()相反,这里调用initActionCall()将allow设为true,意思是允许此线程invoke方法        ControllerInstrumentation.initActionCall();        try {            //invoke action            Java.invokeStatic(actionMethod, getActionMethodArgs(actionMethod));        } catch (InvocationTargetException ex) {            // It's a Result ? (expected)              if (ex.getTargetException() instanceof Result) {                //得到调用action后返回的Result                actionResult = (Result) ex.getTargetException();                //else部分本例未涉及,先跳过不管            } else {            .....

执行完action,下面的代码部分是After拦截器,和Before基本一致,不赘述。

随后将monitor关闭。

之后...继续将返回结果往上扔。

// Ok, rethrow the original action result  if (actionResult != null) {      throw actionResult;  }   throw new NoResult();
catch (InvocationTargetException ex) {                    // It's a Result ? (expected)                    if (ex.getTargetException() instanceof Result) {                        throw (Result) ex.getTargetException();                    }                    // Rethrow the enclosed exception                    if (ex.getTargetException() instanceof PlayException) {                        throw (PlayException) ex.getTargetException();                    }                    StackTraceElement element = PlayException.getInterestingStrackTraceElement(ex.getTargetException());                    if (element != null) {                        throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), ex.getTargetException());                    }                    throw new JavaExecutionException(Http.Request.current().action, ex);                }

一直扔到invoke方法的第一个try..catch块。

public static void invoke(Http.Request request, Http.Response response) {       Monitor monitor = null;       try {           .......       }catch (Result result) {           //遍历执行plugin的onActionInvocationResult()方法,对结果进行处理           for (PlayPlugin plugin : Play.plugins) {               plugin.onActionInvocationResult(result);           }            // Ok there is a result to apply           // Save session & flash scope now            Scope.Session.current().save();           Scope.Flash.current().save();           //相应结果的apply方法,此处result实际是RenderTemplate对象,它的apply方法最终的HTML输出           result.apply(request, response);            //这里可见Plugin的功能是非常灵活的,因为几乎在action生命期的每阶段都出现,其实到后面可以发现PlugIn几乎随处可见,否则怎么能叫做框架的插件呢= =#           for (PlayPlugin plugin : Play.plugins) {               plugin.afterActionInvocation();           }

最后看看RenderTemplate类

public class RenderTemplate extends Result {            private Template template;      private String content;      Map<String,Object> args;            public RenderTemplate(Template template, Map<String,Object> args) {          this.template = template;          this.args = args;          this.content = template.render(args);      }       //apply方法是在invoke方法截获Result后,确认其是需要的返回结果后,调用的结果最终执行代码      public void apply(Request request, Response response) {          try {              setContentTypeIfNotSet(response, MimeTypes.getContentType(template.name, "text/plain"));              response.out.write(content.getBytes("utf-8"));          } catch(Exception e) {              throw new UnexpectedException(e);          }      }   }

执行完结果代码后,来到Invoke方法的结尾处,仍处于catch块,即找到@final的方法并执行。

// @Finally     //这个if判断不知道有什么意义,前面在get action的时候,就是找Application(Controller Class)的action方法,此处怎么会得到null呢,等以后理解加深再解释吧。     if (Controller.getControllerClass() != null) {         try {             List<Method> allFinally = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Finally.class);             //后面略,与@before和@after同             }

Controller.getControllerClass()这个方法,涉及到Play的classloader,大概看了一下,比较复杂,等以后专门研究。

不过其中发现一些比较核心的与play热加载功能相关的代码,如下:

byte[] bc = BytecodeCache.getBytecode(name, applicationClass.javaSource);                 if (bc != null) {                     applicationClass.enhancedByteCode = bc;                     applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, applicationClass.enhancedByteCode.length, protectionDomain);                     resolveClass(applicationClass.javaClass);                     Logger.trace("%sms to load class %s from cache", System.currentTimeMillis() - start, name);                     return applicationClass.javaClass;                 }

这里大概能看出,play可以直接通过读java源代码来动态的生成java class.这应该与Play修改代码不需编译就能运行有关。

小结:到此处,从两个层次学习了Play框架的中处理和响应请求的模块。

最里面一层是Controller层,就是Application,这里放置的是Request最终invoke的action

往外一层是ActionInvoker,负责通过Http Request来判断需要调用的action,并执行调用,此外,还对action起拦截器作用分别在action的生命期的几个阶段Before,After和Finally阶段进行拦截并执行有相应Annotation的方法。除了上述两个作用,ActionInvoker还负责执行PlugIn。可以看出ActionInvoker的职责是控制action。

由此容易想到,ActionInvoker外面应该还有一层,负责实际获取客户端的HTTP Request,并转给ActionInvoker,是的,这个类就是HttpHandler,在下一篇我会详细分析。

画图表示从客户端的Request进入Play到Response返回并跳出Play的过程

怎么进行ActionInvoker源码分析

“怎么进行ActionInvoker源码分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

怎么进行ActionInvoker源码分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么进行ActionInvoker源码分析

本篇内容介绍了“怎么进行ActionInvoker源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!throw new RenderTe
2023-06-17

怎么进行FileZilla源代码分析

怎么进行FileZilla源代码分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代码程式,具有多种特色
2023-06-16

怎样进行FileZilla的源代码分析

本篇文章为大家展示了怎样进行FileZilla的源代码分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代码程式,具有多种特色、直
2023-06-16

怎么进行Go语言HTTP Server源码分析

这期内容当中小编将会给大家带来有关怎么进行Go语言HTTP Server源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Go语言中HTTP Server:HTTP server,顾名思义,支持ht
2023-06-17

如何进行FileZilla的源代码分析

这篇文章将为大家详细讲解有关如何进行FileZilla的源代码分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代码程式,
2023-06-16

Netty源码分析NioEventLoop怎么执行select

本篇内容主要讲解“Netty源码分析NioEventLoop怎么执行select”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Netty源码分析NioEventLoop怎么执行select”吧!
2023-06-29

如何进行HashMap扩容机制源码分析

这期内容当中小编将会给大家带来有关如何进行HashMap扩容机制源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。具体看源码之前,我们先简单的说一下HashMap的底层数据结构  1、HashMap底
2023-06-02

如何进行Netlink源码及实例的分析

本篇文章给大家分享的是有关如何进行Netlink源码及实例的分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。前言这几天在看 ipvs 相关代码的时候又遇到了 netlink
2023-06-15

HashMap源码怎么分析

这篇文章主要介绍“HashMap源码怎么分析”,在日常操作中,相信很多人在HashMap源码怎么分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”HashMap源码怎么分析”的疑惑有所帮助!接下来,请跟着小编
2023-06-05

怎样进行SAP Data Intelligence Graph json源代码的结构分析

本篇文章给大家分享的是有关怎样进行SAP Data Intelligence Graph json源代码的结构分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。以SAP Dat
2023-06-03

Java异步编程中如何进行FutureTask源码分析

本篇文章给大家分享的是有关Java异步编程中如何进行FutureTask源码分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java的异步编程是一项非常常用的多线程技术。但之
2023-06-19

Go语言进阶freecache源码分析

这篇文章主要介绍“Go语言进阶freecache源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go语言进阶freecache源码分析”文章能帮助大家解决问题。00. 什么是 freecach
2023-07-06

怎么用源代码分析FileZilla

本篇文章给大家分享的是有关怎么用源代码分析FileZilla,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代
2023-06-16

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录