热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

责任链模式综述(基础篇)

摘要:本篇综述责任链模式的提出动机、原理结构、典型实现和应用场景,并结合具体实例展现了其灵活性、可插拔性和松耦合性。首先,结合我们日常生活中“打扑克”的例子引出了责任链模式产生动机

摘要:

  本篇综述责任链模式的提出动机、原理结构、典型实现和应用场景,并结合具体实例展现了其灵活性、可插拔性和松耦合性。首先,结合我们日常生活中“打扑克”的例子引出了责任链模式产生动机,并揭示了其应用场景。紧接着,我们概述了责任链模式的内涵和结构,即通过建立一条责任链来组织请求的处理者,请求将沿着链进行传递,而请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。此外,本文给出了责任链模式的典型实现和并结合具体实例介绍其使用方式,以便我们更好体会其优点。


版权声明:

本文原创作者:书呆子Rico
作者博客地址:http://blog.csdn.net/justloveyou_/


友情提示:

  关于责任链模式的介绍共分为两篇,本篇《责任链模式综述(基础篇)》主要介绍了责任链模式的提出动机、原理结构、典型实现和应用场景,解释了责任链模式的本质内涵。本篇的姊妹篇《责任链模式进阶:与AOP思想的融合与应用》主要引入了AOP理念,并在此基础上进一步将责任链结构的实现用方面抽象出来,使得各个具体处理者只需关注自身必须实现的功能性需求。我们知道,无论是Java Web中的过滤器还是Struts2中的Interceptor,它们都是责任链模式与AOP思想互相融合的巧妙实践。为了更进一步理解AOP和CoR,本篇概述了Java Web 的Filter机制,并手动模拟了该机制。此外,我们还结合Struts2的源码和文档解释了拦截器的工作原理,更进一步剖析了AOP理念和CoR模式在Java中的应用,同时也有助于了解Struts2的原理。


一. 引子 —— 责任链模式提出动机与应用场景

  责任链模式在我们生活中有着诸多的应用。比如,在我们玩打扑克的游戏时,某人出牌给他的下家,下家会看看手中的牌,如果要不起上家的牌,则将出牌请求再转发给他的下家,其下家再进行判断,如此反复。一个循环下来,如果其他人都要不起该牌,则最初的出牌者可以打出新的牌。在这个过程中,扑克牌作为一个请求沿着一条链(环)在传递,每一位纸牌的玩家都可以处理该请求。在设计模式中,我们也有一种专门用于处理这种 请求链式传递问题 的模式,即 责任链模式 (Chain of Responsibility Pattern)。

  此外,采购的分级审批问题也是责任链模式的一个应用典范。我们知道,采购审批往往是分级进行的。也就是说,其常常根据采购金额的不同由不同层次的主管人员来审批。例如,主任可以审批 5 万元以下(不包括 5 万元)的采购单,副董事长可以审批 5 万元至 10 万元(不包括 10 万元)的采购单,董事长可以审批 10 万元至 50 万元(不包括 50 万元)的采购单,50 万元及以上的采购单就需要开董事会讨论决定。此案例如图所示:

             

  那么,如何在软件中实现采购单的分级审批呢?这时,如果我们不了解职责链模式,我们给出的解决方案可能是这样的:在系统中提供一个采购单处理类 PurchaseRequestHandler 用于统一处理采购单,其框架代码如下所示:

//采购单处理类 
class PurchaseRequestHandler {

//递交采购单给主任
public void handlePurchaseRequest(PurchaseRequest request) {
if (request.getAmount() <50000) {
//由主任审批该采购单
this.handleByDirector(request);
}
else if (request.getAmount() <100000) {
//由副董事长审批该采购单
this.handleByVicePresident(request);
}
else if (request.getAmount() <500000) {
//由董事长审批该采购单
this.handleByPresident(request);
}
else {
//由董事会审批该采购单
this.handleByCongress(request);
}
}

//主任审批采购单
public void handleByDirector(PurchaseRequest request) {
...
}

//副董事长审批采购单
public void handleByVicePresident(PurchaseRequest request) {
...
}

//董事长审批采购单
public void handleByPresident(PurchaseRequest request) {
...
}

//董事会审批采购单
public void handleByCongress(PurchaseRequest request) {
...
}
}

  上述方案似乎很完美,但仔细分析,发现其存在如下几个问题:

  • PurchaseRequestHandler 类较为庞大,各个级别的审批方法都集中在一个类中,违反了“单一职责原则”,测试和维护难度大;

  • 当需要增加一个新的审批级别或调整任何一级的审批金额和审批细节(例如将董事长的审批额度改为 60 万元)时,都必须修改源代码并进行严格测试;此外,如果需要移除某一级别(例如金额为 10 万元及以上的采购单直接由董事长审批,不再设副董事长一职)时也必须对源代码进行修改,违反了“开闭原则”;

  • 审批流程的设置 缺乏灵活性,现在的审批流程是“主任–>副董事长–>董事长–>董事会”,如果需要改为“主任–>董事长–>董事会”,在此方案中只能通过修改源代码来实现,客户端无法定制审批流程。


  那么,我们如何针对上述问题对系统进行改进呢?我们可以通过使用 责任链模式 提出一种 灵活的 可插拔式的 解决方案,它可以确保最大程度地解决这些问题。


二. CoR 模式概述

  很多情况下,在一个软件系统中可以处理某个请求的对象不止一个。例如上面提到的采购审批子系统,主任、副董事长、董事长和董事会都可以处理采购单,他们可以构成一条处理采购单的链式结构,采购单(可以看作是要处理的信息)沿着这条链进行传递,这条链就称为责任链。责任链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求,如下图所示。链上的每一个对象都是请求处理者,责任链模式可以将请求的处理者组织成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理。在此过程中,客户端实际上无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,从而实现请求发送者和请求处理者解耦。
  
          

  对责任链的理解,关键在于对链的理解,即包含如下两点:

  • 链是一系列节点的集合,在责任链中,节点实质上是指请求的处理者;

  • 链的各节点可灵活拆分再重组,在责任链中,实质上就是请求发送者与请求处理者的解耦。


三. CoR 模式定义与结构

  顾名思义,责任链模式(Chain of Responsibility Pattern) 指的是用一系列类(classes)去处理一个请求request,这些类之间是一个松散的耦合,它们之间的唯一联系就是在它们之间传递的 request。也就是说,如果来了一个请求,那么A类先处理;若A类处理不了,就传递到B类进行处理;如果B类也处理不了,就传递给C类进行处理,这些请求处理类像一个链条(chain)一样,请求在这条链上不断的传递下去,直至其被处理。


1、责任链模式的定义

定义: 使多个对象都有机会处理请求,从而避免请求的发送者与请求处理者耦合在一起。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

类型: 对象行为型模式

实质: 责任链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,从而实现请求发送者与请求处理者的解耦。


2、责任链模式的结构

             这里写图片描述

  我们可以从责任链模式的结构图中看到,具体的请求处理者可以有多个,并且所有的请求处理者均具有相同的接口(继承于同一抽象类)。 责任链模式主要包含如下两个角色:

  • Handler(抽象处理者):处理请求的接口,一般设计为具有抽象请求处理方法的抽象类,以便于不同的具体处理者进行继承,从而实现具体的请求处理方法。此外,由于每一个请求处理者的下家还是一个处理者,因此抽象处理者本身还包含了一个本身的引用( successor)作为其对下家的引用,以便将处理者链成一条链;

  • ConcreteHandler(具体处理者):抽象处理者的子类,可以处理用户请求,其实现了抽象处理者中定义的请求处理方法。在具体处理请求时需要进行判断,若其具有相应的处理权限,那么就处理它;否则,其将请求 转发 给后继者,以便让后面的处理者进行处理。

      在责任链模式里,由每一个请求处理者对象对其下家的引用而连接起来形成一条请求处理链。请求将在这条链上一直传递,直到链上的某一个请求处理者能够处理此请求。事实上,发出这个请求的客户端并不知道链上的哪一个请求处理者将处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。


三. CoR 模式的典型实现

   实现责任链模式的关键代码是: 在抽象类 Handler 里面聚合它自己(持有自身类型的引用),并在 handleRequest 方法里判断其是否能够处理请求。若当前处理者无法处理,则设置其后继者并向下传递,直至请求被处理。


1、对请求处理者的抽象

  责任链模式的核心在于对 请求处理者的抽象。在实现过程中,抽象处理者一般会被设定为 抽象类,其典型实现代码如下所示:

public abstract class Handler {

// protected :维持对下家的引用
protected Handler successor;

public void setSuccessor(Handler successor) {
this.successor=successor;
}

public abstract void handleRequest(String request);
}

  上述代码中,抽象处理者类定义了对下家的引用 (其一般用 protected 进行修饰),以便将请求转发给下家,从而形成一条请求处理链。同时,在抽象处理者类中还声明了抽象的请求处理方法,以便由子类进行具体实现。

  更多关于关键字protected的使用,请移步我的博文《Java 访问权限控制:你真的了解 protected 关键字吗?》。


2、对请求处理者的抽象

  具体处理者是抽象处理者的子类,具体处理者类的典型代码如下:

public class ConcreteHandler extends Handler {
public void handleRequest(String request) {
if (请求满足条件) {
//处理请求
}else {
this.successor.handleRequest(request); //转发请求
}
}
}

  
  在具体处理类中,通过对请求进行判断以便做出相应的处理,因此,其一般具有两大作用:

  • 处理请求,不同的具体处理者以不同的形式实现抽象请求处理方法 handleRequest();

  • 转发请求,若该请求超出了当前处理者类的权限,可以将该请求转发给下家;


3、责任链的创建

  需要注意的是,责任链模式并不创建职责链,职责链的创建工作必须由系统的其他部分来完成,一般由使用该责任链的客户端创建。职责链模式降低了请求的发送者和请求处理者之间的耦合,从而使得多个请求处理者都有机会处理这个请求。


四. COR模式应用实例

  为了让审批流程更加灵活并实现采购单的链式传递和处理,我们现在使用职责链模式来实现采购单的分级审批,其基本结构如图所示:

           这里写图片描述

  在上图中,抽象类 Approver 充当抽象处理者,而 Director、VicePresident、President 和 Congress 则充当具体处理者,PurchaseRequest 充当请求消息类。完整代码如下所示:


(1). 请求消息类

//请求消息类(采购单)
public class PurchaseRequest {
private double amount; //采购金额
private int number; //采购单编号
private String purpose; //采购目的

// 构造器
public PurchaseRequest(double amount, int number, String purpose) {
this.amount = amount;
this.number = number;
this.purpose = purpose;
}

// setter & getter
public void setAmount(double amount) {
this.amount = amount;
}

public double getAmount() {
return this.amount;
}

public void setNumber(int number) {
this.number = number;
}

public int getNumber() {
return this.number;
}

public void setPurpose(String purpose) {
this.purpose = purpose;
}

public String getPurpose() {
return this.purpose;
}
}

(2). 抽象处理者类 (抽象类)

// 抽象处理者(审批者类)
public abstract class Approver {

protected Approver successor; //定义后继处理对象

//设置后继者
public void setSuccessor(Approver successor) {
this.successor = successor;
}

//抽象请求处理方法
public abstract void processRequest(PurchaseRequest request);
}

(3). 具体处理者类 (抽象处理者的子类)

//主任类:具体处理者
public class Director extends Approver {

//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() <50000) {
System.out.println("主任" +"审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); // 处理请求
}else {
this.successor.processRequest(request); // 转发请求
}
}
}


//副董事长类:具体处理者
public class VicePresident extends Approver {

//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() <100000) {
System.out.println("副董事长" + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); // 处理请求
}else {
this.successor.processRequest(request); // 转发请求
}
}
}


//董事长类:具体处理者
public class President extends Approver {

//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() <500000) {
System.out.println("董事长" + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); // 处理请求
}else {
this.successor.processRequest(request); // 转发请求
}
}
}


//董事会类:具体处理者
public class Congress extends Approver {
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
System.out.println("召开董事会审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求
}
}

(4). 客户端测试代码

public class Client {
public static void main(String[] args) {
Approver director,vicePresident,president,congress;
director = new Director();
vicePresident = new VicePresident();
president = new President();
cOngress= new Congress();

//创建责任链
director.setSuccessor(vicePresident);
vicePresident.setSuccessor(president);
president.setSuccessor(congress);

//创建采购单,并从主任开始处理
PurchaseRequest pr1 = new PurchaseRequest(45000,10001,"购买倚天剑");
director.processRequest(pr1);

PurchaseRequest pr2 = new PurchaseRequest(60000,10002,"购买《葵花宝典》");
director.processRequest(pr2);

PurchaseRequest pr3 = new PurchaseRequest(160000,10003,"购买《金刚经》");
director.processRequest(pr3);

PurchaseRequest pr4 = new PurchaseRequest(800000,10004,"购买桃花岛");
director.processRequest(pr4);
}
}/* Output():
主任审批采购单:10001,金额:45000.0元,采购目的:购买倚天剑。
副董事长审批采购单:10002,金额:60000.0元,采购目的:购买《葵花宝典》。
董事长审批采购单:10003,金额:160000.0元,采购目的:购买《金刚经》。
召开董事会审批采购单:10004,金额:800000.0元,采购目的:购买桃花岛。
*/
//:~

五. CoR 模式的灵活性

1、CoR 模式的可扩展性

  我们在上一节中已经给出了使用责任链模式解决审批问题的完整解决方案。如果需要在系统增加一个新的具体处理者,比如增加一个经理(Manager)角色可以审批 5 万元至 8 万元(不包括 8 万元)的采购单,则只需要编写一个抽象处理者类 Approver 的新的子类 Manager,并实现在 Approver 类中定义的抽象处理方法,如果采购金额大于等于 8 万元,则将请求转发给下家,代码如下所示:

//经理类:具体处理者
class Manager extends Approver {
public Manager(String name) {
super(name);
}

//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() <80000) {
System.out.println("经理" + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求
}else {
this.successor.processRequest(request); //转发请求
}
}
}

  由于链的创建过程由客户端负责,因此增加新的具体处理者类不会对原有程序有任何影响,无须修改已有类的源代码,符合“开闭原则”。此外,在客户端代码中,若需要将新的具体请求处理者应用在系统中,则只需创建该具体处理者对象并将其加入责任链中即可,即只需增加如下代码:

Approver manger = new Manager();   // 在客户端创建新的请求处理者

//在客户端创建责任链,只需将经理角色在适当位置链入到责任链中即可
director.setSuccessor(manger); //将经理作为主任的下家
manger.setSuccessor(vicePresident); //将副董事长作为经理的下家
vicePresident.setSuccessor(president);
president.setSuccessor(congress);

2、CoR 模式的优点

  • 降低耦合度,使请求的发送者和接收者解耦,便于灵活的、可插拔的定义请求处理过程;

  • 简化、封装了请求的处理过程,并且这个过程对客户端而言是透明的,以便于动态地重新组织链以及分配责任,增强请求处理的灵活性;

     在上面的示例中,这两个优点主要体现为如下两点:第一,我们可以随时改变内部的请求处理规则,每个请求处理者都可以去动态地指定他的继任者。也就是说,主任完全可以跳过副董事长直接找到董事长进行审批。第二,我们可以从职责链任何一个节点开始,即如果主任不在,可以直接去找副董事长,责任链还会继续,不会有任何影响。

     事实上,如果我们不使用责任链,我们需要和公司中的每一个层级都发生耦合关系,反映在代码上就是我们需要在一个类中写上很多丑陋的 if….else 语句。如果我们使用了责任链,相当于我们面对的是一个黑箱子,我们只需要认识其中的一个部门,然后让黑箱内部去负责处理和传递就好了。


六. CoR 模式的类型

  严格地来说,职责链模式可分为纯的职责链模式和不纯的职责链模式两种。

1、纯的职责链模式

  一个纯的职责链模式要求一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,而不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。而且在纯的职责链模式中,要求一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况。我们前面提到的采购单审批示例应用的是纯的职责链模式。


2、不纯的职责链模式

  在一个不纯的职责链模式中,允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后其后继处理者继续处理该请求,而且一个请求可以最终不被任何处理者对象所接收。


  实际上,纯的责任链模式的实际例子中的应用很难找到,我们一般看到的例子均是不纯的责任链模式的实现,比如 Struts 中的拦截器(Interceptor), Java Web 中的 Filter 等都是责任链模式在Java中的具体应用实例。

  关于责任链模式模式在Java中的应用(AOP 理念, Java Web 中的Filter 和 Struts2中的 Interceptor)请见本篇的姊妹篇《 责任链模式进阶:与AOP思想的融合与应用》。


七. CoR 模式的总结

  责任链模式通过建立一条链来组织请求的处理者,请求将沿着链进行传递,而请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。在实际软件开发中,如果遇到有多个对象可以处理同一请求时可以考虑使用职责链模式,最常见的例子包括在 Java Web 应用开发中创建一个过滤器(Filter)链来对请求数据进行过滤(中文字符乱码的处理)、在工作流系统中实现公文的分级审批、在Struts应用中添加不同的拦截器(常用的有类型转化、异常处理,数据校验…)以增强Struts2的功能等。


八. 更多

  更多关于 protected 关键字 的介绍, 请移步我的博文《Java 访问权限控制:你真的了解 protected 关键字吗?》。

  更多关于 责任链模式模式在Java中的应用(特别是在Java Web 和 Struts中的应用), 请移步我的博文 《 责任链模式进阶:与AOP思想的融合与应用》。


  需要指出的是,本文关于责任链模式理论部分的介绍借鉴了极客学院的《设计模式之行为型模式》一文中的关于责任链模式的案例和应用实例。


引用

《JAVA与模式》之责任链模式
Java设计模式之责任链模式、职责链模式
职责链模式(Chain of Responsibility)的Java实现
Java模式之责任链模式
责任链模式
责任链模式
职责链模式


推荐阅读
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • JavaWeb中读取文件资源的路径问题及解决方法
    在JavaWeb开发中,读取文件资源的路径是一个常见的问题。本文介绍了使用绝对路径和相对路径两种方法来解决这个问题,并给出了相应的代码示例。同时,还讨论了使用绝对路径的优缺点,以及如何正确使用相对路径来读取文件。通过本文的学习,读者可以掌握在JavaWeb中正确找到和读取文件资源的方法。 ... [详细]
author-avatar
LST---诗ting
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有