Giới thiệu thư viện Apache Commons Chain

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh

1. Apache Commons Chain là gì?

Apache Commons Chain là một framework, một thư viện mã nguồn mở của Apache. Nó cung cấp API cho phép chúng ta dễ dàng cài đặt các xử lý tuân theo Chain of Responsibility (COR) pattern.

Chain of Responsiblity cho phép một đối tượng gửi một yêu cầu nhưng không biết đối tượng nào sẽ nhận và xử lý nó. Điều này được thực hiện bằng cách kết nối các đối tượng nhận yêu cầu thành một chuỗi (chain) và gửi yêu cầu theo chuỗi đó cho đến khi có một đối tượng xử lý nó.

Chain of Responsibility Pattern hoạt động như một danh sách liên kết (Linked list) với việc đệ quy duyệt qua các phần tử (recursive traversal).

2. Cài đặt thư viện Apache Commons Chain

Thêm thư viện vào project vào project maven:

<!-- https://mvnrepository.com/artifact/commons-chain/commons-chain -->
<dependency>
    <groupId>commons-chain</groupId>
    <artifactId>commons-chain</artifactId>
    <version>1.2</version>
</dependency>

3. Sử dụng thư viện Apache Commons Chain

Trong Chain framework, các đơn vị công việc trong một chuỗi các bước tuần tự được biểu diễn bằng các Command. Một chuỗi các lệnh (Command) tạo thành một chain. Chuỗi chính nó cũng là một Command có thể được thực thi. Chain được giữ trong danh mục (Catalog), từ đó chúng có thể được lấy tại thời điểm thực thi. Chain framework cung cấp các interface Chain, Catalog, Command, Fillter như sơ đồ lớp bên dưới.

  • Context : thể hiện trạng thái của ứng dụng, chứa dữ liệu được chia sẻ giữa các Command.
  • Command : thể hiện các đơn vị công việc. Command chỉ bao gồm một phương thức execute() với tham số là một Context và kiểu trả về là giá trị boolean. Kiểu trả về boolean: để xác định có nên tiếp tục xử lý hay không. Trả về true nếu đã hoàn thành, không cần Command kế tiếp xử lý, ngược lại trả về false.
  • Chain : nó extends từ Command. Chain có thể chứa một chuỗi tuần tự các Command/ Chain khác.
  • Filter : là một Command đặc biệt, có thêm phương thức postProcess(). Phương thức này được gọi sau khi phương thức execute() được gọi.
  • Catalog : các ứng dụng sử dụng Facade hoặc Factory hoặc DI để giảm sự phụ thuộc giữa các lớp. Các lớp có thể giao tiếp với nhau mà không cần thông qua tên lớp. Catalog cũng dựa trên ý tưởng đó, nó cho phép client có thể thực thi một Command thông qua định danh mà không cần biết tên lớp.

  Hướng dẫn sử dụng thư viện Jackson

  Cách sử dụng lệnh Xcopy trong CMD (Command Prompt)

3.1 Ví dụ sử dụng thư viện Apache Commons Chain

Tạo custom context để chia sẻ dữ liệu giữa các Command.

package com.gpcoder.context;

import org.apache.commons.chain.impl.ContextBase;

import lombok.Data;

@Data
public class MyContext extends ContextBase {

    private String property;
}

Tạo file constant đặt tên cho các command, chain. Chúng ta sẽ sử dụng tên này để lấy các Command từ Catalog và thực thi.

package com.gpcoder.constant;

public enum MyCommandNamed {
    CHAIN_1, CHAIN_2, CMD_1, CMD_2, CMD_3, CMD_4, EXCEPTION_HANDLER
}

Tạo file constant đặt tên cho các command, chain. Chúng ta sẽ sử dụng tên này để lấy các Command từ Catalog và thực thi.

package com.gpcoder.constant;

public enum MyCommandNamed {
    CHAIN_1, CHAIN_2, CMD_1, CMD_2, CMD_3, CMD_4, EXCEPTION_HANDLER
}

Tạo Command1, Command2: đây là command minh họa cho đơn vị công việc sẽ được thực hiện trong Chain.

package com.gpcoder.command;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

import com.gpcoder.context.MyContext;

public class Command1 implements Command {

    public boolean execute(Context ctx) throws Exception {
        System.out.println("This is command 1: ");

        String property = ((MyContext) ctx).getProperty();
        System.out.println("+ property: " + property);

        String customValue = ctx.get("custom-key").toString();
        System.out.println("+ customValue: " + customValue);

        return false;
    }
}
package com.gpcoder.command;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

import com.gpcoder.context.MyContext;

public class Command2 implements Command {

    public boolean execute(Context ctx) throws Exception {
        String value = ((MyContext) ctx).getProperty();
        System.out.println("This is command 2: " + value);
        return false;
    }
}

Tham khảo việc làm Java Fresher mới nhất trên TopDev!

3.1.1 Ví dụ 1 – Thực thi 1 Chain

Tạo chain có 2 command: Command1 và Command2.

package com.gpcoder.example1_chain;

import org.apache.commons.chain.impl.ChainBase;

import com.gpcoder.command.Command1;
import com.gpcoder.command.Command2;

public class MyChain extends ChainBase {

    public MyChain() {
        super();
        addCommand(new Command1());
        addCommand(new Command2());
    }
}

Thực thi chain:

package com.gpcoder.example1_chain;

import org.apache.commons.chain.Command;

import com.gpcoder.context.MyContext;

/**
* Execute a chain example
*/
public class ChainStart1 {

    public static void main(String[] args) throws Exception {
        // Create context
        MyContext context = new MyContext();
        context.setProperty("property-value");
        context.put("custom-key", "custom-value");

        // Get the command
        Command command = new MyChain();
        command.execute(context);
    }
}

Output chương trình:

This is command 1: 
+ property: property-value
+ customValue: custom-value
This is command 2: property-value

3.1.2 Ví dụ 2 – Thực thi một Command được lấy từ Catalog

Tạo Catalog chứa các command và Chain cần sử dụng:

package com.gpcoder.example2_catalog;

import org.apache.commons.chain.impl.CatalogBase;

import com.gpcoder.command.Command1;
import com.gpcoder.command.Command2;
import com.gpcoder.constant.MyCommandNamed;
import com.gpcoder.example1_chain.MyChain;

/**
* CatalogBase is a collection of Chains and Commands with their logical names.
*/
public class MyCatalog extends CatalogBase {

    public MyCatalog() {
        super();
        addCommand(MyCommandNamed.CMD_1.name(), new Command1());
        addCommand(MyCommandNamed.CMD_2.name(), new Command2());
        addCommand(MyCommandNamed.CHAIN_1.name(), new MyChain());
    }
}

Lấy Command từ Catalog và thực thi:

package com.gpcoder.example2_catalog;

import org.apache.commons.chain.Catalog;
import org.apache.commons.chain.Command;

import com.gpcoder.constant.MyCommandNamed;
import com.gpcoder.context.MyContext;

/**
* Execute the specific command example
*/
public class ChainStart2 {

    public static void main(String[] args) throws Exception {

        // Create context
        MyContext ctx = new MyContext();
        ctx.setProperty("property-value");
        ctx.put("custom-key", "custom-value");

        // Get the catalog
        Catalog catalog = new MyCatalog();

        // Get the command
        System.out.println("Execute the specific command: CMD_1");
        Command command1 = catalog.getCommand(MyCommandNamed.CMD_1.name());
        command1.execute(ctx);

        System.out.println("\nExecute the specific chain: CHAIN_1");
        Command chain1 = catalog.getCommand(MyCommandNamed.CHAIN_1.name());
        chain1.execute(ctx);
    }
}

Output chương trình:

Execute the specific command: CMD_1
This is command 1: 
+ property: property-value
+ customValue: custom-value

Execute the specific chain: CHAIN_1
This is command 1: 
+ property: property-value
+ customValue: custom-value
This is command 2: property-value

3.1.3 Ví dụ 3 – Sử dụng Filter để xử lý exception

Tạo Command3 có throw một Exception:

package com.gpcoder.command;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

public class Command3 implements Command {

    public boolean execute(Context ctx) throws Exception {
        throw new UnsupportedOperationException("Cannot execute Command4");
    }
}

Tạo Fitler để handle Exception:

package com.gpcoder.filter;

import org.apache.commons.chain.Context;
import org.apache.commons.chain.Filter;

public class CommandExceptionHandler implements Filter {

    public boolean execute(Context context) throws Exception {
        System.out.println("CommandExceptionHandler.execute() called.");
        return false;
    }

    public boolean postprocess(Context context, Exception exception) {
        if (exception == null) {
            return false;
        }
        System.out.println("Exception " + exception.getMessage() + " occurred.");
        return true;
    }
}

Tạo Chain với 2 Command: Command1 và Command3.

package com.gpcoder.example3_exception;

import org.apache.commons.chain.impl.ChainBase;

import com.gpcoder.command.Command1;
import com.gpcoder.command.Command3;
import com.gpcoder.filter.CommandExceptionHandler;

public class MyChain3 extends ChainBase {

    public MyChain3() {
        super();
        addCommand(new CommandExceptionHandler());
        addCommand(new Command1());
        addCommand(new Command3());
    }
}

Thực thi chain:

package com.gpcoder.example3_exception;

import org.apache.commons.chain.Command;

import com.gpcoder.context.MyContext;

/**
 * Filter for exception handling in Chains example
 */
public class ChainStart3 {

    public static void main(String[] args) throws Exception {
        Command chain = new MyChain3();
        chain.execute(new MyContext());
    }
}

Output của chương trình:

CommandExceptionHandler.execute() called.
This is command 1: 
+ property: null
Exception null occurred.

Lưu ý: Filter Command phải thêm vào đầu chain để có thể handle exception cho các Command được thực thi phía sau.

3.1.4 Ví dụ 4 – Tạo Catalog từ file xml

Trong ví dụ 2, chúng ta tạo một Catalog bằng cách extend từ một CatalogBase. Chúng ta có một cách khác để tạo Catalog là xử dụng cấu hình catalog từ xml file.

Để sử dụng tính năng này, cần thêm các thư viện sau:

<!-- Use for the command config -->
<!-- https://mvnrepository.com/artifact/commons-digester/commons-digester -->
<dependency>
    <groupId>commons-digester</groupId>
    <artifactId>commons-digester</artifactId>
    <version>1.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.3</version>
</dependency>

Để dễ minh họa các trường hợp sử dụng cấu hình xml file, chúng ta sẽ tạo thêm một Command:

package com.gpcoder.command;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

import com.gpcoder.context.MyContext;

public class Command4 implements Command {

    public boolean execute(Context ctx) throws Exception {
        String value = ((MyContext) ctx).getProperty();
        System.out.println("This is command 4: " + value);
        return false;
    }
}

Tạo file chain-config.xml trong thư mục /src/main/resources

<catalog>
    <!-- Single command "chains" from CatalogBaseTestCase -->
    <command name="CMD_1" className="com.gpcoder.command.Command1" />
    <command name="CMD_2" className="com.gpcoder.command.Command2" />

    <!-- Chains with nested commands -->
    <chain name="CHAIN_1">
        <command className="com.gpcoder.filter.CommandExceptionHandler" />
        <command className="com.gpcoder.command.Command1" />
        <command className="com.gpcoder.command.Command2" />
        <command className="com.gpcoder.command.Command3" />
        <!-- Lookup Command -->
        <command name="CHAIN_2" optional="true" className="org.apache.commons.chain.generic.LookupCommand" />
    </chain>

    <chain name="CHAIN_2">
        <command className="com.gpcoder.command.Command4" />
    </chain>
</catalog>

Tạo Catalog từ XML file:

package com.gpcoder.example4_xml_config;

import org.apache.commons.chain.Catalog;
import org.apache.commons.chain.config.ConfigParser;
import org.apache.commons.chain.impl.CatalogFactoryBase;

/**
 * Get catalog from the XML configuration file
 */
public class MyXMLCatalog {

    private static final String CONFIG_FILE = "/chain-config.xml";
    private ConfigParser parser;
    private Catalog catalog;

    public MyXMLCatalog() {
        parser = new ConfigParser();
    }

    public Catalog getCatalog() throws Exception {
        if (catalog == null) {
            parser.parse(this.getClass().getResource(CONFIG_FILE));
        }
        catalog = CatalogFactoryBase.getInstance().getCatalog();
        return catalog;
    }
}

Thực thi Command lấy từ Catalog trên:

package com.gpcoder.example4_xml_config;

import org.apache.commons.chain.Catalog;
import org.apache.commons.chain.Command;

import com.gpcoder.constant.MyCommandNamed;
import com.gpcoder.context.MyContext;

/**
 * Get the catalog from XML file and Execute the specific command example
 */
public class ChainStart4 {

    public static void main(String[] args) throws Exception {
        // Create context
        MyContext ctx = new MyContext();
        ctx.setProperty("property-value");
        ctx.put("custom-key", "custom-value");

        // Get the catalog
        Catalog catalog = new MyXMLCatalog().getCatalog();

        // Get the command
        System.out.println("Execute the specific command: CMD_1");
        Command command1 = catalog.getCommand(MyCommandNamed.CMD_1.name());
        command1.execute(ctx);

        System.out.println("\nExecute the specific chain: CHAIN_1");
        Command chain1 = catalog.getCommand(MyCommandNamed.CHAIN_1.name());
        chain1.execute(ctx);
    }
}

Output chương trình:

Execute the specific command: CMD_1
This is command 1: 
+ property: property-value
+ customValue: custom-value

Execute the specific chain: CHAIN_1
CommandExceptionHandler.execute() called.
This is command 1: 
+ property: property-value
+ customValue: custom-value
This is command 2: property-value
Exception Cannot execute Command4 occurred.
Bài viết gốc được đăng tải tại gpcoder.com
Xem thêm:
Tìm việc làm IT mọi cấp độ tại TopDev