Pull to refresh

Как написать простой калькулятор клиент-сервер (JavaFX+EJB+WildFly)

Reading time8 min
Views8.2K
Итак, допустим, мы хотим реализовать клиент-серверное приложение, где на стороне клиента будут формироваться нужные данные, а на стороне сервера будет производится расчет и возвращаться клиенту в виде результата. Если брать в расчет простой калькулятор (давайте сделаем его еще проще, 4 оператора, операнды без дробей и работа по схеме [операнд1] [оператор] [операнд2] [результат]) и, допустим, реализовать его на каком-нибудь ЯП (язык программирования), например, Java, с использование сервера приложения (допустим WildFly/JBoss)+клиента (можно взять на вооружение JavaFX), то можно это сделать следующим способом:

Этот же вариант можно решить с использованием RMI (Remote Method Invocation) без сервера, клиента GUI и EJB, в консоли, но этот вариант рассматривать не будем, а приступим к более интересной реализации.

1. Нам понадобятся следующие ингредиенты:
1.1. JDK,
1.2. IDE (с поддержкой Java EE),
1.3. WildFly (или другой сервер приложений для Java),
1.4. SceneBuilder (для удобства и быстрого создания GUI).

Для связи клиента с сервером будем использовать JNDI (служба именования и каталогов), используя EJB (фреймворк для построения бизнес-логики).

2. Создаем реализацию серверной части:
2.1. Удаленный интерфейс, посредством которого будет происходить связь между клиентов и сервером (используем аннотацию Remote — компонент EJB будет использовать RMI).

package com.calc.server;

import javax.ejb.Remote;

@Remote
public interface CalcRemote {
    int add(int a, int b);
    int sub(int a, int b);
    int mul(int a, int b);
    int div(int a, int b) throws MyException;
}

2.2. Класс, реализующий этот интерфейс (используем аннотацию @Stateless — сеансовый компонент без сохранения состояния).

package com.calc.server;

import javax.ejb.Stateless;

@Stateless(name = "CalcSessionEJB")
public class CalcSessionBean implements CalcRemote {
    public CalcSessionBean() {
    }

    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        return a - b;
    }

    @Override
    public int mul(int a, int b) {
        return a * b;
    }

    @Override
    public int div(int a, int b) throws MyException {
        try {
            return a / b;
        } catch (ArithmeticException ex) {
            throw new MyException("Divide by Zero!!!");
        }
    }
}

2.3. Добавим наш класс исключения, который будет сигнализировать деление на ноль или неверный формат.

package com.calc.server;

public class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}

2.4. С помощью IDE создаем ear-файл (Enterprise Archive), запускаем сервер (можно со стандартным портом), деплоим на него и если ошибок не было замечено, то с серверной частью закончено.

Лог сервера может быть такой:

	java:global/Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote
	java:app/ejb/CalcSessionEJB!com.calc.server.CalcRemote
	java:module/CalcSessionEJB!com.calc.server.CalcRemote
	java:jboss/exported/Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote
	ejb:Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote
	java:global/Calc_ear_exploded/ejb/CalcSessionEJB
	java:app/ejb/CalcSessionEJB
	java:module/CalcSessionEJB

3. Создаем реализацию клиентской части:
3.1. В SceneBuilder набрасываем следующий макет калькулятора (main.fxml), css пока накручивать не будем:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="321.0" prefWidth="231.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="com.calc.client.impl2.controller.Controller">
  <children>
    <Label disable="false" layoutX="24.0" layoutY="15.0" prefHeight="28.0" prefWidth="189.0" text="EJB Calculator" />
    <TextField fx:id="displayTextField" layoutX="24.0" layoutY="43.0" prefHeight="56.0" prefWidth="182.0" />
    <Button fx:id="num7Button" layoutX="24.0" layoutY="110.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="7" />
    <Button fx:id="num8Button" layoutX="72.0" layoutY="110.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="8" />
    <Button fx:id="num9Button" layoutX="119.0" layoutY="110.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="9" />
    <Button fx:id="divButton" layoutX="166.0" layoutY="110.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="/" />
    <Button fx:id="num4Button" layoutX="24.0" layoutY="161.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="4" />
    <Button fx:id="num5Button" layoutX="72.0" layoutY="161.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="5" />
    <Button fx:id="num6Button" layoutX="119.0" layoutY="161.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="6" />
    <Button fx:id="mulButton" layoutX="166.0" layoutY="161.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="*" />
    <Button fx:id="num1Button" layoutX="24.0" layoutY="209.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="1" />
    <Button fx:id="num2Button" layoutX="72.0" layoutY="209.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="2" />
    <Button fx:id="num3Button" layoutX="119.0" layoutY="209.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="3" />
    <Button fx:id="subButton" layoutX="166.0" layoutY="209.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="-" />
    <Button fx:id="num0Button" layoutX="24.0" layoutY="259.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="0" />
    <Button fx:id="clrButton" layoutX="72.0" layoutY="259.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="C" />
    <Button fx:id="resButton" layoutX="119.0" layoutY="259.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="=" />
    <Button fx:id="addButton" layoutX="166.0" layoutY="259.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="+" />
  </children>
</AnchorPane>

3.2. Подключаем контроллер класс к fxml-форме, для антиблокировки GUI (залипания формы) при ожидании данных с сервера на кнопку "=" добавим новую нитку:

Просмотр кода
package com.calc.client.impl2.controller;

import com.calc.server.CalcRemote;
import com.calc.server.MyException;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Properties;

public class Controller implements Runnable {
    private Calculator calculator;
    @FXML
    private TextField displayTextField;

    @FXML
    private Button num1Button;

    @FXML
    private Button num2Button;

    @FXML
    private Button num3Button;

    @FXML
    private Button num4Button;

    @FXML
    private Button num5Button;

    @FXML
    private Button num6Button;

    @FXML
    private Button num7Button;

    @FXML
    private Button num8Button;

    @FXML
    private Button num9Button;

    @FXML
    private Button num0Button;

    @FXML
    private Button addButton;

    @FXML
    private Button subButton;

    @FXML
    private Button mulButton;

    @FXML
    private Button divButton;

    @FXML
    private Button clrButton;

    @FXML
    private Button resButton;

    @FXML
    private void initialize() {
        System.out.println("initialize()");
        calculator = new Calculator();
        displayTextField.setText("0");

        num1Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("1")));
        num2Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("2")));
        num3Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("3")));
        num4Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("4")));
        num5Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("5")));
        num6Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("6")));
        num7Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("7")));
        num8Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("8")));
        num9Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("9")));
        num0Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("0")));
        addButton.setOnAction(event -> {
            calculator.addOperator("+");
            displayTextField.setText("");
        });
        subButton.setOnAction(event -> {
            calculator.addOperator("-");
            displayTextField.setText("");
        });
        mulButton.setOnAction(event -> {
            calculator.addOperator("*");
            displayTextField.setText("");
        });
        divButton.setOnAction(event -> {
            calculator.addOperator("/");
            displayTextField.setText("");
        });
        resButton.setOnAction(event -> new Thread(this).start());
        clrButton.setOnAction(event -> displayTextField.setText(""));
    }

    private void doRequest(String[] data) throws NamingException, MyException {
        Properties props = new Properties();
        props.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
//        props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
//        props.put(Context.PROVIDER_URL, "remote+http://localhost:8080");
        props.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
//        props.put("jboss.naming.client.ejb.context", "true");
//        props.put(Context.SECURITY_PRINCIPAL, "admin");
//        props.put(Context.SECURITY_CREDENTIALS, "123");
        Context ctx = new InitialContext(props);
        //System.out.println(ctx.getEnvironment());

        CalcRemote calcRemote = (CalcRemote) ctx.lookup("Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote");    //java:jboss/exported/Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote
        String res = Integer.toString(process(calcRemote, data));
        displayTextField.setText(res);
        System.out.println(res);
    }

    private int process(CalcRemote calcRemote, String[] resData) throws MyException {
        int operand1 = Integer.parseInt(resData[0]);
        int operand2 = Integer.parseInt(resData[1]);
        String operator = resData[2];
        switch (operator) {
            case "+":
                return calcRemote.add(operand1, operand2);
            case "-":
                return calcRemote.sub(operand1, operand2);
            case "*":
                return calcRemote.mul(operand1, operand2);
            case "/":
                return calcRemote.div(operand1, operand2);
        }
        return 0;
    }

    @Override
    public void run() {
        displayTextField.setText("WAITING...");
        try {
            doRequest(calculator.getResult());
        } catch (NamingException ex) {
            ex.printStackTrace();
        } catch (MyException ex) {
            displayTextField.setText(ex.getMessage());
        }
    }
}



3.3. Добавим логику работы простейшего калькулятора:

package com.calc.client.impl2.controller;

import com.calc.server.MyException;

public class Calculator {
    private String buffer = "", operator, operand1, operand2;
    private boolean isOperator = false;

    public String addNumber(String value) {
        buffer += value;
        if (!isOperator) {
            operand1 = buffer;
        } else {
            operand2 = buffer;
        }
        return buffer;
    }

    public void addOperator(String value) {
        operator = value;
        buffer = "";
        isOperator = true;
    }

    public String[] getResult() throws MyException {
        isOperator = false;
        buffer = "";
        int check;
        try {
            check = Integer.parseInt(operand1);
            check = Integer.parseInt(operand2);
        } catch (NumberFormatException ex) {
            throw new MyException("Wrong format!!!");
        }
        return new String[]{operand1, operand2, operator};
    }
}

3.5. Добавляем клиентскую библиотеку (в случае с WildFly это jboss-client.jar), запускаем GUI.

Как видно из приложенных кодов, пользователь набирает в буфер для операндов цифры, выбирает оператора, при нажатии кнопки "=" происходит соединение с сервером (через службу JNDI), надпись на дисплее «WAITING...» будет свидетельствовать ожидание ответа с сервера.

Также отметим пару исключений: NumberFormatException (отлавливается при вводе данных пользователем на клиенте) и ArithmeticException (отлавливается при делении на ноль на сервере).
Tags:
Hubs:
Total votes 16: ↑13 and ↓3+10
Comments11

Articles