9. [TD]:使用控制台程序实现 [ui] 层
关键词:多层架构、Spring、依赖注入。
![]() |
9.1. 支持
![]() |
在 [1] 中,[support / chap-09] 文件夹包含控制台应用程序 [UI] 层的 Eclipse 项目。
9.2. Maven 配置
Eclipse 项目 [elections-ui-metier-dao-jdbc] 由以下 Maven [pom.xml] 文件进行配置:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-ui-metier-dao-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-ui-metier-dao-jdbc</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- business -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>config.AppConfig</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- to install the project artifact in the local Maven repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- 第 22–26 行:我们导入了 [business] 层归档,并由此延伸导入了 [DAO] 层归档;
9.3. Spring 配置
![]() |
[UiConfig] 类用于配置 Spring 应用程序:
package elections.ui.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import elections.metier.config.MetierConfig;
@Import(MetierConfig.class)
@ComponentScan(basePackages = { "elections.ui.service" })
public class UiConfig {
}
- 第 8 行:我们导入了 [business] 层中定义的 Bean。该层已经导入了 [DAO] 层中定义的 Bean。因此,我们在此处可以访问来自这三个层的 Bean;
- 第 9 行:[elections.ui.service] 包包含其他 Bean;
9.4. [UI] 层的接口
![]() |
要了解 [UI] 层的 Java 接口可能是什么样子的,我们需要知道谁将使用这个接口。它不是上图中的用户,而是将启动整个应用程序的程序。
![]() |
[IElectionsUI] 接口将提供给主程序 [main],由其启动应用程序。[main] 可以向 [UI] 层提出什么要求?它可以要求其与用户建立交互,以便用户输入缺失的选举数据。我们将采用以下最简接口:
package istia.st.elections.ui;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
- 第 7 行:该接口仅有一个方法:run。通过调用此方法,我们指示 [ui] 层开始与用户交互。
9.5. 应用程序的入口点
![]() |
应用程序的启动类是一个带有静态 [main] 方法的 Java 类。该方法必须创建 [ui、business、demo] 层的实例,并指示 [ui] 层开始与用户交互。该类可以是以下 [AbstractBootElections] 类:
package elections.ui.boot;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import elections.dao.entities.ElectionsException;
import elections.ui.config.UiConfig;
import elections.ui.service.IElectionsUI;
public abstract class AbstractBootElections {
// spring context retrieval
protected AnnotationConfigApplicationContext ctx;
public void run() {
// instantiation layer [ui]
IElectionsUI electionsUI = null;
try {
// spring context retrieval
ctx = new AnnotationConfigApplicationContext(UiConfig.class);
// ui] layer recovery
electionsUI = getUI();
} catch (RuntimeException ex) {
// we report the error
afficheExceptions("Les erreurs suivantes se sont produites :", ex);
// stop the application
System.exit(1);
}
// execution layer [ui]
try {
electionsUI.run();
} catch (ElectionsException ex2) {
// we report the error
afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
// stop the application
System.exit(3);
} catch (RuntimeException ex1) {
// we report the error
afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
// stop the application
System.exit(2);
}
}
protected abstract IElectionsUI getUI();
private void afficheExceptions(String message, ElectionsException ex) {
// the message
System.out.println(String.format("%s -------------", message));
System.out.println(String.format("Code erreur : %d", ex.getCode()));
// errors are displayed
for (String erreur : ex.getErreurs()) {
System.out.println(String.format("-- %s", erreur));
}
}
public void afficheExceptions(String message, Exception ex) {
// the message
System.out.println(String.format("%s -------------", message));
// display the exception stack
Throwable cause = ex;
while (cause != null) {
System.out.println(String.format("-- %s", cause.getMessage()));
cause = cause.getCause();
}
}
}
- 第 19 行:实例化 Spring 上下文:将创建各种配置文件中定义的所有 Bean;
- 第 21 行:我们获取一个实现 [IElectionsUI] 接口的 Bean 的引用。我们将使用两个 Bean 来实现 [IElectionsUI] 接口:
- [ElectionsConsole] 用于控制台应用程序;
- [ElectionsSwing] 用于 Swing 应用程序;
[getUI] 方法是抽象的(第 44 行)。实际上,[AbstractBootElections] 类将是两个类的父类:
- [BootElectionsConsole],它将向其父类提供 [ElectionsConsole] Bean;
- [BootElectionsSwing],它将向其父类提供 [ElectionsSwing] Bean;
- 第 30 行:执行接口的 [run] 方法;
异常在以下位置被处理:
- 第 22–27 行:在 Spring 上下文实例化过程中可能发生异常;
- 第 31–41 行:在 [UI] 层执行过程中可能发生异常。异常分为两类:
- 由 [DAO] 层抛出的 [ElectionsException] 类;
- 用于处理执行过程中可能出现的其他异常的 [RuntimeException] 类;
[BootElectionsConsole] 类是启动控制台实现的类。其代码如下:
package elections.ui.boot;
import elections.ui.service.IElectionsUI;
public class BootElectionsConsole extends AbstractBootElections{
public static void main(String[] arguments) {
new BootElectionsConsole().run();
}
@Override
protected IElectionsUI getUI() {
return ctx.getBean("electionsConsole",IElectionsUI.class);
}
}
- 第 5 行:[BootElectionsConsole] 类继承自 [AbstractBootElections] 类。因此,它必须实现父类声明为抽象的 [getUI] 方法;
- 第 10–13 行:实现 [getUI] 方法;
- 第 12 行:名为 [electionsConsole] 的 Bean 被设计为实现 [IElectionsUI] 接口。[ctx] 是父类中通过以下声明定义的 Spring 上下文:
// spring context retrieval
protected AnnotationConfigApplicationContext ctx;
由于该字段带有 [protected] 属性,因此它在子类中可见。
第 12 行中的 Bean 将声明如下:
@Component
public class ElectionsConsole implements IElectionsUI {
此 Bean 的默认名称是类名的首字母小写。您可以通过显式编写以下内容来覆盖此默认名称:
用于启动 Swing 实现的 [BootElectionsSwing] 类可以如下所示:
package elections.ui.boot;
import elections.ui.service.IElectionsUI;
public class BootElectionsSwing extends AbstractBootElections {
public static void main(String[] arguments) {
new BootElectionsSwing().run();
}
@Override
protected IElectionsUI getUI() {
return ctx.getBean("electionsSwing", IElectionsUI.class);
}
}
这种设计模式将类之间共有的行为提取到父类中,而子类则负责实现其具体细节,被称为策略设计模式。该设计模式要求所有子类都遵循一种共同的行为。
9.6. 实现类 [ElectionsConsole]
![]() |
我们针对 [ui] 层实现的第一个类将是一个利用控制台与用户进行交互的类。以下是在 Eclipse 控制台上显示的对话框示例:
Il y a 7 listes en compétition. Veuillez indiquer le nombre de voix de chacune d'elles :
Nombre de voix de la liste [A] : 2500
Nombre de voix de la liste [B] : 4500
Nombre de voix de la liste [C] : x
Nombre de voix incorrect. Veuillez recommencer
Nombre de voix de la liste [C] : 8000
Nombre de voix de la liste [D] : 12000
Nombre de voix de la liste [E] : 16000
Nombre de voix de la liste [F] : 25000
Nombre de voix de la liste [G] : 32000
Résultats de l'élection
[G,32000,2,false]
[F,25000,2,false]
[E,16000,1,false]
[D,12000,1,false]
[C,8000,0,false]
[B,4500,0,true]
[A,2500,0,true]
[ElectionsConsole] 类可以采用以下基本结构:
package elections.ui.service;
import java.util.Comparator;
import java.util.Scanner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import elections.dao.entities.ListeElectorale;
import elections.metier.service.IElectionsMetier;
@Component
public class ElectionsConsole implements IElectionsUI {
@Autowired
private IElectionsMetier electionsMetier;
@Override
public void run() {
// data entry
try (Scanner clavier = new Scanner(System.in)) {
// lists in competition are requested from the [metier] layer
// we enter the votes
}
// we calculate the number of seats
// we record the results
// lists sorted in descending order of votes
// we display them
}
// electoral list comparison class
class CompareListesElectorales implements Comparator<ListeElectorale> {
// comparison of two candidate lists by number of votes
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// we compare the votes of these two lists
int nbVoix1 = listeElectorale1.getVoix();
int nbVoix2 = listeElectorale2.getVoix();
if (nbVoix1 < nbVoix2) {
return +1;
} else {
if (nbVoix1 > nbVoix2)
return -1;
else
return 0;
}
}
}
}
- 第 12 行:[ElectionsConsole] 类是 Spring 组件;
- 第 13 行:该类实现了 [IElectionsUI] 接口
- 第 15–16 行:Spring 将一个引用注入到 [business] 层;
- 第 18–34 行:[IelectionsUI] 接口的 [run] 方法;
第 21–28 行中的 try 代码块称为 try-with-resources,其语法如下:
第 1 行中的资源必须实现 [java.lang.AutoCloseable] 接口。该资源在第 1 行被打开,并在第 3 行之后自动关闭,无论在这两行之间执行的代码中是否发生异常。这种语法确保了无论发生什么情况,已打开的资源都会被关闭。
任务:编写 [run] 方法的代码。请参考注释作为指导。






