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

от автора

Итак, допустим, мы хотим реализовать клиент-серверное приложение, где на стороне клиента будут формироваться нужные данные, а на стороне сервера будет производится расчет и возвращаться клиенту в виде результата. Если брать в расчет простой калькулятор (давайте сделаем его еще проще, 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 (отлавливается при делении на ноль на сервере).


ссылка на оригинал статьи https://habr.com/ru/post/477436/