spring javafx_带有Spring的JavaFX 2

spring javafx

我将从一个大胆的声明开始:我一直很喜欢Java Swing或applet。 在那里,我说了。 如果我进行一些自我分析,那么这种钦佩可能是在我接触Java时开始的。 Swing(实际上)是我使用Java所做的第一件事,它给出了一些统计结果,并使我能够使用该语言做一些事情。 在我年轻的时候,我们建立了一些自制的胖客户来管理我们的3.5英寸软盘/ CD集合(用VB编写,然后用Basic编写),这可能也起到了作用。

无论如何,足以说明我的个人稀缺性。 事实是,Swing帮助许多人构建了出色的应用程序,但众所周知,Swing有其缺点。 对于初学者来说,很长一段时间以来一直没有发展。 如果还需要很多样板代码

您想要创建高质量的代码。 它带有一些古怪的设计“缺陷”,缺少MVC等现成的模式。 样式有点局限性,因为您必须依靠有限的L&F架构,默认情况下不会内置I18N。 可以说现在开发Swing很好,基本上可以追溯到过去。

幸运的是,Oracle几年前通过启动JavaFX试图改变这一状况。 我记得在Devoxx(或Javapolis,当时的名称)上被引入JavaFX。 漂亮的演示程序看起来非常有前途,所以我很高兴看到Swing的继任者终于来了。 从我看到其内部结构的那一刻起,情况就发生了变化。 它的主要缺点之一是它基于一种黑暗的新语法(称为JavaFX脚本)。 如果您从未见过JavaFX脚本; 它看起来像Java,JSON和JavaScript之间的怪异品种。 尽管已将其编译为Java字节码,并且可以使用其中的Java API,但与Java的集成从未真正好。

语言本身(尽管功能很强大)要求您花费大量时间来了解细节,以便最终获得源代码,但是这次比普通的Java代码更难管理和支持。 事实证明,我并不是唯一的一个。 很多人都感到相同(当然也有其他原因),JavaFX从来没有取得过成功。 但是,不久前Oracle通过引入JavaFX 2改变了潮流。

首先,他们摆脱了JavaFX脚本(不再受支持)并将其转变为真正的本机Java SE API(JavaFX 2.2.3是Java 7 SE更新6的一部分)。 JavaFX API现在看起来更像是熟悉的Swing API,这是一件好事。 它为您提供了布局管理器的外观,事件侦听器以及您以前习惯的所有其他组件,但效果甚至更好。 因此,如果您希望像Swing一样编写JavaFX,尽管语法和改进的体系结构稍有不同。 现在也可以
将现有的Java Swing应用程序与JavaFX混合在一起 。

但是还有更多。 他们引入了一种基于XML的标记语言,使您可以描述视图。 这具有一些优点,首先是XML编码比Java编码更快。 与Java相比,可以更容易地生成XML,并且用于描述视图的语法也更加紧凑。 使用某种标记表示视图也更加直观,尤其是如果您曾经做过一些Web开发。 因此,可以拥有以FXML描述的视图(即其调用方式),应用程序控制器与该视图分离(在Java中是这样)和在CSS中的样式(是的,因此不再有L&F,CSS支持是标准的)。 您仍然可以直接在FXML中嵌入Java(或其他语言)。 但这可能不是您想要的(脚本反模式)。 另一个好处是对绑定的支持。 通过将fx:id属性放在视图组件上,并将@FXML批注放在应用程序控制器中的实例变量上,可以将视图中的每个组件绑定到应用程序控制器。 然后将自动注入相应的元素,因此您可以在应用程序控制器内部更改其数据或行为。 事实证明,使用一些代码行,您就可以轻松集成所选的DI框架,这不是很好吗? 那工具呢?

好吧,首先,有一个用于Eclipse的插件(fxclipse),它将动态呈现FXML。 您可以通过Eclipse市场安装它:

该插件将立即呈现您进行的任何调整:

请注意,至少需要JDK7u6才能使该插件正常工作。 如果您的JDK太旧,则会在Eclipse中看到一个空白窗格。 另外,如果您创建JavaFX项目,则需要将jfxrt.jar手动放入构建类路径中。 您可以在%JAVA_HOME%/ jre / lib中找到此文件。

直到知道该插件在视觉上(通过拖放)对您没有帮助,但那里有一个单独的IDE:
现场建设者 。 该构建器也集成在Netbeans中,对于AFAIK,尚不支持eclipse,因此如果要使用它,则必须单独运行它。 该构建器允许您使用拖放方式以可视方式开发FXML。 细节不错; 场景构建器实际上是用JavaFX编写的。 然后,您还有一个名为Scenic View的单独的应用程序,它对正在运行的JavaFX应用程序进行自省,并显示其构建方式。 您将获得具有不同节点及其层次结构的图。 对于每个节点,您可以看到其属性等等:

好的,让我们从一些代码示例开始。 我要做的第一件事是在场景生成器中设计演示应用程序:

我通过将容器/控制器d&d放在视图上以图形方式进行了此操作。 我还提供了要绑定到我的视图和fx:id的控件,您也可以通过场景生成器来做到这一点:

特别是对于按钮,我还添加了onAction(单击按钮后应在控制器上执行的方法):

接下来,我在Eclipse的源代码视图中手动添加了控制器。 每个FXML只能有一个控制器,应该在顶层元素中声明它。 我制作了两个FXML,一个代表主屏幕,另一个代表菜单栏。 您可能希望将逻辑划分为多个控制器,而不是在单个控制器中塞满很多东西–在这里,单一职责是一个很好的设计指南。 第一个FXML是“ search.fxml”,代表搜索条件和结果视图:

<?xml version="1.0" encoding="UTF-8"?><?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.cell.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?><StackPane id="StackPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="be.error.javafx.controller.SearchController"><children><SplitPane dividerPositions="0.39195979899497485" focusTraversable="true" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0"><items><GridPane fx:id="grid" prefHeight="91.0" prefWidth="598.0"><children><fx:include source="/menu.fxml"/><GridPane prefHeight="47.0" prefWidth="486.0" GridPane.columnIndex="1" GridPane.rowIndex="5"><children><Button fx:id="clear" cancelButton="true" mnemonicParsing="false" onAction="#clear" text="Clear" GridPane.columnIndex="1" GridPane.rowIndex="1" /><Button fx:id="search" defaultButton="true" mnemonicParsing="false" onAction="#search" text="Search" GridPane.columnIndex="2" GridPane.rowIndex="1" /></children><columnConstraints><ColumnConstraints hgrow="SOMETIMES" maxWidth="338.0" minWidth="10.0" prefWidth="338.0" /><ColumnConstraints hgrow="SOMETIMES" maxWidth="175.0" minWidth="0.0" prefWidth="67.0" /><ColumnConstraints hgrow="SOMETIMES" maxWidth="175.0" minWidth="10.0" prefWidth="81.0" /></columnConstraints><rowConstraints><RowConstraints maxHeight="110.0" minHeight="10.0" prefHeight="10.0" vgrow="SOMETIMES" /><RowConstraints maxHeight="72.0" minHeight="10.0" prefHeight="40.0" vgrow="SOMETIMES" /></rowConstraints></GridPane><Label alignment="CENTER_RIGHT" prefHeight="21.0" prefWidth="101.0" text="Product name:" GridPane.columnIndex="0" GridPane.rowIndex="1" /><TextField fx:id="productName" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" /><Label alignment="CENTER_RIGHT" prefWidth="101.0" text="Min price:" GridPane.columnIndex="0" GridPane.rowIndex="2" /><Label alignment="CENTER_RIGHT" prefWidth="101.0" text="Max price:" GridPane.columnIndex="0" GridPane.rowIndex="3" /><TextField fx:id="minPrice" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2" /><TextField fx:id="maxPrice" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="3" /></children><columnConstraints><ColumnConstraints hgrow="SOMETIMES" maxWidth="246.0" minWidth="10.0" prefWidth="116.0" /><ColumnConstraints fillWidth="false" hgrow="SOMETIMES" maxWidth="537.0" minWidth="10.0" prefWidth="482.0" /></columnConstraints><rowConstraints><RowConstraints maxHeight="64.0" minHeight="10.0" prefHeight="44.0" vgrow="SOMETIMES" /><RowConstraints maxHeight="68.0" minHeight="0.0" prefHeight="22.0" vgrow="SOMETIMES" /><RowConstraints maxHeight="68.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" /><RowConstraints maxHeight="68.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" /><RowConstraints maxHeight="167.0" minHeight="10.0" prefHeight="14.0" vgrow="SOMETIMES" /><RowConstraints maxHeight="167.0" minHeight="10.0" prefHeight="38.0" vgrow="SOMETIMES" /></rowConstraints></GridPane><StackPane prefHeight="196.0" prefWidth="598.0"><children><TableView fx:id="table" prefHeight="200.0" prefWidth="200.0"><columns><TableColumn prefWidth="120.0" resizable="true" text="OrderId"><cellValueFactory><PropertyValueFactory property="orderId" /></cellValueFactory></TableColumn><TableColumn prefWidth="120.0" text="CustomerId"><cellValueFactory><PropertyValueFactory property="customerId" /></cellValueFactory></TableColumn><TableColumn prefWidth="120.0" text="#products"><cellValueFactory><PropertyValueFactory property="productsCount" /></cellValueFactory></TableColumn><TableColumn prefWidth="120.0" text="Delivered"><cellValueFactory><PropertyValueFactory property="delivered" /></cellValueFactory></TableColumn><TableColumn prefWidth="120.0" text="Delivery days"><cellValueFactory><PropertyValueFactory property="deliveryDays" /></cellValueFactory></TableColumn><TableColumn prefWidth="150.0" text="Total order price"><cellValueFactory><PropertyValueFactory property="totalOrderPrice" /></cellValueFactory></TableColumn></columns></TableView></children></StackPane></items></SplitPane></children>
</StackPane>

在第11行上,您可以看到我配置了应与视图一起使用的应用程序控制器类。 在第17行,您可以看到单独的menu.fxml的导入,如下所示:

<?xml version='1.0' encoding='UTF-8'?><?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.MenuItem?><Pane prefHeight='465.0' prefWidth='660.0' xmlns:fx='http://javafx.com/fxml' fx:controller='be.error.javafx.controller.FileMenuController'><children><MenuBar layoutX='0.0' layoutY='0.0'><menus><Menu mnemonicParsing='false' text='File'><items><MenuItem text='Exit' onAction='#exit' /> </items></Menu></menus></MenuBar></children>
</Pane>

在第7行,您可以看到它使用了另一个控制器。 在Eclipse中,如果从插件打开fxclipse视图,则将获得与场景构建器相同的渲染视图。 如果您想在代码中进行少量更改以使其直接反映出来,那么它很方便:启动应用程序的代码非常标准:

package be.error.javafx;import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;public class TestApplication extends Application {private static final SpringFxmlLoader loader = new SpringFxmlLoader();@Overridepublic void start(Stage primaryStage) {Parent root = (Parent) loader.load('/search.fxml');Scene scene = new Scene(root, 768, 480);primaryStage.setScene(scene);primaryStage.setTitle('JavaFX demo');primaryStage.show();}public static void main(String[] args) {launch(args);}
}

唯一需要注意的是我们从Application扩展。 这是一个样板代码,例如,它将确保UI的创建发生在JavaFX应用程序线程上。 您可能还记得Swing中的此类故事,其中每个UI交互都需要在事件分派器线程(EDT)上发生,JavaFX也是一样。 当您被应用程序回调时,默认情况下您处于“右线程”状态(例如,动作侦听器等方法)。 但是,如果您启动应用程序或在单独的线程中执行长时间运行的任务,则需要确保在正确的线程上启动UI交互。 挥杆你会用
JavaFX的SwingUtilities.invokeLater() : Platform.runLater() 。

更特别的是我们的SpringFxmlLoader:

package be.error.javafx;import java.io.IOException;
import java.io.InputStream;import javafx.fxml.FXMLLoader;
import javafx.util.Callback;import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class SpringFxmlLoader {private static final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringApplicationConfig.class);public Object load(String url) {try (InputStream fxmlStream = SpringFxmlLoader.class.getResourceAsStream(url)) {System.err.println(SpringFxmlLoader.class.getResourceAsStream(url));FXMLLoader loader = new FXMLLoader();loader.setControllerFactory(new Callback<Class<?>, Object>() {@Overridepublic Object call(Class<?> clazz) {return applicationContext.getBean(clazz);}});return loader.load(fxmlStream);} catch (IOException ioException) {throw new RuntimeException(ioException);}}
}

高亮显示的行显示了自定义ControllerFactory。 无需设置此JavaFX即可简单地实例化您在FXML中指定为控制器的类,而无需任何特殊操作。 在这种情况下,该类将不受Spring管理(除非您将使用CTW / LTW AOP)。 通过指定自定义工厂,我们可以定义控制器的实例化方式。 在这种情况下,我们从应用程序上下文中查找bean。 最后,我们有两个控制器SearchController:

package be.error.javafx.controller;import java.math.BigDecimal;
import java.net.URL;
import java.util.ResourceBundle;import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;import be.error.javafx.model.Order;
import be.error.javafx.model.OrderSearchCriteria;
import be.error.javafx.model.OrderService;public class SearchController implements Initializable {@Autowiredprivate OrderService orderService;@FXMLprivate Button search;@FXMLprivate TableView<Order> table;@FXMLprivate TextField productName;@FXMLprivate TextField minPrice;@FXMLprivate TextField maxPrice;@Overridepublic void initialize(URL location, ResourceBundle resources) {table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);}public void search() {OrderSearchCriteria orderSearchCriteria = new OrderSearchCriteria();orderSearchCriteria.setProductName(productName.getText());orderSearchCriteria.setMaxPrice(StringUtils.isEmpty(minPrice.getText()) ? null:new BigDecimal(minPrice.getText()));orderSearchCriteria.setMinPrice(StringUtils.isEmpty(minPrice.getText()) ? null: new BigDecimal(minPrice.getText()));ObservableList<Order> rows = FXCollections.observableArrayList();rows.addAll(orderService.findOrders(orderSearchCriteria));table.setItems(rows);}public void clear() {table.setItems(null);productName.setText('');minPrice.setText('');maxPrice.setText('');}
}

高亮显示的行按各自的顺序排列:

  • 由Spring自动注入,这是我们的Spring托管服务,将用于从中查找数据
  • JavaFX自动注入,我们需要在控制器中进行操作或读取的控件
  • 一种特殊的init方法来初始化我们的表,以便在视图放大时列将自动调整大小
  • 按下搜索按钮时将调用的操作侦听器样式回调
  • 按下清除按钮时将调用的操作侦听器样式回调

最后,FileMenuController除了关闭我们的应用程序外没有其他特殊功能:

package be.error.javafx.controller;import javafx.application.Platform;
import javafx.event.ActionEvent;public class FileMenuController {public void exit(ActionEvent actionEvent) {Platform.exit();}
}

最后(不太令人兴奋)结果:


搜索后:

使视图更宽,也拉长了列:

文件菜单允许我们退出:

在玩完JavaFX2之后,我印象深刻。 还有越来越多的控件(我相信已经有了浏览器控件之类)。 所以我认为我们在这里是正确的。

参考:来自Koen Serneels –技术博客博客的JCG合作伙伴 Koen Serneels提供的带有Spring的JavaFX 2 。

翻译自: https://www.javacodegeeks.com/2013/03/javafx-2-with-spring.html

spring javafx

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/348999.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jframe和mysql登陆_刚写的一个从数据库读取账户和密码进行登陆的小程序~高手请无~...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.sql.Connection;import java.sql.DriverManager;import java.sql.*; import javax.swing.*;public class LoginSystem extends JFrame{publi…

使用WebCrypto API的电子签名

有时我们需要让用户进行电子签名。 通常&#xff0c;人们会理解为将您的手写签名以某种方式放在屏幕上。 根据管辖范围&#xff0c;可能很好&#xff0c;或者仅存储图像可能还不够。 例如&#xff0c;在欧洲&#xff0c;有910/2014号法规 &#xff0c;该法规定义了什么是电子签…

mycat mysql 物理部署_一、MyCat的搭建

一、什么是mycat简单直接点就是&#xff0c;MyCat其实就是一个数据库的中间件&#xff01;一般我们都是app直接到数据库&#xff01;有了MyCat以后&#xff0c;就是app到MyCat然后再访问数据库。mycat是个中间件&#xff0c;它负责连接管理mysql,应用程序连接mycat,把mycat当作…

java 记事本换行_[求助]记事本自动换行

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼我编的一个记事本 自动换行该如何实现import java.io.*;import java.awt.*;import java.awt.event.*;import java.awt.print.*;public class Jishiben extends Frame implements ActionListener{FileDialog fileDlg;String str, f…

JAVA中的适配器应用_Java适配器模式应用之电源适配器功能详解

本文实例讲述了Java适配器模式应用之电源适配器功能。分享给大家供大家参考&#xff0c;具体如下&#xff1a;一、模式定义存在两种适配器模式1 对象适配器模式&#xff0c;在这种适配器模式中&#xff0c;适配器容纳一个它包裹的类对象的物理实体。2 类适配器模式&#xff0c;…

java.util接口_函数接口– Java 8中java.util.function包中的函数接口

java.util接口我以前写过有关功能接口及其用法的文章。 如果您正在探索要成为Java 8一部分的API&#xff0c;尤其是那些支持lambda表达式的API&#xff0c;您会发现很少的接口&#xff0c;例如Function&#xff0c;Supplier&#xff0c;Consumer&#xff0c;Predicate和其他接口…

java new string作用_java中直接new String对象?

首先明确一点&#xff0c;在Java中比较的是两个对象的地址&#xff0c;equals()比较的是两个对象的值&#xff0c;如果使用equals()来替换&#xff0c;则两个输出结果都为true。言归正传&#xff0c;为什么第一个返回为false&#xff1f;先看一下intern()这个方法的描述&#x…

Apache Camel中的短重试与长重试

《骆驼设计模式》一书介绍了20种模式以及用于设计基于Apache Camel的集成解决方案的众多技巧和最佳实践。 每个模式都基于真实的用例&#xff0c;并提供了Camel特定的实现细节和最佳实践。 为了让您有这本书的感觉&#xff0c;以下是该书的重试模式摘录&#xff0c;其中介绍了如…

java注解的继承_Java注解合并,注解继承

spring中有时候一个类上面标记很多注解。实际上Java注解可以进行继承(也就是把多个注解合并成1个)比如说SpringMVC的注解RestControllerRequestMapping("/person")可以合并为一个PathRestController("/user")实现是&#xff1a;import java.lang.annotatio…

java实现驾校考试系统_jsp驾校考试系统

本驾校考试系统采用了Browser/Server体系结构&#xff0c;JSP(Java Server Page)作为前台开发工具&#xff0c;MySQL作为后台数据库进行开发。最终系统实现的系统将分为管理员和学员两个角色&#xff0c;其中系统管理员部分的主要功能包括修改登录密码、学员信息管理、科目1题目…

Java Maven和Gradle构建的主题缓存

Concourse CI 3.3.x引入了在任务运行之间缓存路径的功能。 此功能有助于加快将内容缓存在特定文件夹中的任务-在这里&#xff0c;我将演示如何使用此功能来加快基于Maven和Gradle的Java构建。 我在这篇文章中使用的代码和管道可以在我的github仓库中找到 – https://github.co…

java qlv转mp4 代码_怎么将qlv格式转换成mp4?教你快速转换视频格式的技巧

如何将qlv格式转换成mp4?众所周知qlv格式是腾讯视频的下载格式&#xff0c;而qlv格式的特点在于不能用别的播放器打开。如果用腾讯视频以外的播放器打开则需要将qlv格式转换成mp4&#xff0c;那你知道将qlv格式转换成mp4的技巧吗&#xff1f;下面小编教你一种转换qlv格式的技巧…

mysql导入竖杠分割的数据_MYSQL :逗号分隔串表,分解成竖表

DROP TEMPORARY TABLE IF EXISTS Temp_Num ;CREATE TEMPORARY TABLE Temp_Num ( xh INT PRIMARY KEY ); -- 创建数字辅助表SET i 0;INSERT INTO Temp_Num(xh) -- 写入数字辅助表SELECT i : i1FROM AdDataCenter.Ad_Targeting_Mobisage aLIMIT 0, 100 ;SELECT b.AdGroupID , SU…

maven 父maven_Maven的春天

maven 父maven1.概述 本教程将讨论如何使用Maven设置Spring&#xff0c;并介绍使用Spring依赖项的特定用例。 最新的Spring版本可以在Maven Central中找到。 2.基本的Maven Spring依赖关系 Spring的设计具有模块化和灵活性–基本的Spring容器可用于多种情况&#xff0c;而无需…

java中cell无法输出_java – iText 5.5.3 PDFPCell:长文本不适合单元格(不正确地包装文本)...

我正在尝试创建一个包含表格的波斯语PDF,我想写入它.当我的字符串很长时,它不能正确地适合单元格.感觉像字符串填充单元格颠倒&#xff01;…我的意思是,例如,当我想写“你好我的朋友.什么事情&#xff1f;”在单元格中输出如下&#xff1a;|这是怎么回事&#xff1f; ||我的朋…

emc存储java打开后报错_连接EMC存储系统 - osc_mk8rqvg4的个人空间 - OSCHINA - 中文开源技术交流社区...

1.准备一台笔记本电脑&#xff0c;一根网线即可。2.将网线一头连接笔记本电脑&#xff0c;另一头连接存储。(连接存储的一头应连接到有扳手图标的那一网口上)3.配置IP地址IP&#xff1a;128.221.1.254子网掩码&#xff1a;255.255.255.04.ping 128.221.1.250看是否能ping通&…

浙大JAVA实验题12_2019浙大计算机考研机试模拟赛(2)——概念专题

题目链接 引用自晴神OJA - 边覆盖B - 极大独立集C - 稳定婚姻问题D - 笛卡尔树没赶得上全程的比赛&#xff0c;就做了两道&#xff0c;后面两道以后有时间再补。两道都是概念题&#xff0c;比较基础~ 以下是题解A - 边覆盖Case Time Limit: 200 MS (Others) / 400 MS (Java) …

hadoop小型集群_小型Hadoop集群的Ganglia配置和一些故障排除

hadoop小型集群Ganglia是一个针对大型集群的开源&#xff0c;可扩展且分布式的监视系统。 它收集&#xff0c;汇总并提供数十种与计算机相关的指标&#xff08;例如CPU&#xff0c;内存&#xff0c;存储&#xff0c;网络使用情况&#xff09;的时序视图。 您可以在UC Berkeley …

java 异常堆栈日志分析_Java 进阶之路:深入解读 Java 异常堆栈丢失原因

在应用程序的开发和维护中&#xff0c;通常需要借助运行日志来监控和定位问题。其中&#xff0c;在日志中打印异常堆栈信息对于定位问题极为重要&#xff0c;因此&#xff0c;作为一名工程师&#xff0c;对打印异常堆栈应该不陌生。笔者在实践中曾遇到一个奇怪的现象: Java 应用…

使用Spock Mocks进行Grails 3.3集成测试

在Grails单元测试中&#xff0c;可以轻松使用Grails随附的Spock框架来模拟或存根协作者&#xff08;例如服务&#xff09;。 “ 测试”一章对模拟协作者&#xff0c; doWithSpring / doWithConfig回调方法&#xff0c;在测试中模拟bean的FreshRuntime批注进行了一些解释&#…