1.整体流程

更为具体的流程为:

在web.xml中可以看到如下配置

<?xml version="1.0" encoding="UTF-8" ?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"><!-- 初始化日志系统 --><listener><listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class></listener><!-- 装载/WEB-INF/webx.xml, /WEB-INF/webx-*.xml --><listener><listener-class>com.alibaba.citrus.webx.context.WebxContextLoaderListener</listener-class></listener><filter><filter-name>mdc</filter-name><filter-class>com.alibaba.citrus.webx.servlet.SetLoggingContextFilter</filter-class></filter><filter><filter-name>webx</filter-name><filter-class>com.alibaba.citrus.webx.servlet.WebxFrameworkFilter</filter-class><init-param><param-name>excludes</param-name><param-value><!-- 需要被“排除”的URL路径,以逗号分隔,如/static, *.jpg。适合于映射静态页面、图片。 --></param-value></init-param><init-param><param-name>passthru</param-name><param-value><!-- 需要被“略过”的URL路径,以逗号分隔,如/myservlet, *.jsp。适用于映射servlet、filter。对于passthru请求,webx的request-contexts服务、错误处理、开发模式等服务仍然可用。 --></param-value></init-param></filter><filter-mapping><filter-name>mdc</filter-name><url-pattern>/*</url-pattern></filter-mapping><filter-mapping><filter-name>webx</filter-name><url-pattern>/*</url-pattern></filter-mapping></web-app>

2.WebxContextLoaderListener

从上面的webx.ml可以看到里面配置了Listener,所以 先看一下WebxContextLoaderListener

public class WebxContextLoaderListener extends ContextLoaderListener {@Overrideprotected final ContextLoader createContextLoader() {return new WebxComponentsLoader() {@Overrideprotected Class<? extends WebxComponentsContext> getDefaultContextClass() {Class<? extends WebxComponentsContext> defaultContextClass = WebxContextLoaderListener.this.getDefaultContextClass();if (defaultContextClass == null) {defaultContextClass = super.getDefaultContextClass();}return defaultContextClass;}};}

WebxContextLoaderListener继承了spring中的ContextLoaderListener,继续看

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {private ContextLoader contextLoader;public void contextInitialized(ServletContextEvent event) {this.contextLoader = createContextLoader();if (this.contextLoader == null) {this.contextLoader = this;}this.contextLoader.initWebApplicationContext(event.getServletContext());}@Deprecatedprotected ContextLoader createContextLoader() {return null;}
.......
}

从这里看以看出两点

1) ContextLoaderListener实现了ServletContextListener接口,并且实现了其中的contextInitialized方法,所以当 ServletContext被创建的时候该方法就会被调用

2)WebxContextLoaderListener重写了createContextLoader方法,生成了WebxComponentsLoader,所以在 ServletContext被创建的时候会调用这个实例的initWebApplicationContext方法

WebxComponentsLoader实际上就是读取bean的配置文件,并实例化相关的bean。(这里由webx框架给我们实例化ApplicationContext,就不用自己再实例化了)

3.WebxFrameworkFilter

webx.ml中配置了这个filter,它的url-mapping对应的是/*,表示任何request都会进入该filter,看下这个filter

    @Overrideprotected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {// 如果指定了excludes,并且当前requestURI匹配任何一个exclude pattern,// 则立即放弃控制,将控制还给servlet engine。if (excludeFilter != null && excludeFilter.matches(request)) {chain.doFilter(request, response);return;}try {getWebxComponents().getWebxRootController().service(request, response, chain);} catch (IOException e) {throw e;} catch (ServletException e) {throw e;} catch (Exception e) {throw new ServletException(e);}}

在filter中配置了<init-param>名称为excludes的参数,表示哪些url请求不经过这个filter。

如果当前请求的uri正好匹配,filter就不用处理这个请求那么就直接跳过这个filter(也就是不经过webx了),把控制权交给servlet 引擎
               如果当前filter需要处理这个请求,就进入WebxRootController的service方法

4.WebxRootController

    public final void service(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws Exception {RequestContext requestContext = null;try {requestContext = assertNotNull(getRequestContext(request, response), "could not get requestContext");if (checkRequest(requestContext)) {request = requestContext.getRequest();response = requestContext.getResponse();RequestHandlerContext ctx = internalHandlerMapping.getRequestHandler(request, response);if (ctx == null) {// 如果定义了passthru filter,则判断request是否被passthru,// 对于需要被passthru的request不执行handleRequest,而直接返回。// 该功能适用于仅将webx视作普通的filter,而filter chain的接下来的部分将可使用webx所提供的request contexts。boolean requestProcessed = false;if (passthruFilter == null || !passthruFilter.matches(request)) {requestProcessed = handleRequest(requestContext);}if (!requestProcessed) {giveUpControl(requestContext, chain);}} else {ctx.getRequestHandler().handleRequest(ctx);}}}
.....
}

首先先获得requestContext,通过getRequestContext(request, response)

requestContext = assertNotNull(getRequestContext(request, response), "could not get requestContext");

然后再看核心部分:

        if (passthruFilter == null || !passthruFilter.matches(request)) {requestProcessed = handleRequest(requestContext);}

如果没有passthru或者和passthru不匹配,也就是说这个请求要由webx来处理了,调用handleRequest来处理请求

看一下这个方法:

protected boolean handleRequest(RequestContext requestContext) throws Exception {HttpServletRequest request = requestContext.getRequest();String path = ServletUtil.getResourcePath(request);// 根据path查找componentWebxComponent component = getComponents().findMatchedComponent(path);boolean served = false;if (component != null) {try {WebxUtil.setCurrentComponent(request, component);served = component.getWebxController().service(requestContext);} finally {WebxUtil.setCurrentComponent(request, null);}}return served;}
}

先获得path,然后通过path找到匹配的WebxComponent,如果没有对应的component,就返回false,即回到前面的方法并放弃控制。否则就直接迪奥哟

5.WebxController

    public boolean service(RequestContext requestContext) throws Exception {PipelineInvocationHandle handle = pipeline.newInvocation();handle.invoke();// 假如pipeline被中断,则视作请求未被处理。filter将转入chain中继续处理请求。return !handle.isBroken();}

可以看出,实际上就是开始执行pipeline了

6.Pipeline(PipelineImpl)

public class PipelineImpl extends AbstractService<Pipeline> implements Pipeline {private Valve[] valves;private String label;public PipelineInvocationHandle newInvocation() {return new PipelineContextImpl(null);}@Overridepublic String toString() {return new ToStringBuilder().append(getBeanDescription()).append(valves).toString();}/*** 实现<code>PipelineContext</code>。*/private final class PipelineContextImpl implements PipelineContext, PipelineInvocationHandle {private int executedIndex = -1;private int executingIndex = -1;private boolean broken;public void invokeNext() {assertInitialized();if (broken) {return;}try {executingIndex++;if (executingIndex <= executedIndex) {throw new IllegalStateException(descCurrentValve() + " has already been invoked: "+ valves[executingIndex]);}executedIndex++;if (executingIndex < valves.length) {Valve valve = valves[executingIndex];try {if (log.isTraceEnabled()) {log.trace("Entering {}: {}", descCurrentValve(), valve);}valve.invoke(this);} catch (PipelineException e) {throw e;} catch (Exception e) {throw new PipelineException("Failed to invoke " + descCurrentValve() + ": " + valve, e);} finally {if (log.isTraceEnabled()) {log.trace("...Exited {}: {}", descCurrentValve(), valve);}}if (executedIndex < valves.length && executedIndex == executingIndex) {if (log.isTraceEnabled()) {log.trace("{} execution was interrupted by {}: {}", new Object[] { descCurrentPipeline(),descCurrentValve(), valve });}}} else {if (log.isTraceEnabled()) {log.trace("{} reaches its end.", descCurrentPipeline());}}} finally {executingIndex--;}}

这里的PipelineInvocationHandler  newInvocation()会new一个PipelineContextImpl内部类对象返回

 private final class PipelineContextImpl implements PipelineContext, PipelineInvocationHandle

这里看一下我们如何在代码中调用Pipeline的

@Autowired
private Pipeline myPipeline;public void invokePipeline() {PipelineInvocationHandle invocation = myPipeline.newInvocation();invocation.invoke();System.out.println(invocation.isFinished());System.out.println(invocation.isBroken());
}

在这里实际上调用的是PipelineContextImpl的invoke(),看一下实现:

        public void invoke() throws IllegalStateException {assertTrue(!isBroken(), ILLEGAL_STATE, "cannot reinvoke a broken pipeline");executingIndex = executedIndex = -1;invokeNext();}

然后才进入invokeNext(),其中有这么一句

 valve.invoke(this);

也就是调用各个valve自己的实现。

现在有一个问题:一个valve执行完之后,如何执行下一个valve呢?

可以看一个自定的valve怎么实现:

public class MyValve implements Valve {public void invoke(PipelineContext pipelineContext) throws Exception {System.out.println("valve started.");pipelineContext.invokeNext(); // 调用后序valvesSystem.out.println("valve ended.");}
}

也就是说每一个valve中都会调用PipelineContextImpl的invokeNext方法,这样的话就可以调用下一个了

注:

1)PipelineInvocationHandler  表示的是一个valve的执行

2)PipelineImpl     维护了一个valve数组,保存了所有的valve
           3)PipelineContext   就是PipelineContextImpl的实现接口之一,代表的是一个valve的执行情况

7.几个重要的Pipeline

(1) 由子应用根据自己的pipeline配置文件(如果有的话,没有就用common子文件夹中的)对请求进行处理

上面是WEB-INF\common目录下的pipeline.xml目录中的pipeline定义。

其中每一个valve都有具体的类与之对应,核心是他们的invoke方法

(2)PrepareForTuibineValve

对应的元素是prepareForTuibine。它用于预备turbine运行时所需要的一些内容,根据request创建并初始化turbine 润达他,并放入pipelineContext,以便valve获得

/*** 预备turbine运行所需要的一些内容。*/
public class PrepareForTurbineValve extends AbstractValve {@Autowiredprivate HttpServletRequest request;public void invoke(PipelineContext pipelineContext) throws Exception {TurbineRunData rundata = getTurbineRunData(request, true);boolean contextSaved = false;try {pipelineContext.setAttribute("rundata", rundata);for (Map.Entry<String, Object> entry : Utils.getUtils().entrySet()) {pipelineContext.setAttribute(entry.getKey(), entry.getValue());}pipelineContext.invokeNext();} catch (Throwable e) {saveTurbineRunDataContext(rundata);contextSaved = true;if (e instanceof Exception) {throw (Exception) e;} else if (e instanceof Error) {throw (Error) e;}} finally {cleanupTurbineRunData(request, !contextSaved);}}public static class DefinitionParser extends AbstractValveDefinitionParser<PrepareForTurbineValve> {}
}

这里有一个问题:如何实现Pipeline共享的呢?

可以看到这里Valve实现类中的invoke方法中有一个参数PipelineContext,这是在PipelineContextImpl中的invokeNext方法中调用valve.invoke(this)传入的

 valve.invoke(this);

这样的话,每一个Valve最后调用invokeNext都会在valve数组中执行下一个Valve

(3)AnalyzeURLValve
                      作用是对请求url和参数进行分析,为接下来的screen和action提供基础数据target等

/*** 根据URL的内容来设置rundata。根据以下规则:* <ol>* <li>取得servletPath + pathInfo - componentPath作为target。</li>* <li>使用MappingRuleService,将target的后缀转换成统一的内部后缀。例如:将jhtml转换成jsp。</li>* </ol>*/
public class AnalyzeURLValve extends AbstractValve {private static final String DEFAULT_ACTION_PARAM_NAME = "action";public void invoke(PipelineContext pipelineContext) throws Exception {TurbineRunDataInternal rundata = (TurbineRunDataInternal) getTurbineRunData(request);String target = null;// 取得target,并转换成统一的内部后缀名。String pathInfo = ServletUtil.getResourcePath(rundata.getRequest()).substring(component.getComponentPath().length());if ("/".equals(pathInfo)) {pathInfo = getHomepage();}// 注意,必须将pathInfo转换成camelCase。int lastSlashIndex = pathInfo.lastIndexOf("/");if (lastSlashIndex >= 0) {pathInfo = pathInfo.substring(0, lastSlashIndex) + "/"+ StringUtil.toCamelCase(pathInfo.substring(lastSlashIndex + 1));} else {pathInfo = StringUtil.toCamelCase(pathInfo);}target = mappingRuleService.getMappedName(EXTENSION_INPUT, pathInfo);rundata.setTarget(target);// 取得actionString action = StringUtil.toCamelCase(trimToNull(rundata.getParameters().getString(actionParam)));action = mappingRuleService.getMappedName(ACTION_MODULE, action);rundata.setAction(action);// 取得actionEventString actionEvent = ActionEventUtil.getEventName(rundata.getRequest());rundata.setActionEvent(actionEvent);pipelineContext.invokeNext();}public static class DefinitionParser extends AbstractValveDefinitionParser<AnalyzeURLValve> {@Overrideprotected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {attributesToProperties(element, builder, "homepage", "actionParam");}}
}

这里ServletUtil.getResourcePath拿到的是servlet path,即除去域名端口剩下的部分(如/user/login.htm),然后subString切掉component的path(即/user),最终拿到的就是pathinfo(即/login.htm)。然后将path info转成驼峰形式。之后通过mappingRuleService得到target。经过这次调用之后,target就变成了/login

在这之后AnalyURLValve做了3件事

1)rundata.setTarget(target):向rundata中设置target,target作为screen的选择依据

2)rundata.setAction(action):向rundata中设置action,action标识了表单提交的处理类

3)rundata.setActionEvent(actionEvent):向rundata中设置actionEvent,actionEvent标识了表单处理类的方法名称

target和action的确定是mappingRuleService根据mappingRule从url和请求参数中获取并对应得到的。事实上,这些都是配置好的,mappingRuleService是webx内置的AbstractService,名称查找规则的配置通过SpringExt加载。

看一个配置webx-componet-and-root.xml:

    <!-- 名称查找规则。 --><services:mapping-rules><!-- External target name => Internal target name --><mapping-rules:extension-rule id="extension.input"><!-- 默认后缀 --><mapping extension="" to="" /><!-- JSP --><mapping extension="jhtml" to="" /><mapping extension="jsp" to="" /><mapping extension="jspx" to="" /><mapping extension="php" to="" /><!-- Velocity --><mapping extension="htm" to="" /><mapping extension="vhtml" to="" /><mapping extension="vm" to="" /></mapping-rules:extension-rule><!-- Internal target name => External target name --><mapping-rules:extension-rule id="extension.output"><!-- 默认后缀 --><mapping extension="" to="htm" /><!-- JSP --><mapping extension="jhtml" to="jhtml" /><mapping extension="jsp" to="jhtml" /><mapping extension="jspx" to="jhtml" /><mapping extension="php" to="jhtml" /><!-- Velocity --><mapping extension="htm" to="htm" /><mapping extension="vhtml" to="htm" /><mapping extension="vm" to="htm" /></mapping-rules:extension-rule><!-- Target name => Action module name --><mapping-rules:direct-module-rule id="action" /><!-- Target name => Screen module name (*.do) --><mapping-rules:direct-module-rule id="screen.notemplate" /><!-- Target name => Screen module name (*.jsp, *.vm) --><mapping-rules:fallback-module-rule id="screen" moduleType="screen" /><!-- Target name => Screen template name --><mapping-rules:direct-template-rule id="screen.template" templatePrefix="screen" /><!-- Target name => Layout template name --><mapping-rules:fallback-template-rule id="layout.template" templatePrefix="layout" /><!-- Target name => Control module name (setControl method) --><mapping-rules:direct-module-rule id="control.notemplate" /><!-- Target name => Control module name (setTemplate method) --><mapping-rules:fallback-module-rule id="control" moduleType="control" /><!-- Target name => Control template name --><mapping-rules:direct-template-rule id="control.template" templatePrefix="control" /></services:mapping-rules>

每一个<extension-rule>标签都会生成一个ExtensionMappingRule实例,作用是根据传入参数的后缀进行后缀替换。target的处理规则就是根据extension.input的规则处理path。例如传入login.htm,输出的是login

<direct-module-rule>标签对应的处理类是DirectModuleMappingRule,规则是对传入的String中的"/"替换为".",并且将action的名称规范化。例如传入supplier/supplierAuditAction,返回supplier.SupplierAuditAction
           actionEvent的提取在ActionEventUtil.getEventName(HttpServletRequest)工具类中是吸纳,它会对event_submit_do开头的请求参数进行截断,取出后面的字符串作为actionEvent。例如请求参数中有event_submit_do_first_audit,那么返回的actionEvent是FirstAudit

(4)CheckCsrfTokenValve
                      用于检查csrf token,防止csrf攻击和重复提交。假如request和session中的token不匹配,则出错,或显示expired页面

(5)LoopValve
                      用于反复执行同一个Pipeline。LoopValve是一个带有子流程的valve(持有一个Pipeline引用),并且内部的valve可以循环调用。只要调用过程中没有中断handle.isBroken()判断,就会一直调用直到超过最大循环次数:10,抛出异常

/*** 用来反复执行同一个子pipeline。* * @author Michael Zhou*/
public class LoopValve extends AbstractValve {private final static int DEFAULT_MAX_LOOP = 10;private final static String DEFAULT_LOOP_COUNTER_NAME = "loopCount";private Pipeline loopBody;private Integer maxLoopCount;private String loopCounterName;public void invoke(PipelineContext pipelineContext) throws Exception {assertInitialized();PipelineInvocationHandle handle = initLoop(pipelineContext);do {invokeBody(handle);} while (!handle.isBroken());pipelineContext.invokeNext();}protected void invokeBody(PipelineInvocationHandle handle) {String loopCounterName = getLoopCounterName();int loopCount = (Integer) handle.getAttribute(loopCounterName);int maxLoopCount = getMaxLoopCount();// maxLoopCount<=0,意味着没有循环次数的限制。if (maxLoopCount > 0 && loopCount >= maxLoopCount) {throw new TooManyLoopsException("Too many loops: exceeds the maximum count: " + maxLoopCount);}handle.invoke();handle.setAttribute(loopCounterName, ++loopCount);}
}

(6)BreakUnlessTargetRedirectedValve
                      这是LoopValve中最后一个valve,用于在内部重定向页面时是否退出循环

public class BreakUnlessTargetRedirectedValve extends BreakValve {@Autowiredprivate HttpServletRequest request;@Overridepublic void invoke(PipelineContext pipelineContext) throws Exception {TurbineRunDataInternal rundata = (TurbineRunDataInternal) getTurbineRunData(request);String target = rundata.getTarget();String redirectTarget = rundata.getRedirectTarget();if (!isEmpty(redirectTarget) && !isEquals(target, redirectTarget)) {rundata.setTarget(redirectTarget);rundata.setRedirectTarget(null);pipelineContext.invokeNext();} else {super.invoke(pipelineContext);}}
}

可以看出,就是先判断你是否时内部重定向并且不是和当前target同名,如果满足则继续循环,否则就跳出循环

(7)PerformActionValve
                      执行action modult,通常用来处理用户提交的表单

public class PerformActionValve extends AbstractValve {@Autowiredprivate HttpServletRequest request;@Autowiredprivate ModuleLoaderService moduleLoaderService;public void invoke(PipelineContext pipelineContext) throws Exception {TurbineRunData rundata = getTurbineRunData(request);// 检查重定向标志,如果是重定向,则不需要将页面输出。if (!rundata.isRedirected()) {String action = rundata.getAction();// 如果找到action,则执行之。if (!StringUtil.isEmpty(action)) {String actionKey = "_action_" + action;// 防止重复执行同一个action。if (rundata.getRequest().getAttribute(actionKey) == null) {rundata.getRequest().setAttribute(actionKey, "executed");try {moduleLoaderService.getModule(ACTION_MODULE, action).execute();} catch (ModuleLoaderException e) {throw new PipelineException("Could not load action module: " + action, e);} catch (Exception e) {throw new PipelineException("Failed to execute action module", e);}}}}pipelineContext.invokeNext();}
}

基本上就是取出rundata和action,如果action存在,则执行moduleLoaderService.getModule(ACTION_MODULE,action).execute()

ModuleLoaderServiceImpl通过ModuleFactory来实际加载module,而ModuleFactory是通过spring初始化时注入的各个模块,所以我们需要在webx.xml中配置模块的路径

ModuleLoaderServiceImpl对web层的action/screen/control等包进行扫描,自动加载到ioc中。要搜索的包名(可以用*通配符)在xml中配置。然后建立module key和module之间的关系。

(8)PerformTemplateScreenValve
                      用于执行screen的渲染类。invoke()的实现在其父类PerformScreenValve中。先取出rundata,如果重定向了,则不执行screen类,否则设置content-type为text/html,通过randata获取target,getScreenModule()通过target找到对应的screen module,调用execute()执行它

Webx处理请求流程相关推荐

  1. 4G LTE/EPC UE 的业务请求流程

    目录 文章目录 目录 UE 发起业务请求(Service Request)流程 UE 侧发起的业务请求流程 网络侧发起的业务请求流程 UE 发起业务请求(Service Request)流程 作用:附 ...

  2. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  3. springMVC请求流程详解

    SpringMVC框架是一个基于请求驱动的Web框架,并且使用了'前端控制器'模型来进行设计, 再根据'请求映射规则'分发给相应的页面控制器进行处理.核心流程: 第一步:发起请求到前端控制器(Disp ...

  4. Okhttp3-网络请求流程解析

    前言 已经大火2年的Retrofit,必然会提到另外两个库,OKhttp3和Rxjava,尤其前者,作为Retrofit网络请求的底层库,我们有必要了解OKhttp3的网络请求是如何运作的,就会理解为 ...

  5. internetreadfile读取数据长度为0_Go发起HTTP2.0请求流程分析(后篇)——标头压缩

    阅读建议 这是HTTP2.0系列的最后一篇,笔者推荐阅读顺序如下: Go中的HTTP请求之--HTTP1.1请求流程分析 Go发起HTTP2.0请求流程分析(前篇) Go发起HTTP2.0请求流程分析 ...

  6. CoAP学习笔记——服务器端繁忙时的处理请求流程

    CoAP学习笔记--服务器端繁忙时的处理请求流程 CoAP是一个简单的请求响应机制,对于一个给定的请求便有一个相应的响应. 很多时候,如果服务器不能立即响应一个CON请求,服务器只能返回一个空应答,这 ...

  7. 修改拦截器里的请求头_OkHttp4 源码分析(1) 请求流程分析

    square/okhttp​github.com 本文基于OkHttp4.7.1分析 同步请求示例代码 OkHttpClient client = new OkHttpClient.Builder() ...

  8. 简述C#中IO的应用 RabbitMQ安装笔记 一次线上问题引发的对于C#中相等判断的思考 ef和mysql使用(一) ASP.NET/MVC/Core的HTTP请求流程...

    简述C#中IO的应用 在.NET Framework 中. System.IO 命名空间主要包含基于文件(和基于内存)的输入输出(I/O)服务的相关基础类库.和其他命名空间一样. System.IO ...

  9. 浏览器工作原理:浅析HTTP请求流程

    一个 TCP 连接过程包括了建立连接.传输数据和断开连接三个阶段. 而 HTTP 协议,正是建立在 TCP 连接基础之上的.HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础,通常由 ...

最新文章

  1. 转载:VMware虚拟机时钟不准的问题(linux图形界面投影到windows配置参考)--略有修改...
  2. python replace_DF.replace介绍
  3. SAP CDS view里,什么时候用left join,什么时候用association
  4. [Leetcode] 第289题 生命游戏
  5. LeetCode 756. 金字塔转换矩阵(回溯)
  6. 微软:中国市场XP主流支持服务结束时间未定
  7. python获取命令行参数,使用getopt获取命令行执行时指定的参数
  8. MySQL5.5安装版安装教程
  9. AI一周热闻:华为年收入突破千亿美元;英伟达发布城市监控数据集
  10. 怎么在html中加入pjax,网页添加Pjax代码实现无刷新加载
  11. 关于数据迁移:解决kettle中mysql的数据是tinyint的kettle查出来后变成boolean问题
  12. 用户注册的邮箱激活模块的设计与实现
  13. php 图形库 锯齿,PHP imageantialias - 是否使用抗锯齿(antialias)功能
  14. IDEA部署web项目时,Artifact中war和war exploded的区别
  15. 使用C++实现FC红白机模拟器 Cartridge 与 Mapper(原理篇)
  16. iOS_实现类似蒲公英、fir等发包平台 通过URL分发内测APP
  17. c语言是世界上最好的语言搞笑图片,C++是世界上最好的语言!不服来辩! | 爆笑囧图...
  18. SpringMVC的数据请求
  19. 云生态瞭望 | 腾讯云生态暖伙伴心
  20. 文盾信息非结构化数据安全解决方案,高效防护企业文档安全

热门文章

  1. RabbitMQ 3.9( 续 )
  2. 对安徽省高等学院计算机教育研究会的一次简单渗透。
  3. 求五个长方体的体积表面积
  4. FFmpeg---cmd命令操作汇总
  5. 心电信号越界怎么回事_心电图机常见故障现象及排除方法
  6. 【bzoj2073】[POI2004]PRZ 状态压缩dp
  7. python财务预算分析_财码Python管理会计小实验—弹性预算vs固定预算
  8. 意法半导体(ST)7月10日宣布收购TouchGFX
  9. #力扣LeetCode883. 三维形体投影面积 @FDDLC
  10. 欧氏距离比较相似度的python实现