【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码
目录
- ★☆★ 写在前面 ★☆★
- ★☆★ 本系列文章 ★☆★
- ★☆★ 开源网址 ★☆★
- 一、给 Swing 加上 Spring
- 0、前期努力
- I. SpringBoot
- II. SpringMVC
- 1、开始搞起:搭建 spring 框架
- 2、添加 Service 并使用
- I. 准备
- II. 使用
- 3、异步 @Async
- I. 准备
- II. 使用
- III. 涅槃重生
- IV. 补充
- 二、给项目打包成 exe
- 1、打包
- 2、转exe
- 三、完
★☆★ 写在前面 ★☆★
请通过目录,选择感兴趣的部分阅读。
★☆★ 本系列文章 ★☆★
【java】本地客户端内嵌浏览器1 - Swing、SWT、DJNativeSwing、javaFX
【java】本地客户端内嵌浏览器2 - chrome/chromium/cef/jcef
【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码
★☆★ 开源网址 ★☆★
https://github.com/supsunc/swing-jcef-spring
一、给 Swing 加上 Spring
★ 这里说一下为什么使用 Spring,是因为本项目的一个功能:“搜寻仪器”,该功能调用了 dll 的方法,此方法至少要等待 7 - 8 秒才会返回结果,而正常写的话,因为是单线程,所以会导致 client 完全卡住,但不是 GG,在卡住期间,js正常运行,且在卡完之后,会直接表现当前 js 运行的状态,给人一种时间消失的感觉。
★ 因此,是打算将“搜寻仪器”扔给异步线程去做,而 spring 的 @Async 注解则正符合需求,于是我便跳进了一个深渊巨坑。
0、前期努力
I. SpringBoot
都说 SpringBoot 多么强大,然而也没真正接触过,在正式入坑之前,还请教了前辈:“SpringBoot只能构建web项目吗?”,哈哈,还是入坑了。
具体细节不再说了,最后成功了用 SpringBoot 搭建起来项目了,但是由于原来的项目依赖相关 dll,用 SpringBoot 打包之后的发布版本,怎么也弄不进去相关 dll,搞了一天,最后我放弃了 SpringBoot。
II. SpringMVC
★☆★ 最开始的想法:我们项目后台就是用 SpringMVC 啊,那么这个 client 能不能用呢。
★☆★ 然后迅速否定,SpringMVC 就是开发 JavaWeb 的,其中的 DispatcherServlet、getServletConfigClasses 等不适用于这种本地 client 啊。
★☆★ 然后转念一想,只用 Spring 不行么?
1、开始搞起:搭建 spring 框架
- 首先就是 Spring 的相关依赖 jar 包
下载地址:
http://maven.springframework.org/release/org/springframework/spring/
https://repo.spring.io/release/org/springframework/spring/
我这边主要使用了核心包:
spring 还需要 commons-logging.jar,下载地址:
commons-logging
- 在项目中 lib 文件夹中创建 spring 文件夹,然后将 jar 包弄到里面,然后 Add as Library。
- 新建 package 叫做
my.spring.config
,用来放置 spring 配置文件。 - 在
my.spring.config
中创建 ApplicationContextXml.java,直接分享源代码:
package qpcr.spring.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan(basePackages = {"my"})
public class ApplicationContextXml {}
- 给 idea 配上 spring 框架(此步不做也行,不影响程序 Run)
- 打开 Project Structure,点击 Fact,然后点击“加号”,然后点击“spring”。
- 选择 Module,点击 OK。
- 点击右侧的“加号”。
- 选中后点击 OK。
- Apply、OK 关闭窗口即可。
- 在包
my.spring.main
中创建 UI.java,然后将 Main.java 中的init()
方法移动到这个 UI.java 中。让 UI.java 实现一个接口org.springframework.beans.factory.InitializingBean
,并重写afterPropertiesSet()
方法,执行init()
。
package my.client.main;import my.client.browser.MyBrowser;
import my.client.handler.DownloadHandler;
import my.client.handler.MenuHandler;
import my.client.handler.MessageRouterHandler;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.browser.CefMessageRouter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;@Component
public class UI implements InitializingBean {private void init() {EventQueue.invokeLater(() -> {JFrame jFrame = new JFrame("MyBrowser");jFrame.setMinimumSize(new Dimension(1366, 738)); // 设置最小窗口大小jFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); // 默认窗口全屏jFrame.setIconImage(Toolkit.getDefaultToolkit().getImage(jFrame.getClass().getResource("/images/icon.png")));if (!CefApp.startup()) { // 初始化失败JLabel error = new JLabel("<html><body> 在启动这个应用程序时,发生了一些错误,请关闭并重启这个应用程序。<br>There is something wrong when this APP start up, please close and restart it.</body></html>");error.setFont(new Font("宋体/Arial", Font.PLAIN, 28));error.setIcon(new ImageIcon(jFrame.getClass().getResource("/images/error.png")));error.setForeground(Color.red);error.setHorizontalAlignment(SwingConstants.CENTER);jFrame.getContentPane().setBackground(Color.white);jFrame.getContentPane().add(error, BorderLayout.CENTER);jFrame.setVisible(true);return;}MyBrowser myBrowser = new MyBrowser("https://www.baidu.com", false, false);CefClient client = myBrowser.getClient();// 绑定 MessageRouter 使前端可以执行 js 到 java 中CefMessageRouter cmr = CefMessageRouter.create(new CefMessageRouter.CefMessageRouterConfig("cef", "cefCancel"));cmr.addHandler(new MessageRouterHandler(), true);client.addMessageRouter(cmr);// 绑定 ContextMenuHandler 实现右键菜单client.addContextMenuHandler(new MenuHandler(jFrame));// 绑定 DownloadHandler 实现下载功能client.addDownloadHandler(new DownloadHandler());jFrame.getContentPane().add(myBrowser.getBrowserUI(), BorderLayout.CENTER);jFrame.setVisible(true);jFrame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int i;String language = "en-us";if (language.equals("en-us"))i = JOptionPane.showOptionDialog(null, "Do you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"Yes", "No"}, "Yes");else if (language.equals("zh-cn"))i = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的", "不"}, "是的");elsei = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?\nDo you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的(Yes)", "不(No)"}, "是的(Yes)");if (i == JOptionPane.YES_OPTION) {myBrowser.getCefApp().dispose();jFrame.dispose();System.exit(0);}}});});}@Overridepublic void afterPropertiesSet() throws Exception {init();}
}
- 修改 main 方法。
★ 此处才是最坑的,我这边用的全是注解开发,没有一个 xml 。
★ 然而网上搜索怎么启动 spring,全是ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
两个实例化方法,然后再 getBean() 之类的。
全注解开发的正确代码应该这么写:
package my.client.main;import my.spring.config.ApplicationContextXml;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Main {public static void main(String[] args) {new AnnotationConfigApplicationContext(ApplicationContextXml.class);}
}
2、添加 Service 并使用
I. 准备
- 新建两个 package,分别是
my.client.interfaces
和my.client.impl
。 - 在
my.client.interfaces
中新建一个 interface 叫做 MyService。
package my.client.interfaces;public interface MyService {String doSomething();
}
- 在
my.client.impl
中新建一个 class 叫做MyServiceImpl
,实现MyService
接口,并加上@Service
注解。
package my.client.impl;import my.client.interfaces.MyService;
import org.springframework.stereotype.Service;@Service
public class MyServiceImpl implements MyService {@Overridepublic String doSomething() {System.out.println("This is method 'doSomething'.");return "doSomething";}
}
II. 使用
- 给 UI.java 注入 MyService。
@Component
public class UI implements InitializingBean {private MyService myService;public UI(MyService myService) {this.myService = myService;}private void init() {...}@Overridepublic void afterPropertiesSet() throws Exception {init();}
}
- 将 myService 传给 MessageRouterHandler 构造函数。
// 绑定 MessageRouter 使前端可以执行 js 到 java 中
CefMessageRouter cmr = CefMessageRouter.create(new CefMessageRouter.CefMessageRouterConfig("cef", "cefCancel"));
cmr.addHandler(new MessageRouterHandler(myService), true);
client.addMessageRouter(cmr);
- 修改 MessageRouterHandler 构造函数,将 MyService 对象存起来。
public class MessageRouterHandler extends CefMessageRouterHandlerAdapter {private MyService myService;public MessageRouterHandler(MyService myService) {this.myService = myService;}@Overridepublic boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request, boolean persistent, CefQueryCallback callback) {...}@Overridepublic void onQueryCanceled(CefBrowser browser, CefFrame frame, long query_id) {}
}
- 在 onQuery 方法中,使用 myService.doSomething()。
if (request.indexOf("doSomething") == 0) {callback.success(myService.doSomething());return true;
}
3、异步 @Async
I. 准备
- 在
my.spring.config
中,创建一个 class 叫做TaskExecutorConfig
,实现AsyncConfigurer
接口。 - 配置线程池,重写
getAsyncExecutor()
方法。
package my.spring.config;import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;@Configuration
public class TaskExecutorConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// Set up the ExecutorService.executor.initialize();// 线程池核心线程数,核心线程会一直存活,即使没有任务需要处理。// 当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。// 核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。// 默认是 1// CPU 核心数 Runtime.getRuntime().availableProcessors();executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() + 1);// 当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。// 如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。// 默认时是 Integer.MAX_VALUE// executor.setMaxPoolSize(10);// 任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。// 默认时是 Integer.MAX_VALUEexecutor.setQueueCapacity(1000);/* keepAliveTime: 当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。* 默认时是 60* executor.setKeepAliveSeconds(10);*/// allowCoreThreadTimeout: 是否允许核心线程空闲退出,默认值为false。// 如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。// executor.setAllowCoreThreadTimeOut(true);return executor;}
}
II. 使用
- 在
my.client.interfaces
中新建一个 interface 叫做 AsyncService。
package my.client.interfaces;import java.util.concurrent.Future;public interface AsyncService {Future<String> asyncMethod();
}
- 在
my.client.impl
中新建一个 class 叫做AsyncServiceImpl
,实现AsyncService
接口,并加上@Service
注解。重写asyncMethod
方法,写一个Thread.sleep(5000);
代替耗时操作。
package my.client.impl;import my.client.interfaces.AsyncService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;import java.util.concurrent.Future;@Service
public class AsyncServicesImpl implements AsyncService {@Override@Asyncpublic Future<String> asyncMethod() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}return new AsyncResult<>("I am finished.");}
}
- 在
MyServiceImpl
中注入AsyncService
。
package my.client.impl;import my.client.interfaces.AsyncService;
import my.client.interfaces.MyService;
import org.springframework.stereotype.Service;@Service
public class MyServiceImpl implements MyService {private AsyncService asyncService;public MyServiceImpl(AsyncService asyncService) {this.asyncService = asyncService;}@Overridepublic String doSomething() {System.out.println("This is method 'doSomething'.");return "doSomething";}
}
- 重写
doSomething()
方法,使用asyncService
的asyncMethod
方法。
★ 这是网上提供的异步结果的获取方法。
★ 等等,这个异步线程不还是在主线程用一个 while 去等待结果么?这算哪门子异步啊。
@Override
public String doSomething() {Future<String> futureAsyncMethod= asyncService.asyncMethod();String result = "";while (!futureAsyncMethod.isDone()) {try {result = futureAsyncMethod.get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}return result;
}
III. 涅槃重生
在 spring 章节部分开头,我说明了为什么要使用 spring。
其直接原因就是client 内嵌浏览器
向client
发送请求,然后请求不响应的时候,client
就会卡住。
那么解决办法就很简单了:
- 把耗时操作扔给异步线程去操作,没有 Done 则返回 “doing”,前端接收响应数据为 “doing”,则再次发请求。
- 判断是否正在进行那个耗时操作,如果在进行,则判断 isDone,没有 Done 则返回 “doing”,重复上一步操作。
- 如果 Done 了,则正常返回数据。
- 首先修改前端网页部分,如果响应数据为 “doing”,则再次发请求。(当然如果你正确返回结果就有可能是 doing 的话,那就把这个字符串换一个)
function doSomething() {// 这里的 cef 就是 client 创建 CefMessageRouter 对象的入参涉及到的字符串window.cef({request: 'doSomething',onSuccess(response) {if(response === "doing"){setTimeout(doSomething, 0); // 将任务加到新队列中,避免网页卡住}else{// 正确得到响应数据}},onFailure(error_code, error_message) {console.log(error_code, error_message);}});
}
- 由于 Spring 组件默认就是单例的,所以可以这么写,直接分享源代码:
package my.client.impl;import my.client.interfaces.AsyncService;
import my.client.interfaces.MyService;
import org.springframework.stereotype.Service;import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;@Service
public class MyServiceImpl implements MyService {private AsyncService asyncService;public MyServiceImpl(AsyncService asyncService) {this.asyncService = asyncService;}private Future<String> futureAsyncMethod = null;@Overridepublic String doSomething() {if (futureAsyncMethod == null)futureAsyncMethod = asyncService.asyncMethod();if (futureAsyncMethod.isDone()) {String result = "";try {result = futureAsyncMethod.get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}futureAsyncMethod = null;return result;} else {return "doing";}}
}
IV. 补充
如果你和我发生了一样的事情:
- 报错:
Bean 'my.spring.config.TaskExecutorConfig' of type [XXXX] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
@Async
根本没生效。
- 请参考这个链接:
【小家Spring】注意BeanPostProcessor启动时对依赖Bean的“误伤”陷阱(is not eligible for getting processed by all...)
- 不过我并没有从这个链接中直接找到解决办法。
- 我的解决办法是,给
TaskExecutorConfig
类加上BeanPostProcessor
的接口:
@Configuration
@EnableAsync
public class TaskExecutorConfig implements AsyncConfigurer, BeanPostProcessor {// BeanPostProcessor 接口的目的是使当前 Configuration 先加载// 可能是吧,不太清楚,请参考上面的链接@Overridepublic Executor getAsyncExecutor() {...}
}
二、给项目打包成 exe
1、打包
- 按图所示。
- 按图所示。
- 按图所示创建文件夹 bin。
- 按图所示,在 bin 中创建文件夹 jcef 和 spring,将对应依赖移进去,在 jcef 中创建 lib 文件夹。
- 右键单击 lib,或点击上面的“加号”,选择 Directory Content。
- 选择 lib 下面 jcef 里面的 lib\win64。
- 点击 jcef.jar 之后,点击下面的 class path 后面的展开。
- 编辑完了之后,Build Artifacts。
- 打开 Artifacts Build 之后的地方:E:\idea\jcef\out\artifacts\jcef_jar。
- 我们写一个 bat 文件命令行,或用 cmd cd 到此路径,然后执行命令行:
java -Djava.library.path=.\bin\jcef\lib -jar jcef.jar
。
如果不写
-Djava.library.path=.\bin\jcef\lib
则会报之前提到过的错:no chrome_elf in java.library.path
。
2、转exe
将
E:\idea\jcef\out\artifacts\jcef_jar
的jcef_jar
改名为app
- 下载工具:exe4j,激活过程我就不说了。
- 打开 exe4j,第一个页面:
Welcome
,直接 Next 即可。
- 第二个页面:
Project type
,默认选择Regular mode
即可,必须选择这个,网上大部分教程全是选择"JAR in EXE" mode
,导致后面步骤完全不一样,真坑,前进的道路真曲折。
- 第三个页面:
Application info
,三个填空:
- 第一个为应用程序名字;
- 第二个为导出地址;
- 第三个为 exe 地址,写一个
.
即可。
- 第四个页面:
Executable info
,输入 exe 名字,视情况勾选Allow only a single running instance of the application
,可以在Advanced Options
中设置一些其他信息。(默认是32-bit,如果用64位jre,则需要到那里设置Generate 64-bit executable
)
- 第五个页面:
Java invocation
。
- 点击那个“加号”。
- 选择 Archive,然后选择那个 jar 包。
- 再次点击那个“加号”,然后选择 Directory,选择 jcef 和 spring 文件夹。
- 点击下面
Main class from
后面的"更多":
- 点击 Advanced Options 里面的 Native libraries。
- 点击“加号”后,选择 jcef 里面的 lib 文件夹。
- 第六个页面:
JRE
,可以设置 Minimum version,也可以在Advanced Options
中设置一些其他信息。
- 第七个页面:
Splash screen
,第八个页面:Messages
,默认即可。
- 第九个页面:
Compile executable
,等待自动完成。
- 第十个页面:
Finished
,可以点击 Save As 将配置存起来,下次直接 open 这个配置。
- 点击
Click Here to Start the Application
,可以直接启动 exe,或到指定路径下,双击打开。
三、完
本博客写了 4 天,写之前研究这些全部内容,用了两个星期。
本博客于2019-10-31 18:38
首发于CSDN博客
。
累死我啦!!!
【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码相关推荐
- 【java】本地客户端内嵌浏览器2 - chrome/chromium/cef/jcef
目录 ★☆★ 写在前面 ★☆★ ★☆★ 本系列文章 ★☆★ ★☆★ 开源网址 ★☆★ 一.发现新大陆 - CEF/JCEF 0.前言 1.使用 jcef.jar 搭建项目 2.启动包含 jcef.ja ...
- C# WPF使用CefSharp客户端内嵌浏览器做一个开小差工具
前言 CefSharp是一个C#客户端内嵌入chromium开源项目浏览器的工具,方便在客户端中自然的访问网页内容,十分好用.当然,网上有很多使用CefSharp的教程了,怎么使用都很详尽.我这里只是 ...
- java gui 嵌入浏览器_DJNativeSwing-SWT组件-Java GUI中内嵌浏览器
Java项目中经常需要在GUI程序中嵌入浏览器,而Swing自带的组件对CSS.JS的支持不是很好,网上也有很多组件,参考 但是由于对各个平台的支持不是很好,笔者是在Mac系统下进行开发,很多组件只支 ...
- html微信内置浏览器点击图片放大,双指缩放,附源码(自测可用)
本人用的是vue框架 引用微信的JS <script type="text/javascript" src="http://res.wx.qq.com/open/j ...
- java使用swing实现内嵌浏览器
java使用swing实现内嵌浏览器 1.使用swing内嵌浏览器需要导入3个jar包,第3个根据电脑版本选择 dj-native-swing-swt.jar dj-native-swing. ...
- Java swing 做一个传统Web项目的桌面程序启动器(内嵌浏览器)
背景:公司有个老项目,web项目,但是使用者都想要一个桌面应用程序.实际上,是web程序的启动较为麻烦.这里每次都需要启动Tomcat和浏览器. 想法:重写一个项目太麻烦,想想成本,人间不值得.于是我 ...
- Java实现内嵌浏览器
创建项目 ----> 导入需要的jar ----> 代码实现 需要的jar: https://pan.baidu.com/s/1MEZ1S0LnKSMGQm24QWgmCw 代码: ...
- PC游戏中用CEF3制作内嵌浏览器
因为项目需要,需要将游戏手机助手中的朋友圈给移植到PC游戏中,而以前游戏中的内嵌浏览器采用的是IE6内核,满足不了我们的需求,于是决定把Cef3内嵌到游戏中,在完成正常工作之余,利用闲散时间不断地查找 ...
- java selenium div内嵌滚动条 网页长截图发邮件
java selenium 网页内嵌滚动条截图发邮件 主要问题 下面展开说 由于公司要求做一个接口,请求这个接口进行网页截图并发送邮件的功能,本来前期是用python写好了,but似乎不太符合要求,那 ...
最新文章
- Datawahle文化衫来了!
- JSP与servlets的区别
- 在objective-c / cocoa中抛出异常
- 用CSS的float属性创建三栏布局网页的方法
- 多进程多线程GDB调试 (转)
- HDOJ 2546饭卡(01背包问题)
- 打印5列五颗星_13个Excel快捷打印技巧,让你熟练掌握打印机操作
- 基于Web用户控件的Portal
- mysql集群fuzhi_MySQL集群 和MySQL主从复制的不同
- vscode 文件高亮插件_vscode中的vue文件不高亮,但是已经安装了vetur插件了,到底为什么???...
- spring boot进行上传文件
- openGauss与PostgreSQL分区策略语法测试
- Java基础-Collection集合接口(List及Set)
- 【图像直线拟合】基于matlab最小二乘法图像直线拟合【含Matlab源码 100期】
- java catch中throw_Java的catch块中throw e和throw new Exception(e)有什么区别?
- 搜索命令:whereis/which/locate/find/grep
- 一加nfc门禁卡录入_一加7t怎么开启NFC 模拟门禁卡方法介绍
- 解决pyspark的 Added jobs for time问题
- 一份完整的聚合支付设计方案,喜欢就拿去用吧!
- 解决vue项目路由出现message: “Navigating to current location (XXX) is not allowed“的问题
热门文章
- 基于卷积神经网络的人脸表情识别应用--AR川剧变脸(一)
- 数据分析师培训机构告诉你,如何成为优秀的数据分析师
- selenium淘宝爬虫
- 【错误记录】Visual Studio 2019 中运行 Unity C# 脚本时报错 ( 根据解决方案, 可能需要安装额外的组件才能获得 | .NET 桌面开发 | 使用 Unity 的游戏开发 )
- .easyui(DataGrid数据查询)
- python前端框架实例_Python数据可视化:PyQt5 + ECharts框架实例
- php脚本的执行过程(编译与执行相分离)
- 《微信小程序跳转页面安卓闪现两次》
- 什么是外观检测系统?外观检测系统的功能有哪些?
- Python怎么启动打开Windows的应用程序