Każda aplikacja warta lub nie warta uwagi ale posiadająca aspiracje ma dziś własny instalator. W dzisiejszym odcinku zajmiemy się stworzenie narzędzia do instalacji naszego projektu. Będzie to plik wykonywalny typu EXE. Jego zadaniem będzie zebraniem informacji od użytkownika o przeczytaniu licencji (zaznaczeniu odpowiedniego kwadracika), zadecydowanie o utworzeniu ikony na pulpicie, wybranie miejsca na dysku gdzie ma zostać zainstalowana aplikacja (plik jar), oraz na samym końcu uruchomienie naszej aplikacji.
Do tej prezentacji wybrałem bardzo prostą opcję czyli zagnieździłem plik .jar w pliku .exe, wiem że dziś mamy dobrodziejstwo internetu i możemy sobie spokojnie pobrać zasoby. Sypię głowę popiołem ale naszym celem jest uruchomienie aplikacji a nie sprzeczanie się nad tym co można 😉
Instalator jest bardzo prosty i to jest jego celem. Jeśli ktoś ma chęć rozszerzyć projekt o nowe możliwości to proszę bardzo, będzie mi miło że dałem komuś natchnienie.

Zapraszam do lektury i komentarzy!

Zacznijmy od wyboru technologii

L.P.CoWięcej
1repo*http://git.e-strix.com/sample_installer.git/
2Java1.8.0
3Maven3.3.9

*) W konfiguracji projektu (plik pom.xml) jest oznaczony svn, natomiast aktualnie repo znajduje się na prywatnym serwerze git. Powodem jest fakt że nie opłaca stawiać mi się stawiać svn’a dla jednego projektu. W załączonym filmie będę pracował na SVN i myślę że to wystarczy. Jeśli jesteś zainteresowany konfiguracją SVN zapraszam tutaj

1. Zaczynajmy: Struktura projektu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
│   LICENSE.md<br>
│   pom.xml<br>
│   README.md<br>
│<br>
└───src<br>
  └───main<br>
    ├───java<br>
    │  └───pl<br>
    │    └───estrix<br>
    │      └───javafx<br>
    │        AppContext.java<br>
    │        FilterProp.java<br>
    │        InstallerService.java<br>
    │        JavaApplication.java<br>
    │        MainController.java<br>
    │        Step1Controller.java<br>
    │        Step2Controller.java<br>
    │        Step3Controller.java<br>
    │<br>
    └───resources<br>
      │    main.fxml<br>
      │    step_1.fxml<br>
      │    step_2.fxml<br>
      │    step_3.fxml<br>
      │<br>
      ├───content<br>
      │    application.ico<br>
      │    config.bat<br>
      │    JavaFxGitSample_5.0.0.jar<br>
      │    sudo.cmd<br>
      │<br>
      ├───img<br>
      │    application.png<br>
      │    logo.png<br>
      │<br>
      └───META-INF<br>
           spring.factories<br>

Nie będę omawiał każdego pliku, nie ma na to czasu, zwrócę uwagę tylko na te, które wnoszą coś ciekawego. Są to dość proste rzeczy, jeśli masz wątpliwości to proszę wróć do podstaw programowania w java i javaFx. Jest wiele dobrych materiałów na YouTube o tym temacie, również w języku polskim. Mój ulubiony to kanał „Zacznij Programować”, polecam do zapoznania się 🙂

Cały projekt można pobrać z odnośnika repozytorium.

Plik: pom.xml

4.0.0
pl.estrix
estrix-jar-launcher
1.0-SNAPSHOT jar launcher
http://maven.apache.org

windows-deploy


Windows


com.akathist.maven.plugins.launch4j
launch4j-maven-plugin
1.7.22


l4j-clui package
launch4j


gui target/JavaFxGitSample-win-install.exe
target/estrix-jar-launcher-1.0-SNAPSHOT.jar

org.springframework.boot.loader.JarLauncher anything

src/main/resources/content/application.ico

1.8.0
preferJre


1.0.0.0
${project.version}
${project.name}
2018 e-Strix.com 1.0.0.0 1.0.0.0 ${project.name} e-Strix Kamil Mucik
e-Strix Kamil Mucik
JavaFxGitSample-win-install.exe



org.springframework.boot
spring-boot-starter-parent
2.0.1.RELEASE


scm:svn:http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk
scm:svn:http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk
http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk



false
corp1
Corporate Repository
file://C:\temp

default

UTF-8 UTF-8 1.8
pl.estrix.javafx.JavaApplication



org.springframework.boot
spring-boot-starter


org.apache.commons
commons-io
1.3.2

org.springframework.boot
spring-boot-maven-plugin

true
org.apache.maven.plugins
maven-antrun-plugin

generate-resources
run














Plik: src\main\java\pl\estrix\javafx\AppContext.java

1
package pl.estrix.javafx;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.stage.Stage;
import org.springframework.context.ConfigurableApplicationContext;

class AppContext {

/**
* Path where app will be installed.
*/
static String targetPath = „C:\\Users\\TEST\\AppData\\Local\\”;

/**
* No comment.
*/
static Boolean createShortcut = false;

static ConfigurableApplicationContext springContext;

static Boolean finish = Boolean.FALSE;

static Stage primaryStage;

static BooleanProperty licenseAgreement = new SimpleBooleanProperty(Boolean.FALSE);
static BooleanProperty buttonNextProperty = new SimpleBooleanProperty(Boolean.FALSE);
static BooleanProperty buttonPrevProperty = new SimpleBooleanProperty(Boolean.FALSE);

1
}

Plik: src\main\java\pl\estrix\javafx\FilterProp.java

1
package pl.estrix.javafx;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(„filter.properties”)
class FilterProp {

@Value(„${estrix.application.name}”)
private String name ;
@Value(„${estrix.application.version}”)
private String version ;
@Value(„${estrix.application.biuld-time}”)
private String biuldTime ;

String getName() {
return name;
}

String getVersion() {
return version;
}

String getBiuldTime() {
return biuldTime;
}

1
}

Plik: src\main\java\pl\estrix\javafx\InstallerService.java

1
package pl.estrix.javafx;

import org.apache.commons.io.FileUtils;

import java.io.*;
import java.net.URL;

class InstallerService {

static void createFolder(String path) {
try {
path += „_SampleApp/”;
boolean success = (new File(path)).mkdirs();

if (success) {
AppContext.targetPath = path;
}
} catch (Exception e){
e.printStackTrace();
}
}

static void createSettings(){
try {
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(AppContext.targetPath + „settings.bat”, true)));
out.println(„set app_path=”+AppContext.targetPath);
out.println(„set app_desktop_shortcut=”+AppContext.createShortcut);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}

static void copyResource(String file){
URL inputUrl = InstallerService.class.getResource(„/content/” + file);
File dest = new File(AppContext.targetPath + „/” + file);
try {
FileUtils.copyURLToFile(inputUrl, dest);
} catch (IOException e) {
e.printStackTrace();
}
}

static void executeBatch(){
try {
Runtime.getRuntime().exec(„cmd /c config.bat”, null, new File(AppContext.targetPath));
} catch (IOException e) {
e.printStackTrace();
}
}

1
2
3
4
5
6
7
8
    static void launchApp(){<br>
        try {<br>
            Runtime.getRuntime().exec("javaw.exe -jar JavaFxGitSample_5.0.0.jar", null, new File(AppContext.targetPath));<br>
        } catch (IOException  e) {<br>
            e.printStackTrace();<br>
        }<br>
    }<br>
}<br>

Plik: src\main\java\pl\estrix\javafx\JavaApplication.java

1
package pl.estrix.javafx;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.*;

@SpringBootApplication
@ComponentScan(basePackages = „pl.estrix.javafx”)
@PropertySource(„classpath:filter.properties”)
@Configuration
public class JavaApplication extends Application {

private ConfigurableApplicationContext context;
private Parent rootNode;

public static void main(final String[] args) {
Application.launch(args);
}

@Override
public void init() throws Exception {
SpringApplicationBuilder builder = new SpringApplicationBuilder(JavaApplication.class);
context = builder.run(getParameters().getRaw().toArray(new String[0]));

AppContext.springContext = context;
FXMLLoader loader = new FXMLLoader(getClass().getResource(„/main.fxml”));
loader.setControllerFactory(AppContext.springContext::getBean);
rootNode = loader.load();
}

@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(rootNode, 580, 420));
primaryStage.centerOnScreen();
primaryStage.show();

Image icon = new Image(JavaApplication.class.getResourceAsStream(„/img/application.png”));
primaryStage.getIcons().add(icon);

AppContext.primaryStage = primaryStage;
}

@Override
public void stop() throws Exception {
AppContext.springContext.close();
Platform.exit();
}

1
}<br>

Plik: src\main\java\pl\estrix\javafx\MainController.java

1
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import java.io.IOException;

@Controller
public class MainController {

@Autowired
private FilterProp filterProp;
@Autowired
private JavaApplication javaApplication;

@FXML
private BorderPane borderPane;
@FXML
private Button nextButton;
@FXML
private Button prevButton;
@FXML
private Label infoBuild;
@FXML
private Label infoName;
@FXML
private Label infoVersion;

@FXML
public void initialize() {
nextButton.disableProperty().bind(AppContext.buttonNextProperty.not());
prevButton.disableProperty().bind(AppContext.buttonPrevProperty.not());

infoBuild.setText(filterProp.getBiuldTime());
infoName.setText(filterProp.getName());
infoVersion.setText(filterProp.getVersion());

step1();
}

private void setCenter(String fxmlPath) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlPath));
loader.setControllerFactory(AppContext.springContext::getBean);
AnchorPane node = loader.load();

borderPane.setCenter( node);
} catch (IOException e) {
e.printStackTrace();
}

}

private void step1() {setCenter(„/step_1.fxml”); }

private void step2() {setCenter(„/step_2.fxml”); }

void step3() {setCenter(„/step_3.fxml”); }

public void onPrevAction() {
step1();
}

public void onNextAction() {
step2();
}

1
2
3
4
5
6
7
8
    void onCloseAction() {<br>
        try {<br>
            javaApplication.stop();<br>
        } catch (Exception e) {<br>
            e.printStackTrace();<br>
        }<br>
    }<br>
}<br>

Plik: src\main\java\pl\estrix\javafx\Step1Controller.java

1
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.scene.control.RadioButton;
import org.springframework.stereotype.Controller;

@Controller
public class Step1Controller {

@FXML
private RadioButton agreementCheckYes;

@FXML
public void initialize() {
agreementCheckYes.selectedProperty().addListener( (object, oldValue, newValue) ->{
AppContext.licenseAgreement.setValue(newValue);
AppContext.buttonNextProperty.setValue(newValue);
});

1
2
3
4
        AppContext.buttonPrevProperty.setValue(Boolean.FALSE);<br>
        agreementCheckYes.selectedProperty().setValue(AppContext.licenseAgreement.getValue());<br>
    }<br>
}

Plik: src\main\java\pl\estrix\javafx\Step2Controller.java

1
package pl.estrix.javafx;

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import java.io.File;

@Controller
public class Step2Controller {

@Autowired
private MainController mainController;

@FXML
private TextField textField2;
@FXML
private ProgressBar progressBar;
@FXML
private CheckBox createShortcut;
@FXML
private Button installButton;
@FXML
private Button changeDirButton;

@FXML
public void initialize() {
AppContext.buttonPrevProperty.setValue(Boolean.TRUE);
AppContext.buttonNextProperty.setValue(Boolean.FALSE);

textField2.setText(AppContext.targetPath);

createShortcut.selectedProperty().addListener( ((observable, oldValue, newValue) -> {
AppContext.createShortcut = newValue;
}));

Platform.runLater( () -> {
installButton.requestFocus();
});
}

public void onPathChangeAction() {
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle(„Wybierz folder aplikacji”);
File defaultDirectory = new File(AppContext.targetPath);
chooser.setInitialDirectory(defaultDirectory);
File selectedDirectory = chooser.showDialog(AppContext.primaryStage);
if (selectedDirectory != null) {
AppContext.targetPath = selectedDirectory.getAbsolutePath();
} else {
AppContext.targetPath = textField2.getPromptText();
}
textField2.setText(AppContext.targetPath);
}

public void onInstallAction() {
Task copyWorker = createWorker();
progressBar.progressProperty().unbind();
progressBar.progressProperty().bind(copyWorker.progressProperty());
installButton.disableProperty().setValue(Boolean.TRUE);
changeDirButton.disableProperty().setValue(Boolean.TRUE);
createShortcut.disableProperty().setValue(Boolean.TRUE);

copyWorker.messageProperty().addListener((observable, oldValue, newValue) ->
System.out.println(newValue)
);
copyWorker.stateProperty().addListener((observable, oldValue, newValue) -> {
if (Worker.State.SUCCEEDED.equals(newValue)){
AppContext.finish = true;
AppContext.buttonNextProperty.setValue(Boolean.TRUE);
AppContext.buttonPrevProperty.setValue(Boolean.FALSE);
mainController.step3();
}
});

new Thread(copyWorker).start();
}

private Task createWorker() {
return new Task() {
@Override
protected Object call() throws Exception {
updateProgress(1,10);
InstallerService.createFolder(AppContext.targetPath);
updateProgress(2,10);
InstallerService.createSettings();
updateProgress(3,10);
InstallerService.copyResource(„sudo.cmd”);
updateProgress(4,10);
InstallerService.copyResource(„JavaFxGitSample_5.0.0.jar”);
updateProgress(5,10);
InstallerService.copyResource(„application.ico”);
updateProgress(6,10);
InstallerService.copyResource(„config.bat”);
updateProgress(7,10);
InstallerService.executeBatch();

updateProgress(10,10);
return true;
}
};
}

1
}<br>

Plik: src\main\java\pl\estrix\javafx\Step3Controller.java

1
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class Step3Controller {

@Autowired
private MainController mainController;

@FXML
private CheckBox launchApp;

@FXML
public void initialize() {
AppContext.buttonPrevProperty.setValue(Boolean.FALSE);
AppContext.buttonNextProperty.setValue(Boolean.FALSE);
}

public void onExitAppAction() {
if (launchApp.isSelected()) {
InstallerService.launchApp();
}

mainController.onCloseAction();
}

1
}

Plik: src\main\resources\main.fxml

Plik: src\main\resources\step_1.fxml



<br /> <RadioButton layoutX=”14.0″ layoutY=”281.0″ mnemonicParsing=”false” selected=”true” text=”Nie zgadzam się” AnchorPane.bottomAnchor=”30.0″ AnchorPane.leftAnchor=”8.0″><br /> <toggleGroup><br /> <ToggleGroup fx:id=”accept” /><br /> </toggleGroup><br /> </RadioButton><br /> <RadioButton fx:id=”agreementCheckYes” layoutX=”14.0″ layoutY=”309.0″ mnemonicParsing=”false” text=”Zgadzam się” toggleGroup=”$accept” AnchorPane.bottomAnchor=”10.0″ AnchorPane.leftAnchor=”8.0″ /><br /> </children><br /> </AnchorPane></code></p> <p>Plik: <strong>src\main\resources\step_2.fxml</strong><br /> <code lang=”java”><?xml version=”1.0″ encoding=”UTF-8″?></p> <p><?import javafx.scene.control.Button?><br /> <?import javafx.scene.control.CheckBox?><br /> <?import javafx.scene.control.Label?><br /> <?import javafx.scene.control.ProgressBar?><br /> <?import javafx.scene.control.Separator?><br /> <?import javafx.scene.control.TextField?><br /> <?import javafx.scene.layout.AnchorPane?></p> <p><AnchorPane maxHeight=”-Infinity” maxWidth=”-Infinity” minHeight=”-Infinity” minWidth=”-Infinity” prefHeight=”340.0″ prefWidth=”420.0″ style=”-fx-background-color: #f0f0f0;” xmlns=”http://javafx.com/javafx/8.0.121″ xmlns:fx=”http://javafx.com/fxml/1″ fx:controller=”pl.estrix.javafx.Step2Controller”><br /> <children><br /> <Label layoutX=”43.0″ layoutY=”39.0″ text=”step 2″ AnchorPane.leftAnchor=”8.0″ AnchorPane.topAnchor=”24.0″ /><br /> <TextField fx:id=”textField2″ disable=”true” editable=”false” layoutX=”43.0″ layoutY=”70.0″ prefHeight=”25.0″ prefWidth=”284.0″ promptText=”C:\Program Files\JavaFxSample” AnchorPane.leftAnchor=”8.0″ AnchorPane.rightAnchor=”64.0″ AnchorPane.topAnchor=”48.0″ /><br /> <ProgressBar fx:id=”progressBar” layoutX=”8.0″ layoutY=”300.0″ prefHeight=”26.0″ prefWidth=”200.0″ progress=”0.0″ AnchorPane.bottomAnchor=”22.0″ AnchorPane.leftAnchor=”8.0″ AnchorPane.rightAnchor=”64.0″ /><br /> <Separator layoutY=”273.0″ prefWidth=”200.0″ AnchorPane.leftAnchor=”0.0″ AnchorPane.rightAnchor=”0.0″ /><br /> <Button fx:id=”installButton” layoutX=”344.0″ layoutY=”292.0″ mnemonicParsing=”false” onAction=”#onInstallAction” text=”Instaluj” AnchorPane.rightAnchor=”8.0″ /><br /> <CheckBox fx:id=”createShortcut” layoutX=”40.0″ layoutY=”114.0″ mnemonicParsing=”false” text=”Utwórz ikonę na pulpicie” AnchorPane.leftAnchor=”8.0″ AnchorPane.topAnchor=”84.0″ /><br /> <Button fx:id=”changeDirButton” layoutX=”345.0″ layoutY=”70.0″ mnemonicParsing=”false” onAction=”#onPathChangeAction” text=”Zmień” AnchorPane.rightAnchor=”8.0″ AnchorPane.topAnchor=”48.0″ /><br /> </children><br /> </AnchorPane></code></p> <p>Plik: <strong>src\main\resources\step_3.fxml</strong><br /> <code lang=”java”><?xml version=”1.0″ encoding=”UTF-8″?></p> <p><?import javafx.scene.control.Button?><br /> <?import javafx.scene.control.CheckBox?><br /> <?import javafx.scene.control.Label?><br /> <?import javafx.scene.layout.AnchorPane?></p> <p><AnchorPane maxHeight=”-Infinity” maxWidth=”-Infinity” minHeight=”-Infinity” minWidth=”-Infinity” prefHeight=”340.0″ prefWidth=”420.0″ style=”-fx-background-color: #f0f0f0;” xmlns=”http://javafx.com/javafx/8.0.121″ xmlns:fx=”http://javafx.com/fxml/1″ fx:controller=”pl.estrix.javafx.Step3Controller”><br /> <children><br /> <Label layoutX=”61.0″ layoutY=”35.0″ text=”Podsumowanie” AnchorPane.leftAnchor=”24.0″ AnchorPane.topAnchor=”24.0″ /><br /> <Button layoutX=”120.0″ layoutY=”155.0″ mnemonicParsing=”false” onAction=”#onExitAppAction” prefHeight=”30.0″ prefWidth=”180.0″ text=”Zakończ” /><br /> <CheckBox fx:id=”launchApp” layoutX=”44.0″ layoutY=”170.0″ mnemonicParsing=”false” text=”Uruchom aplikacje” AnchorPane.leftAnchor=”24.0″ AnchorPane.topAnchor=”64.0″ /><br /> </children><br /> </AnchorPane></code></p> <p>Plik: <strong>src\main\resources\content\config.bat</strong><br /> <code lang=”java”>@echo off</p> <p>:configloader<br /> call settings.bat<br /> :configloaderends</p> <p>set mypath=%cd%<br /> set binPath=%mypath%\bin<br /> if not exist „%binPath%” (<br /> mkdir %binPath%<br /> )</p> <p>rem :::::::: CREATE Menu Start Icon<br /> echo Set oWS = WScript.CreateObject(„WScript.Shell”) > %mypath%\CreateShortcut.vbs<br /> echo sLinkFile = „%mypath%\bin\JavaFxGitSample.lnk” >> %mypath%\CreateShortcut.vbs<br /> echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %mypath%\CreateShortcut.vbs<br /> echo oLink.TargetPath = „%mypath%\JavaFxGitSample_5.0.0.jar” >> %mypath%\CreateShortcut.vbs<br /> echo oLink.WorkingDirectory = „%mypath%\” >> %mypath%\CreateShortcut.vbs<br /> echo oLink.Description = „JavaFxGitSample” >> %mypath%\CreateShortcut.vbs<br /> echo oLink.IconLocation = „%mypath%\application.ico” >> %mypath%\CreateShortcut.vbs<br /> echo oLink.Save >> CreateShortcut.vbs<br /> cscript CreateShortcut.vbs<br /> del CreateShortcut.vbs</p> <p>if „%app_desktop_shortcut%” == „true” (<br /> copy %mypath%\bin\JavaFxGitSample.lnk %userprofile%\Desktop\JavaFxGitSample.lnk<br /> )</p> <p>sudo.cmd xcopy %mypath%\bin %ProgramData%\Microsoft\Windows\””Start Menu””\Programs\e-Strix\</code></p> <p>Plik: <strong>src\main\resources\content\sudo.cmd</strong><br /> <code lang=”java”>@echo Set objShell = CreateObject(„Shell.Application”) > %temp%\sudo.tmp.vbs<br /> @echo args = Right(„%*”, (Len(„%*”) – Len(„%1”))) >> %temp%\sudo.tmp.vbs<br /> @echo objShell.ShellExecute „%1”, args, „”, „runas” >> %temp%\sudo.tmp.vbs<br /> @cscript %temp%\sudo.tmp.vbs </code></p> <p>Plik: <strong>src\main\resources\META-INF\spring.factories</strong><br /> <code lang=”java”>org.springframework.boot.autoconfigure.EnableAutoConfiguration=\<br /> pl.estrix.javafx.JavaApplication</code></p>