要使用一个web应用程序,必须要将表示该应用程序的Context实例部署到一个host实例中。在tomcat中,context实例可以用war文件的形式来部署,也可以将整个web应用拷贝到Tomcat安装目录下的webapp下。对于部署的每个web应用程序,可以在其中包含一个描述文件(该文件是可选的),该文件中包含了对context的配置选项,是xml格式的文件。
注意,tomcat4和tomcat5使用两个应用程序来管理tomcat及其应用的部署,分别是manager应用程序和admin应用程序。这里两个应用程序位于%CATALINA_HOME%/server/webapps目录下,各自有一个描述文件,分别是manager.xml和admin.xml。
    本文将讨论使用一个部署器来部署web应用程序,部署器是org.apache.catalina.Deployer接口的实例。部署器需要与一个host实例相关联,用于部署context实例。部署一个context到host,即创建一个StandardContext实例,并将该context实例添加到host实例中。创建的context实例会随其父容器——host实例而启动(容器的实例在启动时总是会调用其子容器的start方法,除非该该container是一个wrapper实例)。
    本文会先说明tomcat部署器如何部署一个web应用程序,然后描述Deployer接口及其标准实现org.apache.catalina.core.StandardHostDeployer类的工作原理。

tomcat中在StandardHost中使用了一个生命周期监听器(lifecycle listener)org.apache.catalina.startup.HostConfig来部署应用。
当调用StandardHost实例的start方法时,会触发START事件,HostConfig实例会响应该事件,调用其start方法,在该方法中会部署并安装指定目录中的所有的web应用程序。

How Tomcat Works(十八)中,描述了如何使用Digester对象来解析XML文档的内容,但并没有涉及Digester对象中所有的规则,其中被忽略掉的一个主题就是部署器,也就是本文的主题

在Tomcat中,org.apache.catalina.startup.Catalina类是启动类,使用Digester对象来解析server.xml文件,将其中的xml元素转换为java对象。

Catalina类中定义了createStartDigester方法来添加规则到Digester中:

  1. digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));

org.apache.catalina.startup.HostRuleSet类继承自org.apache.commons.digester.RuleSetBase类,作为RuleSetBase的子类,HostRuleSet提供了addRuleInstances方法实现,该方法定义了RuleSet中的规则(Rule)。

下面是HostRuleSet类的addRuleInstances方法的实现片段:

  1. public void addRuleInstances(Digester digester) {
  2. digester.addObjectCreate(prefix + "Host", "org.apache.catalina.core.StandardHost", "className");
  3. digester.addSetProperties(prefix + "Host");
  4. digester.addRule(prefix + "Host", new CopyParentClassLoaderRule(digester));
  5. digester.addRule(prefix + "Host",
  6. new LifecycleListenerRule (digester, "org.apache.catalina.startup.HostConfig", "hostConfigClass"));

正如代码中所示,当出现模式Server/Service/Engine/Host时,会创建一个org.apache.catalina.startup.HostConfig实例,并被添加到host,作为一个生命周期监听器。换句话说,HostConfig对象会处理StandardHost对象的start和stop方法触发的事件。

下面的代码是HostConfig的lifecycleEvent方法实现:

  1. public void lifecycleEvent(LifecycleEvent event) {
  2. // Identify the host we are associated with
  3. try {
  4. host = (Host) event.getLifecycle();
  5. if (host instanceof StandardHost) {
  6. int hostDebug = ((StandardHost) host).getDebug();
  7. if (hostDebug > this.debug) {
  8. this.debug = hostDebug;
  9. }
  10. setDeployXML(((StandardHost) host).isDeployXML());
  11. setLiveDeploy(((StandardHost) host).getLiveDeploy());
  12. setUnpackWARs(((StandardHost) host).isUnpackWARs());
  13. }
  14. } catch (ClassCastException e) {
  15. log(sm.getString("hostConfig.cce", event.getLifecycle()), e);
  16. return;
  17. }
  18.  
  19. // Process the event that has occurred
  20. if (event.getType().equals(Lifecycle.START_EVENT))
  21. start ();
  22. else if (event.getType().equals(Lifecycle.STOP_EVENT))
  23. stop();
  24. }

如果变量host指向的对象是一个org.apache.catalina.core.StandardHost实例,会调用setDeployXML方法,setLiveDeploy方法和setUnpackWARs方法:

  1. setDeployXML(((StandardHost) host).isDeployXML());
  2. setLiveDeploy(((StandardHost) host).getLiveDeploy());
  3. setUnpackWARs(((StandardHost) host).isUnpackWARs());

StandardHost类的isDeployXML方法指明host是否要部署一个描述文件,默认为true。liveDeploy属性指明host是否要周期性的检查是否有新的应用部署。unpackWARs属性指明host是否要解压缩war文件。
接收到START事件后,HostConfig的lifecycleEvent方法会调用start方法来部署web应用:

  1. protected void start() {
  2. if (debug >= 1)
  3. log(sm.getString("hostConfig.start"));
  4. if (host.getAutoDeploy()) {
  5. deployApps();
  6. }
  7. if (isLiveDeploy ()) {
  8. threadStart();
  9. }
  10. }

当autoDeploy属性值为true时(默认为true),则start方法会调用deployApps方法。此外,若liveDeploy属性为true(默认为true),则该方法会开一个新线程调用threadStart方法。
deployApps方法从host中获取appBase属性值(默认为webapps),该值定义于server.xml文件中。部署进程会将%CATALINE_HOME%/webapps目录下的所有目录看做为Web应用程序的目录来执行部署工作。此外,该目录下找到的war文件和描述文件也会被部署。

deployApps方法实现如下:

  1. protected void deployApps() {
  2. if (!(host instanceof Deployer))
  3. return;
  4. if (debug >= 1)
  5. log(sm.getString("hostConfig.deploying"));
  6. File appBase = appBase();
  7. if (!appBase.exists() || !appBase.isDirectory())
  8. return;
  9. String files[] = appBase.list();
  10. deployDescriptors(appBase, files);
  11. deployWARs(appBase, files);
  12. deployDirectories(appBase, files);
  13. }

deployApps方法会调用其他三个方法,deployDescriptors,deployWARs和deployDirectories。对于所有方法,deployApps方法会传入appBase对象和appBase下所有的文件名的数组形式。context实例是通过其路径来标识的,所有的context必须有其唯一路径。已经被部署的contex实例t会被添加到HostConfig对象中已经部署的ArrayList中。因此,在部署一个context实例之前,deployDescriptors,deployWARs和deployDirectories方法必须确保已部署ArrayList中的没有相同路径的context实例。
注意,deployDescriptors,deployWARs和deployDirectories三个方法的调用顺序是固定的

下面方法为部署描述符:

  1. /**
  2. * Deploy XML context descriptors.
  3. */
  4. protected void deployDescriptors(File appBase, String[] files) {
  5.  
  6. if (!deployXML)
  7. return;
  8.  
  9. for (int i = 0; i < files.length; i++) {
  10.  
  11. if (files[i].equalsIgnoreCase("META-INF"))
  12. continue;
  13. if (files[i].equalsIgnoreCase("WEB-INF"))
  14. continue;
  15. if (deployed.contains(files[i]))
  16. continue;
  17. File dir = new File(appBase, files[i]);
  18. if (files[i].toLowerCase().endsWith(".xml")) {
  19.  
  20. deployed.add(files[i]);
  21.  
  22. // Calculate the context path and make sure it is unique
  23. String file = files[i].substring(0, files[i].length() - 4);
  24. String contextPath = "/" + file;
  25. if (file.equals("ROOT")) {
  26. contextPath = "";
  27. }
  28. if (host.findChild(contextPath) != null) {
  29. continue;
  30. }
  31.  
  32. // Assume this is a configuration descriptor and deploy it
  33. log(sm.getString("hostConfig.deployDescriptor", files[i]));
  34. try {
  35. URL config =
  36. new URL("file", null, dir.getCanonicalPath());
  37. ((Deployer) host).install(config, null);
  38. } catch (Throwable t) {
  39. log(sm.getString("hostConfig.deployDescriptor.error",
  40. files[i]), t);
  41. }
  42.  
  43. }
  44.  
  45. }
  46.  
  47. }

部署WAR文件:

  1. /**
  2. * Deploy WAR files.
  3. */
  4. protected void deployWARs(File appBase, String[] files) {
  5.  
  6. for (int i = 0; i < files.length; i++) {
  7.  
  8. if (files[i].equalsIgnoreCase("META-INF"))
  9. continue;
  10. if (files[i].equalsIgnoreCase("WEB-INF"))
  11. continue;
  12. if (deployed.contains(files[i]))
  13. continue;
  14. File dir = new File(appBase, files[i]);
  15. if (files[i].toLowerCase().endsWith(".war")) {
  16.  
  17. deployed.add(files[i]);
  18.  
  19. // Calculate the context path and make sure it is unique
  20. String contextPath = "/" + files[i];
  21. int period = contextPath.lastIndexOf(".");
  22. if (period >= 0)
  23. contextPath = contextPath.substring(0, period);
  24. if (contextPath.equals("/ROOT"))
  25. contextPath = "";
  26. if (host.findChild(contextPath) != null)
  27. continue;
  28.  
  29. if (isUnpackWARs()) {
  30.  
  31. // Expand and deploy this application as a directory
  32. log(sm.getString("hostConfig.expand", files[i]));
  33. try {
  34. URL url = new URL("jar:file:" +
  35. dir.getCanonicalPath() + "!/");
  36. String path = expand(url);
  37. url = new URL("file:" + path);
  38. ((Deployer) host).install(contextPath, url);
  39. } catch (Throwable t) {
  40. log(sm.getString("hostConfig.expand.error", files[i]),
  41. t);
  42. }
  43.  
  44. } else {
  45.  
  46. // Deploy the application in this WAR file
  47. log(sm.getString("hostConfig.deployJar", files[i]));
  48. try {
  49. URL url = new URL("file", null,
  50. dir.getCanonicalPath());
  51. url = new URL("jar:" + url.toString() + "!/");
  52. ((Deployer) host).install(contextPath, url);
  53. } catch (Throwable t) {
  54. log(sm.getString("hostConfig.deployJar.error",
  55. files[i]), t);
  56. }
  57.  
  58. }
  59.  
  60. }
  61.  
  62. }
  63.  
  64. }

也可以直接将Web应用程序整个目录复制到%CATALINA_HOME%/webapps目录下,部署目录:

  1. /**
  2. * Deploy directories.
  3. */
  4. protected void deployDirectories(File appBase, String[] files) {
  5.  
  6. for (int i = 0; i < files.length; i++) {
  7.  
  8. if (files[i].equalsIgnoreCase("META-INF"))
  9. continue;
  10. if (files[i].equalsIgnoreCase("WEB-INF"))
  11. continue;
  12. if (deployed.contains(files[i]))
  13. continue;
  14. File dir = new File(appBase, files[i]);
  15. if (dir.isDirectory()) {
  16.  
  17. deployed.add(files[i]);
  18.  
  19. // Make sure there is an application configuration directory
  20. // This is needed if the Context appBase is the same as the
  21. // web server document root to make sure only web applications
  22. // are deployed and not directories for web space.
  23. File webInf = new File(dir, "/WEB-INF");
  24. if (!webInf.exists() || !webInf.isDirectory() ||
  25. !webInf.canRead())
  26. continue;
  27.  
  28. // Calculate the context path and make sure it is unique
  29. String contextPath = "/" + files[i];
  30. if (files[i].equals("ROOT"))
  31. contextPath = "";
  32. if (host.findChild(contextPath) != null)
  33. continue;
  34.  
  35. // Deploy the application in this directory
  36. log(sm.getString("hostConfig.deployDir", files[i]));
  37. try {
  38. URL url = new URL("file", null, dir.getCanonicalPath());
  39. ((Deployer) host).install(contextPath, url);
  40. } catch (Throwable t) {
  41. log(sm.getString("hostConfig.deployDir.error", files[i]),
  42. t);
  43. }
  44.  
  45. }
  46.  
  47. }
  48.  
  49. }

正如前面描述的, 如果变量liveDeploy的值为true,start方法会调用threadStart()方法

  1. if (isLiveDeploy()) {
  2. threadStart();
  3. }

threadStart()方法会派生一个新线程并调用run()方法,run()方法会定期检查是否有新应用要部署,或已部署的Web应用程序的web.xml是否有修改

下面的run()方法的实现(HostConfig类实现了java.lang.Runnable接口)

  1. /**
  2. * The background thread that checks for web application autoDeploy
  3. * and changes to the web.xml config.
  4. */
  5. public void run() {
  6.  
  7. if (debug >= 1)
  8. log("BACKGROUND THREAD Starting");
  9.  
  10. // Loop until the termination semaphore is set
  11. while (!threadDone) {
  12.  
  13. // Wait for our check interval
  14. threadSleep();
  15.  
  16. // Deploy apps if the Host allows auto deploying
  17. deployApps();
  18.  
  19. // Check for web.xml modification
  20. checkWebXmlLastModified();
  21.  
  22. }
  23.  
  24. if (debug >= 1)
  25. log("BACKGROUND THREAD Stopping");
  26.  
  27. }

部署器用org.apache.catalina.Deployer接口表示,StandardHost实现了 Deployer接口,因此,StandardHost也是一个部署器,它是一个容器,Web应用可以部署到其中,或从中取消部署

下面是Deployer接口的定义:

  1. /* public interface Deployer extends Container { */
  2. public interface Deployer {
  3.  
  4. public static final String PRE_INSTALL_EVENT = "pre-install";
  5.  
  6. public static final String INSTALL_EVENT = "install";
  7.  
  8. public static final String REMOVE_EVENT = "remove";
  9.  
  10. public String getName();
  11.  
  12. public void install(String contextPath, URL war) throws IOException;
  13.  
  14. public void install(URL config, URL war) throws IOException;
  15.  
  16. public Context findDeployedApp(String contextPath);
  17.  
  18. public String[] findDeployedApps();
  19.  
  20. public void remove(String contextPath) throws IOException;
  21.  
  22. public void start(String contextPath) throws IOException;
  23.  
  24. public void stop(String contextPath) throws IOException;
  25.  
  26. }

StandardHost类使用一个辅助类(org.apache.catalina.core.StandardHostDeployer,与StandardHost类都实现了Deployer接口) 来完成部署与安装Web应用程序的相关任务,下面的代码片段演示了StandardHost对象如何将部署任务委托给StandardHostDeployer实例来完成

  1. /**
  2. * The <code>Deployer</code> to whom we delegate application
  3. * deployment requests.
  4. */
  5. private Deployer deployer = new StandardHostDeployer(this);
  6. public void install(String contextPath, URL war) throws IOException {
  7. deployer.install(contextPath, war);
  8. }
  9. public synchronized void install(URL config, URL war) throws
  10. IOException {
  11. deployer.install(config, war);
  12. }
  13. public Context findDeployedApp(String contextPath) {
  14. return (deployer.findDeployedApp(contextPath));
  15. }
  16. public String[] findDeployedApps() {
  17. return (deployer.findDeployedApps());
  18. }
    public void remove(String contextPath) throws IOException {
  19. deployer.remove(contextPath);
  20. }
  21. public void start(String contextPath) throws IOException {
  22. deployer.start(contextPath);
  23. }
  24. public void stop(String contextPath) throws IOException {
  25. deployer.stop(contextPath);
  26. }

org.apache.catalina.core.StandardHostDeployer类是一个辅助类,帮助完成将Web应用程序部署到StandardHost实例的工作。StandardHostDeployer实例由StandardHost对象调用,在其构造函数中,会传入StandardHost类的实例

  1. public StandardHostDeployer(StandardHost host) {
  2.  
  3. super();
  4. this.host = host;
  5.  
  6. }

下面的install()方法用于安装描述符,当HostConfig对象的deployDescriptors方法调用StandardHost实例的install()方法后, StandardHost实例调用该方法

  1. public synchronized void install(URL config, URL war) throws IOException {
  2.  
  3. // Validate the format and state of our arguments
  4. if (config == null)
  5. throw new IllegalArgumentException
  6. (sm.getString("standardHost.configRequired"));
  7.  
  8. if (!host.isDeployXML())
  9. throw new IllegalArgumentException
  10. (sm.getString("standardHost.configNotAllowed"));
  11.  
  12. // Calculate the document base for the new web application (if needed)
  13. String docBase = null; // Optional override for value in config file
  14. if (war != null) {
  15. String url = war.toString();
  16. host.log(sm.getString("standardHost.installingWAR", url));
  17. // Calculate the WAR file absolute pathname
  18. if (url.startsWith("jar:")) {
  19. url = url.substring(4, url.length() - 2);
  20. }
  21. if (url.startsWith("file://"))
  22. docBase = url.substring(7);
  23. else if (url.startsWith("file:"))
  24. docBase = url.substring(5);
  25. else
  26. throw new IllegalArgumentException
  27. (sm.getString("standardHost.warURL", url));
  28.  
  29. }
  30.  
  31. // Install the new web application
  32. this.context = null;
  33. this.overrideDocBase = docBase;
  34. InputStream stream = null;
  35. try {
  36. stream = config.openStream();
  37. Digester digester = createDigester();
  38. digester.setDebug(host.getDebug());
  39. digester.clear();
  40. digester.push(this);
  41. digester.parse(stream);
  42. stream.close();
  43. stream = null;
  44. } catch (Exception e) {
  45. host.log
  46. (sm.getString("standardHost.installError", docBase), e);
  47. throw new IOException(e.toString());
  48. } finally {
  49. if (stream != null) {
  50. try {
  51. stream.close();
  52. } catch (Throwable t) {
  53. ;
  54. }
  55. }
  56. }
  57.  
  58. }

第二个install()方法用于安装WAR文件或目录

  1. public synchronized void install(String contextPath, URL war)
  2. throws IOException {
  3.  
  4. // Validate the format and state of our arguments
  5. if (contextPath == null)
  6. throw new IllegalArgumentException
  7. (sm.getString("standardHost.pathRequired"));
  8. if (!contextPath.equals("") && !contextPath.startsWith("/"))
  9. throw new IllegalArgumentException
  10. (sm.getString("standardHost.pathFormat", contextPath));
  11. if (findDeployedApp(contextPath) != null)
  12. throw new IllegalStateException
  13. (sm.getString("standardHost.pathUsed", contextPath));
  14. if (war == null)
  15. throw new IllegalArgumentException
  16. (sm.getString("standardHost.warRequired"));
  17.  
  18. // Calculate the document base for the new web application
  19. host.log(sm.getString("standardHost.installing",
  20. contextPath, war.toString()));
  21. String url = war.toString();
  22. String docBase = null;
  23. if (url.startsWith("jar:")) {
  24. url = url.substring(4, url.length() - 2);
  25. }
  26. if (url.startsWith("file://"))
  27. docBase = url.substring(7);
  28. else if (url.startsWith("file:"))
  29. docBase = url.substring(5);
  30. else
  31. throw new IllegalArgumentException
  32. (sm.getString("standardHost.warURL", url));
  33.  
  34. // Install the new web application
  35. try {
  36. Class clazz = Class.forName(host.getContextClass());
  37. Context context = (Context) clazz.newInstance();
  38. context.setPath(contextPath);
  39.  
  40. context.setDocBase(docBase);
  41. if (context instanceof Lifecycle) {
  42. clazz = Class.forName(host.getConfigClass());
  43. LifecycleListener listener =
  44. (LifecycleListener) clazz.newInstance();
  45. ((Lifecycle) context).addLifecycleListener(listener);
  46. }
  47. host.fireContainerEvent(PRE_INSTALL_EVENT, context);
  48. host.addChild(context);
  49. host.fireContainerEvent(INSTALL_EVENT, context);
  50. } catch (Exception e) {
  51. host.log(sm.getString("standardHost.installError", contextPath),
  52. e);
  53. throw new IOException(e.toString());
  54. }
  55.  
  56. }

start()方法用于启动Context实例:

  1. public void start(String contextPath) throws IOException {
  2. // Validate the format and state of our arguments
  3. if (contextPath == null)
  4. throw new IllegalArgumentException
  5. (sm.getString("standardHost.pathRequired"));
  6. if (!contextPath.equals("") && !contextPath.startsWith("/"))
  7. throw new IllegalArgumentException
  8. (sm.getString("standardHost.pathFormat", contextPath));
  9. Context context = findDeployedApp(contextPath);
  10. if (context == null)
  11. throw new IllegalArgumentException
  12. (sm.getString("standardHost.pathMissing", contextPath));
  13. host.log("standardHost.start " + contextPath);
  14. try {
  15. ((Lifecycle) context).start();
  16. } catch (LifecycleException e) {
  17. host.log("standardHost.start " + contextPath + ": ", e);
  18. throw new IllegalStateException
  19. ("standardHost.start " + contextPath + ": " + e);
  20. }
  21. }

stop()方法用于停止Context实例:

  1. public void stop(String contextPath) throws IOException {
  2.  
  3. // Validate the format and state of our arguments
  4. if (contextPath == null)
  5. throw new IllegalArgumentException
  6. (sm.getString("standardHost.pathRequired"));
  7. if (!contextPath.equals("") && !contextPath.startsWith("/"))
  8. throw new IllegalArgumentException
  9. (sm.getString("standardHost.pathFormat", contextPath));
  10. Context context = findDeployedApp(contextPath);
  11. if (context == null)
  12. throw new IllegalArgumentException
  13. (sm.getString("standardHost.pathMissing", contextPath));
  14. host.log("standardHost.stop " + contextPath);
  15. try {
  16. ((Lifecycle) context).stop();
  17. } catch (LifecycleException e) {
  18. host.log("standardHost.stop " + contextPath + ": ", e);
  19. throw new IllegalStateException
  20. ("standardHost.stop " + contextPath + ": " + e);
  21. }
  22.  
  23. }

---------------------------------------------------------------------------

本系列How Tomcat Works系本人原创

转载请注明出处 博客园 刺猬的温驯

本人邮箱: chenying998179#163.com (#改为@)

本文链接http://www.cnblogs.com/chenying99/p/3250908.html

How Tomcat Works(二十)的更多相关文章

  1. How Tomcat Works(十四)补充

    在How Tomcat Works(十四)中,本人并没有对javax.servlet.Filter及javax.servlet.FilterChain做详细的描述,本文在这里做一下补充 FilterC ...

  2. How Tomcat Works(十四)

    我们已经知道,在tomcat中有四种类型的servlet容器,分别为Engine.Host.Context 和Wrapper,本文接下来对tomcat中Wrapper接口的标准实现进行说明. 对于每个 ...

  3. How Tomcat Works(十二)

    tomcat容器通过一个称为Session管理器的组件来管理建立的Session对象,该组件由org.apache.catalina.Manager接口表示:Session管理器必须与一个Contex ...

  4. How Tomcat Works(十八)

    在前面的文章中,如果我们要启动tomcat容器,我们需要使用Bootstrap类来实例化连接器.servlet容器.Wrapper实例和其他组件,然后调用各个对象的set方法将它们关联起来:这种配置应 ...

  5. How Tomcat Works(十六)

    本文接下来会介绍Host容器和Engine容器,在tomcat的实际部署中,总是会使用一个Host容器:本文介绍Host接口和Engine接口及其相关类 Host容器是org.apache.catal ...

  6. How Tomcat Works(十五)

    本文接下来分析Context容器,Context容器实例表示一个具体的Web应用程序,其中包括一个或多个Wrapper实例:不过Context容器还需要其他的组件支持,典型的如载入器和Session管 ...

  7. How Tomcat Works(十)

    本文接下来分析tomcat的日志记录器,日志记录器是用来记录消息的组件,在tomcat中,日志记录器需要与某个servlet容器相关连:在org.apache.catalina.logger包下,to ...

  8. How Tomcat Works(十九)

    本文重点关注启动tomcat时会用到的两个类,分别为Catalina类和Bootstrap类,它们都位于org.apachae.catalina.startup包下:Catalina类用于启动或关闭S ...

  9. How Tomcat works — 二、tomcat启动(1)

    主要介绍tomcat启动涉及到的一些接口和类. 目录 概述 tomcat包含的组件 server和service Lifecycle Container Connector 总结 概述 tomcat作 ...

随机推荐

  1. 转 : 配置 mysql-advanced-5.6.21-winx64 免安装版

    mySQL包:mysql-advanced-5.6.21-winx64.zip 下载地址:https://edelivery.oracle.com/EPD/Search/handle_go 服务器版本 ...

  2. nextSibling VS nextElementSibling

    2. nextSibling vs nextElementSibling { //FF { 在Firefox中,link2的nextSibling并不是link3,因为两者之间有一个换行符. 这被认为 ...

  3. python3+ros api

    官方文档:https://wiki.mikrotik.com/wiki/Manual:API_Python3 # !/usr/bin/env python# -*- coding:utf-8 -*-# ...

  4. thinkPHP增删改查的方法案例

    thinkphp对数据库增删改查进行了封装操作,使得使用更加方便,但是不一定灵活. 可以用封装的用,需要写sql,可以执行sql. 1.原始的 $Model = new Model(); // 实例化 ...

  5. __CLASS__

    <?php class base_class { function say_a() { echo "'a' - said the " . __CLASS__ . " ...

  6. zabbix 在linux上安装以及一些配置

    本文章将演示zabbix 3.2版本的安装,供有需要的伙伴们参考: 网络也有很多关于zabbix的安装文档,甚至每一步的配置都有详细的截图,我这里就不演示截图了,多配置几次自然就熟练了.多折腾. 楼主 ...

  7. hibernate中dialect的讲解

    RDBMS方言 DB2 org.hibernate.dialect.DB2Dialect DB2 AS/400 org.hibernate.dialect.DB2400Dialect DB2 OS39 ...

  8. AngularJS绑定数据

    绑定数据总共有三种方式1.{{}}最常用2.ngbind3.ng-model 主要用在input标签

  9. 使用Java进行远程方法调用的几个方案及比较

    Java远程方法调用是编程过程中比较常见的问题,列举一下主要包括如下几类: 1.Java RMI (Remote Method Invocation) 2.EJB远程接口调用 3.WebService ...

  10. leetcode893

    class Solution { public: int numSpecialEquivGroups(vector<string>& A) { set<string> ...