—— 若发现文章有任何不正确的地方,敬请指出,望不吝赐教,谢谢!
文章目录
- 效果展示
- 一、准备知识
- 相关工具:Scene Builder [点击下载](https://openjfx.cn/scene-builder/)
- 二、运行环境
- 三、引入依赖
- 四、项目结构介绍
- 五、配置数据库交互层DAO层
- 5.1 连接数据库配置
- 5.2 MyBatis配置
- 5.3 编写MyBatis工具类 DBUtils.java
- 5.4 DAO层
- 六、编写Service业务逻辑层
- 七、JavaFX相关的类
- 7.1 启动类
- 7.2 编写StageController窗口控制类
- 7.3 公用接口ControlledStage
- 7.4 组件的Controller类
- 7.4.1 登录控制层
- 7.4.2 主页面控制层
- 八、核心业务逻辑
在登录窗口进行登录,登录验证成功后跳转到另一个窗口,若失败则弹出提示框
JavaFX 中文官网:点击查看
javaFX 是一个开源的下一代客户端应用平台,适用于基于Java构建的桌面、移动端和嵌入式系统。 它是许多个人和公司的共同努力的成果,目的是为开发丰富的客户端应用提供一个现代、高效、功能齐全的工具包。
Scene Builder 是一款创建美丽的用户界面,并将您的设计变成一个交互式原型。Scene Builder通过创建可直接用于JavaFX应用程序的用户界面,缩小了设计师和开发人员之间的差距。
JavaFX相关的资料网站:
JavaFXChina 是国内JavaFX资料最为系统全面的网站之一,他们靠业余时间维护此站点,希望能帮到大家。
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.unigroupId>
<artifactId>cs-pluginsartifactId>
<version>1.0-SNAPSHOTversion>
<name>cs-pluginsname>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<junit.version>5.8.1junit.version>
<java.version>8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.openjfxgroupId>
<artifactId>javafx-controlsartifactId>
<version>${java.version}version>
dependency>
<dependency>
<groupId>org.openjfxgroupId>
<artifactId>javafx-fxmlartifactId>
<version>${java.version}version>
dependency>
<dependency>
<groupId>org.kordamp.bootstrapfxgroupId>
<artifactId>bootstrapfx-coreartifactId>
<version>0.4.0version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-apiartifactId>
<version>${junit.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-engineartifactId>
<version>${junit.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.25version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>${java.version}source>
<target>${java.version}target>
configuration>
plugin>
<plugin>
<groupId>org.openjfxgroupId>
<artifactId>javafx-maven-pluginartifactId>
<version>0.0.8version>
<executions>
<execution>
<id>default-cliid>
<configuration>
<mainClass>com.uni/com.uni.AppmainClass>
<launcher>applauncher>
<jlinkZipName>appjlinkZipName>
<jlinkImageName>appjlinkImageName>
<noManPages>truenoManPages>
<stripDebug>truestripDebug>
<noHeaderFiles>truenoHeaderFiles>
configuration>
execution>
executions>
plugin>
plugins>
build>
project>
由于工具会涉及数据库的相关交互,同时有视图的概念,这里采用类似于Web的MVC模式进行设计,其中的M表示Model模型,在这里对应于JavaFX的Stage窗口,V则表示View视图,对应于JavaFX的Scene,C表示Controller控制层,JavaFX官方API有提供getController()的方法。
其他的相关介绍:
名称 | 描述 |
---|---|
controller | 存储控制层相关的类,其中比较重要的是Stage相关的类 |
dao | 存储负责数据库交互的Mapper接口,实现则通过resources文件夹里的XML映射文件 |
pojo | 存储实体类 |
service | 存储业务层接口以及对应的Impl实现类 |
utils | 存储一些工具类,比如获取MyBatis负责数据库交互的mapper类 |
App.java | 继承于JavaFX的Application类,主要用于启动JavaFX程序以及配置相关的窗口信息 |
resources/com/uni/mapper/ | 存储MyBatis里Mapper接口对应的XML映射文件 |
login.fxml | 使用SceneBuilder工具进行可视化设计的登录的页面 |
main.fxml | 使用SceneBuilder工具设计的登录成功后的主页面(这里涉及页面切换概念) |
lib | 存储一些关键的jar包,可以忽略,Maven导入依赖就行 |
database.properties | 配置JDBC连接的一些信息,数据库连接地址、驱动类、用户、密码 |
mybatis-config.xml | 配置项目的MyBatis |
/resources/database.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
user=数据库用户名
password=数据库密码
这里需要注意,为方便代码里获取到配置内容,最好是将该其放在resources资源文件夹里,方便查找以及访问
JDK官方有API操作properties的配置文件,所以不需要手动进行IO流的转换,这里则通过MyBatis框架提供的API直接连接数据库
范例: 根据MySQL的配置文件创建MyBatis框架的工厂(可通过工厂取Mapper类、执行方法等)
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
String CONFIG_PATH = "mybatis-config.xml";
InputStream config = Resources.getResourceAsStream(CONFIG_PATH);
factory = new SqlSessionFactoryBuilder().build(config);
在没有SpringBoot下配置MyBatis果然有些复杂了,不过稍微总结一下,也不难理解,它的主要配置内容如下:
/resources/mybatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="database.properties"/>
<settings>
<setting name="logImpl" value="LOG4J" />
settings>
<typeAliases>
<typeAlias type="com.uni.pojo.User" alias="User"/>
<package name="com.uni.mapper"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/uni/mapper/UserMapper.xml"/>
mappers>
configuration>
关于MyBatis的使用,之前浅学了SSM+SpringBoot,就只记住了@Repository、@Autowired这两个注解,当不使用注解的时候,有多种选择进行创建,这里则使用其中的工厂模式来创建。
DBUtils.java
package com.uni.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class DBUtils{
private static final String CONFIG_PATH = "mybatis-config.xml";
private static SqlSessionFactory factory;
// 单例模式
static {
try {
// 获取配置文件输入流
InputStream config = Resources.getResourceAsStream(CONFIG_PATH);
// 创建工厂
factory = new SqlSessionFactoryBuilder().build(config);
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取session会话
public static SqlSession getSession(){
return factory.openSession();
}
// 关闭session会话
public static void close(){
//..
}
}
在编写DAO层接口前,需先编写相应的实体类,比如笔者需要实现登录效果,这里就设计一个用户User的Pojo(简单的Java对象) 类
User.java
package com.uni.pojo;
/**
* @作者: Unirithe
* @日期: 2022/2/27
**/
public class User {
private Integer id;
private String username;
private String password;
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
Dao层接口类:UserMapper.java
package com.uni.dao;
import com.uni.pojo.User;
/**
* @作者: Unirithe
* @日期: 2022/2/27
**/
public interface UserMapper {
User selectByPassword(User user);
}
Dao层XML映射文件:UserMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.uni.dao.UserMapper">
<select id="selectByPassword" parameterType="User" resultType="User">
SELECT * FROM cs_user
WHERE username = #{username} AND password = #{password}
select>
mapper>
接口类:UserService.java
(实现类放在最后的部分,因为它用到了JavaFX的相关的一些类)
package com.uni.service;
import com.uni.pojo.User;
/**
* @作者: Unirithe
* @日期: 2022/2/27
**/
public interface UserService {
public User login(User user);
}
App.java
package com.uni;
import com.uni.controller.StageController;
import javafx.application.Application;
import javafx.stage.Stage;
public class App extends Application {
public static String mainViewID = "MainView";
public static String mainViewRes = "main.fxml";
public static String loginViewID = "LoginView";
public static String loginViewRes = "login.fxml";
@Override
public void start(Stage primaryStage) {
//新建一个StageController控制器
StageController stageController = new StageController();
//设置默认的主窗口
//加载两个窗口
stageController.loadStage(loginViewID, loginViewRes);
stageController.loadStage(mainViewID, mainViewRes);
//切换主窗口
stageController.change(loginViewID);
}
public static void main(String[] args) {
launch();
}
}
该类使用HashMap结构来存储对应的窗口名称、对应的Stage窗口对象,使用双端队列进行窗口的切换控制,目前还存在一些不足,仅作参考,相关的方法以及介绍。
方法名 | 描述 |
---|---|
void addStage(String a, Stage b) | 根据名称添加Stage窗口到Map结构中 |
Stage getStage(String a ) | 根据Stage窗口名称获取对应的Stage对象 |
change(String a) | 根据Stage名称切换到对应的窗口(要求Stage存在于Map中)设计双端队列的处理 |
loadStage(String a, String b, StageStyle …) | 根据Stage的名称和对应的fxml文件位置,加上可选的窗口风格加载新的Stage对象,加载完毕后会保存到Map结构 |
unloadStage(String a) | 根据Stage名称删除相应的窗口 |
stages | 静态变量,存储所有的Stage名称以及Stage信息 |
queue | 静态变量, 存储当前的窗口序列,可用于返回操作(这里暂未使用) |
StageController.java
package com.uni.controller;
import com.uni.App;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
/**
* @作者: Unirithe
* @日期: 2022/2/27
**/
public class StageController {
private static HashMap<String, Stage> stages = new HashMap<String, Stage>(); // 存储所有Stage
private static Deque<Stage> queue = new LinkedList<>(); // 窗口双端队列(先进后出)(切换窗口有效)
/**
* 添加Stage,保存到Map结构里
* @param name 窗口名称
* @param stage 窗口的stage,由该类的{@link #loadStage}方法进行创建
*
*/
public void addStage(String name, Stage stage) {
stages.put(name, stage);
}
/**
* 根据Stage名称从Map结构里获取相应的Stage
* @param name Stage的对应名称,加载通过{@link #loadStage(String, String, StageStyle...)} 添加通过{@link #addStage(String, Stage)}
* @return Map里存在的Stage对象
*/
public Stage getStage(String name) {
return stages.get(name);
}
/**
* 切换Map结构里存在的Stage,将其压入栈中。若不存在则抛出异常
* @param stageName 切换的Stage名称
*/
public void change(String stageName) {
// 首先获取当前的state
Stage stage = stages.get(stageName);
if(stage == null)
throw new NullPointerException("未获取到指定的Stage,请确保它已经使用过addStage()/loadStage()");
else {
// 遍历队列
if (queue != null && !queue.isEmpty()) {
// 将队首的传移动到队尾
queue.push(queue.pollFirst());
// 关闭所有窗口
for (Stage temp : queue) {
// 若当前窗口是要切换的窗口,则移除(防止重复)
if(temp.equals(stage))
queue.remove(temp);
temp.close();
}
}
// 头插法
queue.addFirst(stage);
// 启动队首窗口
queue.getFirst().show();
}
}
/**
* 加载Stage资源,同时会调用{@link #addStage(String, Stage)},最终存储到 Map结构里
* @param name Stage的名称
* @param resources Stage的资源位置(建议在resources资源文件夹下)
* @param styles Stage的Style风格,可指定多个风格
* @return 返回是否加载成功,只要不报错则说明加载以及保存成功, 成功返回true,反之返回false
*/
public boolean loadStage(String name, String resources, StageStyle... styles) {
try {
//加载FXML资源文件
FXMLLoader loader = new FXMLLoader(App.class.getResource(resources));
Pane tempPane = loader.load();
//通过Loader获取FXML对应的ViewCtr,并将本StageController注入到ViewCtr中
// 通过官方API获取当前Stage的Controller,利用官方提供的泛型设置将其返回为接口ControlledStage
ControlledStage controlledStage = loader.getController();
// 调用相应的set方法,为当前Staged设置现在的Controller列
controlledStage.setStageController(this);
//构造对应的Stage
Scene tempScene = new Scene(tempPane);
Stage tempStage = new Stage();
tempStage.setScene(tempScene);
//初始化窗口的风格
for (StageStyle style : styles) {
tempStage.initStyle(style);
}
//将设置好Stage添加到Map结构
this.addStage(name, tempStage);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/***
* 卸载指定的Stage
* @param name Stage的名称,这Stage在通过{@link #addStage} 或者 {@link #loadStage}两种方法添加后的菜菜有效
* @return 是否卸载成功,成功为true,反之为false
*/
public boolean unloadStage(String name) {
if (stages.remove(name) == null) {
System.out.println("窗口不存在,请检查名称");
return false;
} else {
System.out.println("窗口移除成功");
return true;
}
}
}
该接口的作用是要求实现setStageController()方法,StageController则是上面那个自定义的用于处理JavaFX窗口显示的控制层类,当我们绑定一些按钮,要实现界面跳转时就得通过Controller层的逻辑代码进行处理。所以我们规定其他的Controller类都需要实现这个接口,这提示该类需要有一个StageController对象作为成员变量,方便进行窗口切换的操作。
package com.uni.controller;
/**
* @作者: Unirithe
* @日期: 2022/2/27
**/
public interface ControlledStage {
public void setStageController(StageController stageController);
}
相关的fxml,通过SceneBuilder可视化工具进行设计
<VBox prefHeight="407.0" prefWidth="560.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.uni.controller.LoginController"
>
<children>
<MenuBar VBox.vgrow="NEVER">
<menus>
<Menu mnemonicParsing="false" text="帮助">
<items>
<MenuItem mnemonicParsing="false" text="About MyHelloApp" />
items>
Menu>
menus>
MenuBar>
<AnchorPane maxHeight="-1.0" maxWidth="-1.0" prefHeight="321.0" prefWidth="520.0" VBox.vgrow="ALWAYS">
<children>
<TextField fx:id="username" layoutX="217.0" layoutY="119.0" prefHeight="35.0" prefWidth="202.0" />
<PasswordField fx:id="password" layoutX="217.0" layoutY="175.0" prefHeight="35.0" prefWidth="202.0" />
<Button fx:id="btLogin" onAction="#btLogin" defaultButton="true" layoutX="207.0" layoutY="253.0" mnemonicParsing="false" prefHeight="30.0" prefWidth="195.0" text="登录" />
<Button layoutX="132.0" layoutY="253.0" mnemonicParsing="false" text="注册" />
<CheckBox layoutX="331.0" layoutY="219.0" mnemonicParsing="false" text="记住密码" />
<Text layoutX="45.0" layoutY="82.0" strokeType="OUTSIDE" strokeWidth="0.0" text="CS1.6插件管理系统-登录">
<font>
<Font size="41.0" />
font>
Text>
<Label layoutX="132.0" layoutY="120.0" text="账号">
<font>
<Font size="25.0" />
font>
Label>
<Label layoutX="132.0" layoutY="176.0" text="密码">
<font>
<Font size="25.0" />
font>
Label>
children>
AnchorPane>
children>
VBox>
在登录控制层中,通过@FXML获取到了fxml格式的UI配置文件里使用fx:id
标记的组件,然后直接调用业务层的login方法进行登录,最后根据返回的结果是否为null则判断是否登录成功,若登录成功则需要跳转窗口,若失败则弹出提示窗口(这个功能在业务逻辑层实现)
package com.uni.controller;
import com.uni.App;
import com.uni.pojo.User;
import com.uni.service.UserService;
import com.uni.utils.ServiceUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import java.net.URL;
import java.util.ResourceBundle;
public class LoginController implements ControlledStage, Initializable {
private static UserService userService = ServiceUtils.userService;
StageController controller;
@FXML
private TextField username;
@FXML
private PasswordField password;
@FXML
private void btLogin(){
User login = userService.login(new User(username.getText(), password.getText()));
if(login != null)
goToMain();
}
public void setStageController(StageController stageController) {
this.controller = stageController;
}
public void initialize(URL location, ResourceBundle resources) {
}
public void goToMain(){
controller.change(App.mainViewID);
}
}
这里还没有设计逻辑跳转,但可以参照之前的登录控制层进行添加
package com.uni.controller;
/**
* @作者: Unirithe
* @日期: 2022/2/27
**/
public class MainController implements ControlledStage{
private StageController controller;
@Override
public void setStageController(StageController stageController) {
this.controller = stageController;
}
}
之前声明了UserService接口,定义了login()方法,先进行补充:
这里用到了JavaFX的Alert警告框,通过调用showAndWati()方法就可以弹出这个窗口,则密码验证失败后弹出提示用户输入错误,方便用户操作。
package com.uni.service.impl;
import com.uni.dao.UserMapper;
import com.uni.pojo.User;
import com.uni.service.UserService;
import com.uni.utils.DBUtils;
import javafx.scene.control.Alert;
/**
* @作者: Unirithe
* @日期: 2022/2/27
**/
public class UserServiceImpl implements UserService {
private static UserMapper userMapper;
public UserServiceImpl(){
if(userMapper == null)
userMapper = DBUtils.getSession().getMapper(UserMapper.class);
}
@Override
public User login(User user) {
User result = userMapper.selectByPassword(user) ;
if(result == null){
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("登录提示");
alert.setContentText("输入的账号或密码有误");
alert.showAndWait();
}
return result;
}
}