How Tomcat works之第六章:Lifecycle生命周期

第六章:生命周期

Catalian包含几个组件。当战斗机启动的时候,这些组件也需要启动。当catalina停止的时候,这些组件也需要给一个机会去清理。例如,当容器停止的时候,它必须调用所有加载的Servlet的destroy方法并且session管理器必须保存session对象到二级缓存。一个用于启动和停止组件的一致的机制通过org.apache.catalian.LifeCycle接口完成。

 

一个实现了LifeCycle接口的组件也可以出发以下一个或几个组件:BEFORE_START_EVENT,START_EVENT,AFTER_START_EVENT,BEFORE_STOP_EVENT,STOP_EVENT和AFTER_STOP_EVENT。当组件启动的时候经常出发前三个时间。当组件停止的时候经常触发后面三个事件。一个Event是由org.apache.catalina.LifeCycle类实现。当然了,如果一个Container组件可以触发事件,那就必有事件监听器,可以对这些事件写入响应。监听器由org.apache.catalina.LifeCycleListener接口实现。

 

这一章将讨论这三类LifeCycle,LifecycleEvent和LifecycleListener。另外,它也介绍了一个工具类LifeCycleSupport,为组件提供了一个简单的方法触发LifeCycle事件和处理lifeCycle监听器。在这一章中,你也将通过实现了Lifecycle的类构建项目。这个应用是基于第五章的应用程序。

 

LifeCycle接口 

战斗机设计允许一个组件包含另一个组件。例如,一个容器可以包含组件像加载器,一个管理者等等。一个父组件负责启动和停止它的子组件。Catalina设计成这样,所有的组件除了一个其他的全部被拘留在父组件中,这样BootStrap类仅需要启动一个单一组件。这种单一启动/停止原理通过实现LifeCycle接口成为可能。看看清单6.1的LifeCycle接口。

 

清单6.1  The LifeCycle interface

 

package org.apache.catalina;

 

public interface Lifecycle {

  public static final String START_EVENT = "start";

  public static final String BEFORE_START_EVENT ="before_start";

  public static final String AFTER_START_EVENT = "after_start";

  public static final String STOP_EVENT = "stop";

  public static final String BEFORE_STOP_EVENT = "before_stop";

  public static final String AFTER_STOP_EVENT = "after_stop";

 

  public void addLifecycleListener(LifecycleListener listener);

  public LifecycleListener[] findLifecycleListeners();

  public void removeLifecycleListener(LifecycleListener listener);

  public void start() throws LifecycleException;

  public void stop() throws LifecycleException;

}

 

LifeCycle接口中最重要的方法是start和stop。一个组件提供了这些方法的实现,这样它的父类可以启动和停止这个组件。其他的三个方法----addLifeCycleListener,findLifeCycleListener和removeLifeCycleLisenter,都与监听器相关。一个组件可以有监听器,对发生在组件中的事件感兴趣。当一个事件发生的时候,关注那个时间的监听器将被提醒。被LifeCycle接口实例触发的六个事件的名字以public static String 定义。

 

LifeCycleEvent类

 Org.apache.catalina.LifeCycleEvent类表示一个lifecycle事件并在清单6.2中展示:

Listing 6.2: Theorg.apache.catalinaLifecycleEvent interface

 

package org.apache.catalina;

import java.util.EventObject;

 

public final class LifecycleEvent extendsEventObject {

  public LifecycleEvent(Lifecycle lifecycle, String type) {

     this(lifecycle, type, null);

   }

  public LifecycleEvent(Lifecycle lifecycle, String type,

     Object data) {

 

     super(lifecycle);

this.lifecycle = lifecycle;

      this.type = type;

      this.data = data;

   }

  private Object data = null;

  private Lifecycle lifecycle = null;

  private String type = null;

 

  public Object getData() {

      return (this.data);

   }

  public Lifecycle getLifecycle() {

      return (this.lifecycle);

   }

  public String getType() {

      return (this.type);

   }

}

 

LifecycleLisenter接口

Org.apache.catalina.LifecycleLisenter接口表示一个Lifecycle监听器并在清单6.3中展示:

Listing 6.3: Theorg.apache.catalina.LifecycleListener interface

 

package org.apache.catalina;

import java.util.EventObject;

public interface LifecycleListener {

  public void lifecycleEvent(LifecycleEvent event);

}

在接口中只有一个方法,lifecycleEvent,在监听器关注的事件在触发的时候被调用。

 

LifecycleSupport类

实现了LifeCycle的组件和允许监听器关注它的事件必须提供LifeCycle接口里三个方法的实现---addLifeCycleLisenter,findLifecycleLisenters和removeLifecycleListener。那个组件得将添加到它的监听器保存在一个数组或者ArrayList或者一个相似的对象中。Catalina提供了一个工具类使组件更加容器处理监听器和生命周期事件:

Org.apache.catalina.util.LifecycleSupport。LifecycleSupport类在清单6.4中显示:

 

Listing 6.4: The LifecycleSupport class

 

package org.apache.catalina.util;

import org.apache.catalina.Lifecycle;

import org.apache.catalina.LifecycleEvent;

importorg.apache.catalina.LifecycleListener;

 

public final class LifecycleSupport {

  public LifecycleSupport(Lifecycle lifecycle) {

      super();

      this.lifecycle = lifecycle;

   }

 

  private Lifecycle lifecycle = null;

  private LifecycleListener listeners[] = new LifecycleListener[0];

  public void addLifecycleListener(LifecycleListener listener) {

      synchronized (listeners) {

         LifecycleListener results[] =

            new LifecycleListener[listeners.length + 1];

         for (int i = 0; i < listeners.length; i++)

            results[i] = listeners[i];

         results[listeners.length] = listener;

            listeners = results;

      }

   }

 

  public LifecycleListener[] findLifecycleListeners() {

      return listeners;

   }

 

  public void fireLifecycleEvent(String type, Object data) {

      LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);

      LifecycleListener interested[] = null;

      synchronized (listeners) {

         interested = (LifecycleListener[]) listeners.clone();

      }

      for (int i = 0; i < interested.length; i++)

interested[i].lifecycleEvent(event);

   }

 

   publicvoid removeLifecycleListener(LifecycleListener listener) {

      synchronized (listeners) {

         int n = -1;

         for (int i = 0; i < listeners.length; i++) {

              if (listeners[i] == listener) {

                 n = i;

                 break;

              }

         }

         if (n < 0)

              return;

         LifecycleListener results[] =

              newLifecycleListener[listeners.length - 1];

 

         int j = 0;

         for (int i = 0; i < listeners.length; i++) {

              if (i != n)

              results[j++] = listeners[i];

         }

         listeners = results;

      }

   }

}

 

就如在清单6.4中看的,LifecycleSupport类保存所有lifecycleListener在以一个叫做listeners的数组中,这个数组中初始化的时候没有成员。

 

Private LifecycleListener lisenter[] = newLifecycleLisenter[0];

 

当在addLifecycleLisenter方法中添加一个lisenter时,就会创建一个新的数组,这个数组的长度是旧数组的长度加1.之后,将旧数组中的所有元素复制到新数组中并添加一个新的lisenter。当removeLifecycleLisenter方法移除一个lisenter时,也创建一个新的数组,长度是旧的数组减一。之后,除了被移除的那个元素的所有元素被复制到新的数组中。

 

fireLifecycleEvent方法触发一个event。首先,它克隆了lisenter数组。之后,调用每个元素的lifecycleevent方法,传递触发的事件。

 

一个实现了Lifecycle接口的组件可以调用LifecycleSupport类。例如,这章中应用程序里的SimpleContext类声明了一下成员变量:

 

protected LifecycleSupport lifecycle = newLifecycleSupport(this);

 

为了增加一个Lifecycle监听器,SimpleContext类调用了LifecycleSupport类的addLifecycleLisenter方法:

 

public voidaddLifecycleListener(LifecycleListener listener) {

  lifecycle.addLifecycleListener(listener);

}

 

为了移除一个监听器,SimpleContext类调用了LifecycleSupport类的removeLifecycleLisenter方法:

public voidremoveLifecycleListener(LifecycleListener listener) {

  lifecycle.removeLifecycleListener(listener);

}

 

为触发一个事件,SimpleContext类调用了LLifecycleSupport类的fireLifeCycleEvent方法,就像如下代码显示的那样:

lifecycle.fireLifecycleEvent(START_EVENT,null);

 

应用:

这章的应用构建在第五章应用的基础上。阐明了Lifecycle接口和Lifecycle相关类型的使用。它包含一个context和两个wrapper还有一个loader和一个mapper。这个应用程序中组件实现了Lifecycle接口和用作context的监听器。第五章的两个Value在这里不适用为了使应用更简单。这个应用的UML在图6.1中显示。注意到有许多类和接口在UML中不显示:

图6.1 这章应用程序的类图

 

注意SimpleContextLifeCycleListener类代表了一个监听器用来监听SimpleContext类;

 

Ex06.pyrmont.core.SimpleContext

这章的SimpleContext类与第五章的相似,除了实现了Lifecycle接口;SimpleContext使用了一下变量与一个LifecycleSupport实例关联。

 

Protected LifecycleSupport lifecycle = newLifecycleSupport(this);

 

它也使用一个boolean命名started来指明SimpleContext实例是否开始。SimpleContext提供了来自Lifecycle接口方法的实现。这些方法在清单6.5中

 

Listing 6.5: Methods from the Lifecycleinterface.

 

public voidaddLifecycleListener(LifecycleListener listener) {

  lifecycle.addLifecycleListener(listener);

}

public LifecycleListener[]findLifecycleListeners() {

  return null;

}

public voidremoveLifecycleListener(LifecycleListener listener) {

  lifecycle.removeLifecycleListener(listener);

}

public synchronized void start() throwsLifecycleException {

   if(started)

     throw new LifecycleException("SimpleContext has alreadystarted");

   //Notify our interested LifecycleListeners

  lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

  started = true;

try {

      // Start our subordinate components, if any

      if ((loader != null) && (loader instanceof Lifecycle))

         ((Lifecycle) loader).start();

 

      // Start our child containers, if any

      Container Children[] = findChildren();

     for (int i = 0; i < children.length; i++) {

         if (children[i] instanceof Lifecycle)

            ((Lifecycle) children[i]).start();

     }

 

      // Start the Valves in our pipeline (including the basic),

      // if any

      if (pipeline instanceof Lifecycle)

         ((Lifecycle) pipeline).start();

      // Notify our Interested LifecycleListeners

      lifecycle.firelifecycleEvent(START_EVENT, null);

   }

  catch (Exception e) {

      e.printStackTrace();

   }

   //Notify our interested LifecycleListeners

  lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

}

 

public void stop() throwsLifecycleException {

   if(!started)

     throw new LifecycleException("SimpleContext has not beenstarted");

   //Notify our interested LifecycleListeners

  lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);

  lifecycle.fireLifecycleEvent(STOP_EVENT, null);

  started = false;

  try {

      // Stop the Valves in our pipeline (including the basic), if any

      if (pipeline instanceof Lifecycle) (

 

         ((Lifecycle) pipeline).stop();

     }

      // Stop our child containers, if any

      Container children[] = findChildren();

     for (int i = 0; i < children.length; i++) {

         if (children[i] instanceof Lifecycle)

            ((Lifecycle) children[i]).stop();

 

if ((loader != null) && (loaderinstanceof Lifecycle)) {

         ((Lifecycle) loader).stop();

     }

   }

  catch (Exception e) {

     e.printStackTrace();

   }

   //Notify our interested LifecycleListeners

  lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);

}

 

注意start方法如何启动所有的子容器和与之关联的组件比如Loader,Pipeline和Mapper和stop方法如何停止它们。使用这种机理,启动一个容器中的所有组件,你需要去启动最高层次的组件(这里是SimpleContext)。为了停止它们,你仅仅去关闭同样的单一组件。

 

Simplecontext的start方法以校验组件之前是否已经启动开始。如果已经启动,抛出LifecycleException。

 

if (started)

       throw new LifecycleException(

          "SimpleContext has already started");

 

之后触发BEFORE_START_EVENT事件

 

// Notify our interested LifecycleListeners

    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

 

结果,每个监听器,已经注册了SimpleContext实例中关注的事件,会被唤醒。在这个应用中,一种类型的SimpleContextLifecycleLisenter注册了兴趣。在我们讨论SimpleContextLifecycleLisenter类的时候将看到这个监听器发生什么。

 

下一步,start方法设置started值为true表明组将已经被启动。

   Started= true;

 

Start方法稍后启动了所有组件和子容器。当前有两个组件实现了Lifecycle接口,SimpleLoader和SimplePipeLine。SimpleWrapper有两个Wrapper作为它的子容器。这些wrapper也是实现了Lifecycle接口的SimpleWrapper。

 

try {

       // Start our subordinate components, if any

       if ((loader != null) && (loader instanceof Lifecycle))

          ((Lifecycle) loader).start();

 

       // Start our child containers, if any

       Container children[] = findChildren();

       for (int i = 0; i < children.length; i++) {

          if (children[i] instanceof Lifecycle)

              ((Lifecycle) children[i]).start();

       }

 

       // Start the Valves in our pipeline (including the basic),

       // if any

       if (pipeline instanceof Lifecycle)

          ((Lifecycle) pipeline).start();

 

在组件和子容器启动后,start方法触发两个事件:START_EVENT和AFTER_START_EVENT。

 

// Notify our interested LifecycleListeners

    lifecycle.fireLifecycleEvent(START_EVENT, null);

    .

    .

    .

    // Notify our interested LifecycleListeners

    lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

 

 

stop方法触发检查 如果实例已经启动了。如果没有,抛出LifecycleException。

 

if (!started)

       throw new LifecycleException(

          "SimpleContext has not been started");

 

稍后产生了BEFORE_STOP_EVENT和STOP_EVENT事件,并重置了started值。

 

// Notify our interested LifecycleListeners

    lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);

    lifecycle.fireLifecycleEvent(STOP_EVENT, null);

    started = false;

 

下一步,stop方法停止了所有与之关联的组件和SimpleContext实例的子容器。

 

try {

 

       // Stop the Valves in our pipeline (including the basic), if any

       if (pipeline instanceof Lifecycle) {

          ((Lifecycle) pipeline).stop();

       }

 

       // Stop our child containers, if any

       Container children[] = findChildren();

       for (int i = 0; i < children.length; i++) {

          if (children[i] instanceof Lifecycle)

              ((Lifecycle) children[i]).stop();

        }

       if ((loader != null) && (loader instanceof Lifecycle)) {

          ((Lifecycle) loader).stop();

       }

    }

 

最后,触发了AFTER_STOP_EVENT事件。

// Notify our interested LifecycleListeners

    lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);


ex06.pyrmont.core.SimpleLifecycleListener


SimpleContextLifecycleListener类代表了一个SimpleContext实例的监听器。在清单6.6中给出。


package ex06.pyrmont.core;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;

public class SimpleContextLifecycleListener implements
LifecycleListener {

   public void lifecycleEvent(LifecycleEvent event) {
      Lifecycle lifecycle = event.getLifecycle();

System.out.println("SimpleContextLifecycleListener's event " +
          event.getType().toString());
       if (Lifecycle.START_EVENT.equals(event.getType())) {
          System.out.println("Starting context.");
       }
       else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
          System.out.println("Stopping context.");
       }
   }


在SimpleContextLifecycleListener类的LifecycleEvent的实现方法是简单的。它仅仅简单打印了触发事件的类型。如果是一个START_EVENT,LifecycleEvent方法打印Starting context。如果是STOP_EVENT,就打印Stopping 上下文。


ex06.pyrmont.core.SimpleLoader

SimpleLoader类与第五章的相似。除了在这个应用的类中实现了Lifecycle接口。对于这个类实现Lifecycle接口的方法不做任何事情只是忘控制台打印字符串。一个SimpleLoader实例可以通过相关容器开启。


SimpleLoader类的生命周期接口方法在清单6.7中给出。


public void addLifecycleListener(LifecycleListener listener) { }
public LifecycleListener[] findLifecycleListeners() {
   return null;
}
public void removeLifecycleListener(LifecycleListener listener) { }
public synchronized void start() throws LifecycleException {
   System.out.println("Starting SimpleLoader");
}
public void stop() throws LifecycleException { }

ex06.pyrmont.core.SimplePipeline


另外对于PipeLine接口,SimplePipeline类实现了Lifecycle接口。来自Lifecycle接口的方法留下空白除了这个类的实例可以从相关的容器启动的方法。这个类的剩余部分与第五站相似。


ex06.pyrmont.core.SimpleWrapper


这个类与第五章的SimpleWrapper类相似。在这个应用中,实现了Lifecycle接口所以可以从其父类容器中开启。在这个应用中,来自Lifecycle接口方法的大部分是空白的除了开始和停止方法。清单6.8显示了来自Lifecycle接口的实现方法。


public void addLifecycleListener(LifecycleListener listener) {                }
public LifecycleListener[] findLifecycleListeners() {
   return null;
}
public void removeLifecycleListener(LifecycleListener listener) ( }
public synchronized void start() throws LifecycleException {
   System.out.println("Starting Wrapper " + name);
   if (started)
      throw new LifecycleException("Wrapper already started");
   // Notify our interested LifecycleListeners
   lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
   started = true;

   // Start our subordinate components, if any
   if ((loader != null) && (loader instanceof Lifecycle))
      ((Lifecycle) loader).start();
   // Start the Valves in our pipeline (including the basic), if any
   if (pipeline instanceof Lifecycle)
      ((Lifecycle) pipeline).start();
   // Notify our interested LifecycleListeners
   lifecycle.fireLifecycleEvent(START_EVENT, null);
   // Notify our interested LifecycleListeners
   lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}

public void stop() throws LifecycleException {

ystem.out.println("Stopping wrapper " + name);
   // Shut down our servlet instance (if it has been initialized)
   try {
       instance.destroy();
   }
   catch (Throwable t) {
   }
   instance = null;
   if (!started)
      throw new LifecycleException("Wrapper " + name + " not started");
   // Notify our interested LifecycleListeners
   lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
   // Notify our interested LifecycleListeners
   lifecycle.fireLifecycleEvent(STOP_EVENT, null);
   started = false;

   // Stop the Valves in our pipeline (including the basic), if any
   if (pipeline instanceof Lifecycle) {
       ((Lifecycle) pipeline).stop();
   }
   // Stop our subordinate components, if any
   if ((loader != null) && (loader instanceof Lifecycle)) {
       ((Lifecycle) loader).stop();
   }
   // Notify our interested LifecycleListeners

   lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}

SimpleWrapper类中的start方法与SimpleContext类的启动方法相似。它启动了添加到它的任何组件并触发了BEFORE_START_EVENT ,START_EVENT和AFTER_START_EVENT事件。


SimpleWrapper类的stop方法甚至更有趣。在打印一个简单地字符串后,它调用Servlet实例的destory方法。



     System.out.println("Stopping wrapper " + name);
     // Shut down our servlet instance (if it has been initialized)
     try {
        instance.destroy();
     }
     catch (Throwable t) {
}
     instance = null;

之后,检查包装是否已经启动了。如果没有,抛出LifecycleException。


if (!started)
        throw new LifecycleException("Wrapper " + name + " not started");

下一步,触发BEFORE_STOP_EVENT和STOP_EVENT事件并重置了started布尔值。


// Notify our interested LifecycleListeners
     lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
     // Notify our interested LifecycleListeners
     lifecycle.fireLifecycleEvent(STOP_EVENT, null);
     started = false;


下一步,停止了与之关联的加载器和管道组件。在这个应用中,SimpleWrapper实例没有加载器。


// Stop the Valves in our pipeline (including the basic), if any
     if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).stop();
     }
     // Stop our subordinate components, if any
     if ((loader != null) && (loader instanceof Lifecycle)) {
        ((Lifecycle) loader).stop();
     }


最后,触发AFTER_STOP_EVENT事件

// Notify our interested LifecycleListeners
     lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);


总结


在这一章 你学会了如何使用Lifecycle接口工作。这个接口为为组件定义了生命周期并提供了一个高雅的方式发送事件到其他组件。另外,Lifecycle接口也使通过一个单一的

start/stop开启/关闭容器中的所有组件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值