小工具      在线工具  汉语词典  css  js  c++  java

Servlet工作原理分析

Java 额外说明

收录于:43天前

从Servlet容器开始

要介绍Servlet,首先要解释一下Servlet容器。 servlet 和servlet 容器之间的关系有点像枪和子弹之间的关系。枪是为子弹而生的,子弹使枪具有杀伤力。虽然它们是相互依存的,但又是相互独立发展的。它们都是适应工业生产的结果。从技术角度来说,意味着通过标准化接口相互解耦、协作。既然接口是连接Servlet和Servlet容器的关键,那么我们就从它们的接口开始吧。

前面提到,Servlet容器是一个独立开发的标准化产品。目前它们的类型有很多,但都有自己的市场定位。很难说谁更好,谁更差。每个都有自己的特点。例如,流行的Jetty在定制和移动领域取得了很好的进展。这里我们以大家最熟悉的Tomcat为例,介绍Servlet容器是如何管理Servlet的。 Tomcat本身也非常复杂。我们只从 servlet 和 servlet 容器之间的接口开始。关于Tomcat的详细介绍可以参考我的另一篇文章《Tomcat系统架构与模式设计分析》。

在Tomcat的容器层面,Context容器是直接管理容器中Servlet的包装类Wrapper,因此Context容器的运行模式将直接影响Servlet的工作模式。

图 1. Tomcat 容器模型

图 1 . Tomcat 容器模型

从上图可以看出,Tomcat的容器分为四个级别。真正管理Servlet的容器​​是Context容器。一个Context对应一个Web项目。这可以很容易地在 Tomcat 配置文件中找到,如下所示:

清单1 上下文配置参数

1

2

<Context path="/projectOne " docBase="D:\projects\projectOne"

reloadable="true" />

下面详细描述Tomcat解析Context容器的过程,包括如何构建Servlet。

Servlet容器启动流程

Tomcat7也开始支持嵌入式功能,并添加了启动类org.apache.catalina.startup.Tomcat。通过创建实例对象并调用start方法可以轻松启动Tomcat。我们还可以使用这个对象来添加和修改Tomcat的配置参数,比如动态添加Context、Servlet等。接下来我们将使用这个Tomcat类来管理一个新的Context容器。我们将选择 Tomcat7 附带的示例 Web 项目,并查看如何将其添加到此 Context 容器中。

清单 2. 将 Web 项目添加到 Tomcat

1

2

3

4

5

6

7

Tomcat tomcat = getTomcatInstance();

File appDir = new File(getBuildDirectory(), "webapps/examples");

tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());

tomcat.start();

ByteChunk res = getUrl("http://localhost:" + getPort() +

              "/examples/servlets/servlet/HelloWorldExample");

assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);

清单 1 中的代码创建一个 Tomcat 实例并添加一个 Web 应用程序,然后启动 Tomcat 并调用 HelloWorldExample servlet 之一来查看是否正确返回了预期的数据。

Tomcat的addWebapp方法代码如下:

清单 3.Tomcat.addWebapp

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17号

18

19

20

public Context addWebapp(Host host, String url, String path) {

       silence(url);

       Context ctx = new StandardContext();

       ctx.setPath( url );

       ctx.setDocBase(path);

       if (defaultRealm == null) {

           initSimpleAuth();

       }

       ctx.setRealm(defaultRealm);

       ctx.addLifecycleListener(new DefaultWebXmlListener());

       ContextConfig ctxCfg = new ContextConfig();

       ctx.addLifecycleListener(ctxCfg);

       ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");

       if (host == null) {

           getHost().addChild(ctx);

       } else {

           host.addChild(ctx);

       }

       return ctx;

}

我们已经介绍过,一个Web应用对应一个Context容器,也就是Servlet运行时的Servlet容器。添加 Web 应用程序时,将创建 StandardContext 容器并为 Context 容器设置必要的参数。 url和path分别代表应用程序。 Tomcat中的访问路径和应用程序的实际物理路径与清单1中的两个参数一致。最重要的配置是ContextConfig。该类将负责解析整个Web应用配置,稍后将详细介绍。最后,将此Context容器添加到父容器Host中。

接下来会调用Tomcat的start方法来启动Tomcat。如果你了解了Tomcat的系统架构,你就会很容易理解Tomcat的启动逻辑。 Tomcat的启动逻辑是基于观察者模式设计的。所有容器都继承Lifecycle接口,该接口管理容器的整个生命周期。所有容器的修改和状态的改变都会通知给注册的观察者(Listener)。对于这种设计模式,请参考《Tomcat系统架构与设计模式第二部分:设计模式》。 Tomcat启动时序图如图2所示。

图 2. Tomcat 主要类的启动时序图(查看大图

图 2. Tomcat 主要类的启动时序图

上图描述了Tomcat启动过程中各主类之间的时序关系。下面我们重点关注examples应用对应的StandardContext容器的启动过程。

当Context容器的初始化状态设置为init时,添加到Context容器中的Listener将会被调用。 ContextConfig 继承 LifecycleListener 接口,该接口在调用清单 3 时被添加到 StandardContext 容器中。 ContextConfig类负责解析整个Web应用程序的配置文件。

ContextConfig的init方法主要完成以下工作:

  1. 创建contextDgester对象用于解析xml配置文件
  2. 读取默认的context.xml配置文件,如果存在则解析
  3. 读取默认主机配置文件,如果存在则解析
  4. 读取默认Context自带的配置文件,如果存在则解析
  5. 设置上下文的 DocBase

ContextConfig的init方法完成后,Context容器会执行startInternal方法。该方法的启动逻辑比较复杂,主要包括以下部分:

  1. 创建一个读取资源文件的对象
  2. 创建类加载器对象
  3. 设置应用程序的工作目录
  4. 启动相关辅助类如:logger、realm、resources等。
  5. 修改启动状态并通知感兴趣的观察者(Web应用程序的配置)
  6. 子容器的初始化
  7. 获取ServletContext并设置必要的参数
  8. 初始化“启动时加载”Servlet

Web应用程序初始化工作

Web应用程序的初始化是在ContextConfig的configureStart方法中实现的。应用程序的初始化主要涉及解析web.xml文件。该文件描述了Web应用的关键信息,也是Web应用的入口。

Tomcat 将首先查找 globalWebXml。该文件的搜索路径在引擎的工作目录中查找两个文件之一:org/apache/catalin/startup/NO_DEFAULT_XML 或conf/web.xml。然后,它查找可能位于 System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default 中的 hostWebXml 文件,然后查找应用程序配置文件示例/WEB-INF/web.xml。 web.xml文件中的每个配置项都会被解析成对应的属性并存储在WebXml对象中。如果当前应用程序支持Servlet3.0,则解析将完成9个额外的任务。这额外的9个任务主要是针对Servlet3.0的新特性,包括jar包中META-INF/web-fragment.xml的解析。并支持评论。

接下来,会将WebXml对象中的属性设置到Context容器中,包括创建Servlet对象、过滤器、监听器等。这段代码在WebXml的configureContext方法中。下面是解析 Servlet 的代码片段:

清单 4. 创建一个 Wrapper 实例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17号

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

for (ServletDef servlet : servlets.values()) {

           Wrapper wrapper = context.createWrapper();

           String jspFile = servlet.getJspFile();

           if (jspFile != null) {

               wrapper.setJspFile(jspFile);

           }

           if (servlet.getLoadOnStartup() != null) {

               wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());

           }

           if (servlet.getEnabled() != null) {

               wrapper.setEnabled(servlet.getEnabled().booleanValue());

           }

           wrapper.setName(servlet.getServletName());

           Map<String,String> params = servlet.getParameterMap();

           for (Entry<String, String> entry : params.entrySet()) {

               wrapper.addInitParameter(entry.getKey(), entry.getValue());

           }

           wrapper.setRunAs(servlet.getRunAs());

           Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();

           for (SecurityRoleRef roleRef : roleRefs) {

               wrapper.addSecurityReference(

                       roleRef.getName(), roleRef.getLink());

           }

           wrapper.setServletClass(servlet.getServletClass());

           MultipartDef multipartdef = servlet.getMultipartDef();

           if (multipartdef != null) {

               if (multipartdef.getMaxFileSize() != null &&

                       multipartdef.getMaxRequestSize()!= null &&

                       multipartdef.getFileSizeThreshold() != null) {

                   wrapper.setMultipartConfigElement(new

MultipartConfigElement(

                           multipartdef.getLocation(),

                           Long.parseLong(multipartdef.getMaxFileSize()),

                           Long.parseLong(multipartdef.getMaxRequestSize()),

                           Integer.parseInt(

                                   multipartdef.getFileSizeThreshold())));

               } else {

                   wrapper.setMultipartConfigElement(new

MultipartConfigElement(

                           multipartdef.getLocation()));

               }

           }

           if (servlet.getAsyncSupported() != null) {

               wrapper.setAsyncSupported(

                       servlet.getAsyncSupported().booleanValue());

           }

           context.addChild(wrapper);

}

此代码清楚地描述了如何将 Servlet 包装到 Context 容器内的 StandardWrapper 中。这里有一个问题,为什么要把Servlet封装成StandardWrapper而不是直接封装成Servlet对象。这里StandardWrapper是Tomcat容器的一部分,具有容器的特性,而Servlet是独立的Web开发标准,不应该强耦合在Tomcat中。

除了将Servlet打包成StandardWrapper并作为子容器添加到Context中之外,所有其他web.xml属性都被解析到Context中,所以Context容器才是真正运行Servlet的Servlet容器。一个Web应用程序对应一个Context容器,而容器的配置属性是由应用程序的web.xml指定的,这样我们就可以了解web.xml扮演什么角色。

创建Servlet实例

之前我们已经完成了Servlet的解析,将其封装成StandardWrapper并添加到Context容器中,但是它仍然无法为我们工作,因为它还没有被实例化。下面我们将介绍Servlet对象是如何创建和初始化的。

创建Servlet对象

如果Servlet的load-on-startup配置项大于0,那么它会在Context容器启动时实例化。前面提到,解析配置文件时会读取默认的globalWebXml。 conf下的web.xml文件中定义了一些默认的配置项,该文件定义了两个Servlet,即:org.apache.catalina.servlets。 DefaultServlet 和 org.apache.jasper.servlet.JspServlet。它们的启动负载分别为1和3。也就是说,这两个Servlet会在Tomcat启动时启动。

创建Servlet实例的方法从Wrapper.loadServlet开始。 loadServlet方法需要做的就是获取servletClass,然后将其传递给InstanceManager,根据servletClass.class创建一个对象。如果这个Servlet是用jsp文件配置的,那么servletClass就是conf/web.xml中定义的org.apache.jasper.servlet.JspServlet。

创建Servlet对象的相关类结构图如下:

图 3. 创建 Servlet 对象的相关类结构

图 3. 创建 Servlet 对象的相关类结构

初始化Servlet

在StandardWrapper的initServlet方法中初始化Servlet。该方法只是简单的调用Servlet的init方法,同时将包装StandardWrapper对象的StandardWrapperFacade作为ServletConfig传递给Servlet。为什么Tomcat容器将StandardWrapperFacade传递给Servlet对象,后面会详细分析。

如果Servlet与jsp文件关联,则JspServlet被预先初始化。接下来会模拟一个简单的请求来调用jsp文件,以便将jsp文件编译成类并初始化该类。

这样,Servlet对象就初始化完成了。其实就是web中Servlet解析的过程。一些不可预知的错误发生时的控制和判断行为等等。这里我们只阐述一些关键环节,试图给大家一个整体的脉络。

下面是这个过程的完整时序图,省略了一些细节。

图 4. 初始化 Servlet 的时序图(查看大图

图 4. 初始化 Servlet 的时序图

Servlet 架构

我们知道Java Web应用程序是基于Servlet规范运行的,那么Servlet本身是如何运行的呢?为什么要设计这样的结构呢?

图5.Servlet顶层类关联图

图 5.Servlet 顶层类关联图

从上图可以看出,Servlet规范就是基于这些类的。与Servlet自动关联的有3个类,分别是ServletConfig、ServletRequest和ServletResponse。这三个类通过容器传递给Servlet。 ServletConfig是Servlet初始化时传递给Servlet的,最后两个是请求到达时调用Servlet时传递的。我们清楚的明白了ServletRequest和ServletResponse在Servlet操作中的意义,但是ServletConfig和ServletContext对于Servlet来说有什么价值呢?如果你仔细观察ServletConfig接口中声明的方法,你会发现这些方法是用来获取这个Servlet的一些配置属性的,而这些配置属性在Servlet运行时可能会用到。 ServletContext 是做什么的? Servlet的运行方式是典型的“握手交互”运行方式。所谓“握手交互”是指两个模块通常为了交换数据​​而准备一个交易场景,并且这个场景跟随交易流程直至交易完成。该交易场景的初始化是根据交易对象指定的参数定制的。这些指定的参数通常是一个配置类。所以你是对的,事务场景是由ServletContext描述的,定制的参数集是由ServletConfig描述的。 ServletRequest 和 ServletResponse 是要交互的特定对象。它们经常被用作传递交互结果的交通工具。

ServletConfig是Servlet init时从容器传递过来的,那么ServletConfig到底是什么呢?

下图是Tomcat容器中ServletConfig和ServletContext的类关系图。

图6. 容器中ServletConfig的类关联图

图 6. ServletConfig 在容器中的类关联图

从上图可以看出,StandardWrapper和StandardWrapperFacade都实现了ServletConfig接口,其中StandardWrapperFacade是StandardWrapper门面类。所以传递给Servlet的是StandardWrapperFacade对象。该类可以保证ServletConfig指定的数据是从StandardWrapper获取的,而不会将ServletConfig不关心的数据暴露给Servlet。

同样,ServletContext 具有与 ServletConfig 类似的结构。 Servlet中实际能够获取到的ServletContext对象也是一个ApplicationContextFacade对象。 ApplicationContextFacade 还确保 ServletContex 只获取它应该从容器中获取的数据。它们都起到了封装数据的作用,并且都使用了门面设计模式。

通过ServletContext,可以获取Context容器中的一些必要的信息,比如应用程序的工作路径、容器支持的Servlet的最低版本等。

Servlet中定义的两个实际对象ServletRequest和ServletResponse是什么? ,当我们创建自己的Servlet类时,通常会使用HttpServletRequest和HttpServletResponse,它们继承了ServletRequest和ServletResponse。为什么Context容器传递过来的ServletRequest和ServletResponse可以转换为HttpServletRequest和HttpServletResponse呢?

图7. 请求相关类结构图

图 7.Request 相关类结构图

上图是Tomcat创建的Request和Response的类结构图。一旦Tomcat收到请求,它会首先创建org.apache.coyote.Request和org.apache.coyote.Response。 Tomcat内部使用这两个类来描述请求和相应的信息类。它们是轻量级的类。 ,它们的作用是,服务器收到请求后,经过简单的分析后,很快将请求分配给后续线程处理,因此它们的对象很小,很容易被JVM回收。接下来,当它被移交给用户线程来处理请求时,会创建 org.apache.catalina.connector.Request 和 org.apache.catalina.connector.Response 对象。这两个对象会遍历整个servlet容器,直到传递给servlet。传递给Servlet的是Request和Response的门面类RequestFacade和RequestFacade。这里使用的外观模式与之前的目的相同——将数据封装在容器中。 。一次请求对应的Request和Response的类变换如下图所示:

图8. 请求和响应转换过程

图 8.Request 和 Response 的转变过程

Servlet 的工作原理

我们已经知道Servlet是如何加载的,Servlet是如何初始化的,以及Servlet的架构。现在的问题是如何称呼它。

当用户从浏览器向服务器发起请求时,通常会包含以下信息:http://hostname:port/contextpath/servletpath。主机名和端口用于建立到服务器的 TCP 连接,以下 URL 用于选择服务器中的子容器来服务用户的请求。那么服务器如何根据这个URL到达正确的Servlet容器呢?

这个事情在Tomcat 7.0中很容易解决,因为这个映射工作有一个特殊的类来完成,它就是org.apache.tomcat.util.http.mapper。此类保存 Tomcat Container 容器中的所有子级。容器信息,当org.apache.catalina.connector。 Request类进入Container容器,mapper会根据请求的hostnane和contextpath,将host和context容器设置到Request的mappingData属性中。所以在Request进入Container容器之前,它要访问的子容器就已经确定了。

图9.Request的Mapper类图

图 9.Request 的 Mapper 类关系图

您可能对 Mapper 中容器之间的完整关系存在疑问。这就回到了图2中MapperListener类的19步初始化过程。下面是MapperListener的init方法代码:

清单 5.MapperListener.init

1

2

3

4

5

6

7

8

9

10

11

12

13

public void init() {

       findDefaultHost();

       Engine engine = (Engine) connector.getService().getContainer();

       engine.addContainerListener(this);

       Container[] conHosts = engine.findChildren();

       for (Container conHost : conHosts) {

           Host host = (Host) conHost;

           if (!LifecycleState.NEW.equals(host.getState())) {

               host.addLifecycleListener(this);

               registerHost(host);

           }

       }

}

这段代码的作用是为整个Container容器中的每个子容器添加MapperListener类作为监听器,这样每当任何一个容器发生变化时,都会通知MapperListener,并获取该容器的MapperListener对应的mapper属性关系也将被拯救。修订。 for循环的作用是将宿主和后续的子容器注册到mapper中。

图 10. 容器中的请求路由图

图 10.Request 在容器中的路由图

上图描述了一个Request请求如何到达最终的Wrapper容器。我们现在知道请求如何到达正确的 Wrapper 容器,但是在请求到达最终的 Servlet 之前仍然需要完成一些步骤。必须执行过滤器链并通知 web.xml 中定义的侦听器。

下一步是执行Servlet 的service 方法。通常,我们定义的servlet不会直接实现javax.servlet.servlet接口,而是继承更简单的HttpServlet类或GenericServlet类。我们可以选择Override对应的方法来实现我们想要完成的事情。

Servlet 确实可以帮助我们完成所有的工作,但是如今的 Web 应用程序很少使用 Servlet 直接与所有页面交互。相反,他们使用更高效的 MVC 框架。这些MVC框架的基本原理都是将所有请求映射到一个Servlet,然后实现service方法,这是MVC框架的入口。

当一个Servlet从Servlet容器中移除时,就意味着该Servlet的生命周期结束了。这时候就会调用Servlet的destroy方法来做一些清理工作。

会话和 Cookie

前面我们已经解释过Servlet是如何调用的。我们基于 Servlet 构建应用程序。那么我们可以从Servlet中获取哪些数据信息呢?

Servlet可以为我们提供两部分数据。一种是Servlet初始化时调用init方法时设置的ServletConfig。该类基本上包含了 Servlet 本身以及 Servlet 运行所在的 Servlet 容器的基本信息。根据前面的介绍,ServletConfig的实际对象是StandardWrapperFacade。看能获取哪些容器信息,可以看看这个类提供了哪些接口。还有一些由 ServletRequest 类提供的数据。它的实际对象是RequestFacade。从提供的方法中我们发现它主要描述了本次请求的HTTP协议信息。因此,要掌握Servlet的工作原理,必须对HTTP协议有清楚的了解。如果你还不知道,可以找一些参考资料。关于这方面,还有Session和Cookie让很多人感到困惑。

会话和 Cookie 对于有经验的 Java Web 用户和初学者来说都是一个令人头疼的问题。 Session和Cookie的作用是维持访问用户与后端服务器的交互状态。它们有各自的优点和缺点。然而讽刺的是,它们的优点和使用场景却是矛盾的。例如,使用cookie传输信息时,随着cookie数量的增加和访问次数的增加,也会占用大量的网络带宽。想象一下如果 cookie 占用 200 字节会发生什么。如果每天有几亿个PV,会占用多少带宽?因此,我们希望在流量大的时候使用Session。然而Session的致命弱点是它不能轻易地在多个服务器之间共享,所以这也限制了Session的使用。

不管Session和Cookie有什么缺点,我们仍然需要使用它们。下面详细说一下Session是如何基于Cookies工作的。实际上有 3 种方法可以让 Session 工作:

  1. 基于URL路径参数,默认支持
  2. 基于cookie,如果不修改Context容器的cookie标识,则默认支持。
  3. 基于SSL,默认不支持,仅当connector.getAttribute("SSLEnabled")为TRUE时支持

第一种情况,当浏览器不支持Cookie功能时,浏览器会将用户的SessionCookieName重写为用户请求的URL参数。传输格式如/path/Servlet;name=value;name2=value2? Name3=value3,其中“Servlet;”后面的 K-V 对是要传递的Path参数,服务器会从这个Path参数中获取用户配置的SessionCookieName。关于这个SessionCookieName,如果web.xml中配置了session-config配置项,那么cookie-config下的name属性就是SessionCookieName的值。如果不配置session-config配置项,默认的SessionCookieName大家都很熟悉。然后Request根据这个SessionCookieName从Parameters中获取Session ID,并将其设置到request.setRequestedSessionId中。

请注意,如果客户端也支持Cookie,Tomcat仍会解析Cookie中的Session ID并覆盖URL中的Session ID。

如果是第三种情况,将根据javax.servlet.request.ssl_session属性值设置Session ID。

有了Session ID,服务器就可以创建一个HttpSession对象。第一个触发是通过请求。 getSession() 方法。如果当前Session ID没有对应的HttpSession对象,则新建一个,并将该对象添加到org.apache.catalina的sessions容器中的Saved中。 Manager,Manager类会管理所有Session的生命周期。 Session过期后会被回收,服务器会被关闭,Session会被序列化到磁盘等。只要这个HttpSession对象存在,用户就可以根据Session ID获取这个对象来维护状态。

图 11. 会话相关类图

图 11.Session 相关类图

从上图可以看出,request.getSession获取到的HttpSession对象实际上是StandardSession对象的门面对象。这和前面的Request、Servlet是一样的原理。下图是Session工作的时序图:

图 12.Session 工作的时序图(查看大图

图 12.Session 工作的时序图

还有一点是,与Session关联的cookie与其他cookie没有什么不同。该配置的配置可以通过web.xml中的session-config配置项来指定。

Servlet 中的监听器

侦听器在整个 Tomcat 服务器中广泛使用。它是基于观察者模式设计的。 Listener的设计为开发Servlet应用提供了一种快速的手段,可以轻松地从另一个垂直维度控制程序和数据。目前,Servlet 为两类事件提供了 5 个观察者接口,分别是:4 个 EventListeners 类型,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 个 LifecycleListeners 类型,ServletContextListener、HttpSessionListener。如下:

图 13.Servlet 中的 Listener(查看大图

图 13.Servlet 中的 Listener

它们基本上涵盖了整个 Servlet 生命周期中您感兴趣的每个事件。这些Listener的实现类可以在web.xml中的<listener>标签中配置。当然,监听器也可以在应用程序内动态添加。需要注意的是,容器启动后就不能再添加ServletContextListener了,因为它监听的事件将不再出现。掌握这些Listener的使用可以让我们的编程更加灵活。

总结

这篇文章涵盖了很多内容。似乎不可能解释每一个细节。本文试图了解从Servlet容器的启动到Servlet的初始化,以及Servlet架构的一些关键点。目的是让读者有一个整体完整的结构图,并对一些疑难问题进行详细分析。我希望能有所帮助。

相关主题

  • 查看文章 《Tomcat系统架构与设计模式》(developerWorks,2010 年 5 月):了解 Tomcat 中容器的体系结构,基本的工作原理,以及 Tomcat 中使用的经典的设计模式介绍。
  • JavaServlet技术介绍(developerWorks,2004 年 12 月):介绍并解释 servlet 是什么,它们是如何工作的,如何使用它们来创建您能够想像到的任意复杂度的 Web 应用程序,以及作为一名专业编程人员,您如何才能最有效地使用 servlet。
  • 参考 阿帕奇雄猫官网,了解 Tomcat 最新动态,以及开发人员参考手册。
  • Servlet最新规范,本文是基于 Servlet3.0 规范讲解的,这里有最新的 Servlet 规范,以及 API 的介绍。
  • HTTP协议,W3C 关于 HTTP 协议的详细描述。
  • developerWorks Web 开发专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
  • developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
  • developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。您还可以通过 Web 2.0 入门 栏目,迅速了解 Web 2.0 的相关概念。
  • 查看 HTML5主题,了解更多和 HTML5 相关的知识和动向。
. . .

相关推荐

额外说明

移植Linux系统,配置驱动对应的设备树。

引用文章:添加链接描述 设备树包 可以到github中下载: https://github.com/Xilinx/device-tree-xlnx/releases/tag/xilinx-v2020.1 设备树的生成 设备树的配置 设备树对应驱动 ZYN

额外说明

UEditor(一):取消自动保存功能

UEditor的自动保存默认是开启的,在编辑的时候是不是就会冒出一句“本地保存成功”,让人恼的是:它自动保存到本地什么地方了不知道,想取消这个功能好像也没有“传说”中的那么简单。   从网上搜罗了下,基本上有三种方法: 1、修改ueditor.confi

额外说明

Java基础 第二节 第十七课

FInal 关键字 概述 使用方式 修饰类 修饰方法 修饰变量 局部变量 -- 基本类型 局部变量 -- 引用类型 成员变量 概述 学习了继承后, 我们知道, 子类可以在父类的基础上改写父类内容, 比如, 方法重写. 那么我们能不能随意的继承 API 中

额外说明

【云驻共创】货物流转数据全自动采集,解决快递积压难题

 随着网上购物的不断普及与发展,快递业务也因此有了突飞猛进的发展,但是在网购高峰期出现爆仓,又该如何解决货物积压难题呢?让我们一起来认识一下货物流转数据全自动采集的妙用吧!看看华为又提出了怎样的解决方案呢? 一、物流智慧化的趋势 国家政策大力支持物流行业

额外说明

PL/SQL中文乱码问题

最近工作原因开始接触Oracle,对于用习惯MySQL的我还是有些不适应的,首先就是开发工具的改变。在Oracle下,PL/SQL dev可以说是Oracle客户端工具中的绝对霸主,那么要使用Oracle自然少不了使用PL/SQL dev。最近就遇到一个

额外说明

Kingbase数据库中查询锁表以及解锁

Kingbase数据库中查询锁表以及解锁 一、根据被锁表的表名,查询出oid(表名区分大小写) select oid from sys_class where relname = '表名'; 二、根据查询出的oid,查询出pid select pid

额外说明

Spring MVC 之 Restful 风格请求⽀持

Tips: REST⻛格请求是什么样的? SpringMVC对REST⻛格请求到底提供了怎样的⽀持 在 Web 系统中,前端通过 HTTP 请求给后端传递参数有四种方式,可以将参数放在请求路径、Query 参数、HTTP 协议头、HTTP 协议体中。而放

额外说明

栈和队列的共同处和不同处

共同处 栈和队列的共同处是:它们都是由几个数据特性相同的元素组成的有限序列,也就是所谓的线性表。 不同处 队列 队列(queue)是限定仅在表的一端插入元素、在另一端删除元素的线性表。 在队列中,允许插入的一端被称之为队尾(rear)允许删除的一端被称之

额外说明

Windows系统缺少ActSizer.ocx文件导致程序无法启动问题

其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个ActSizer.ocx文件

额外说明

wordpress 密码_如何在WordPress中对用户强制使用强密码

密码 We have already noticed other sites requiring their users to have a strong password. 蓝主机 requires their users to have a stro

ads via 小工具