BeanShell
BeanShell是一个小型嵌入式Java源代码解释器,具有对象脚本语言特性,能够动态地执行标准JAVA语法。
BeanShell不仅仅可以通过运行其内部的脚本来处理Java应用程序,还可以在运行过程中动态执行你java应用程序执行java代码。因为BeanShell是用java写的,运行在同一个虚拟机的应用程序,因此可以自由地引用对象脚本并返回结果。
BeanShell - Introduction
1、eval()
对脚本进行反射式访问的最简单形式是通过 eval() 命令....
工程在pom.xml引入
<dependency>
<groupId>org.beanshell</groupId>
<artifactId>bsh-core</artifactId>
<version>2.0b4</version>
</dependency>
eval返回值为Object,可以通过eval()求文本表达式的值或者运行脚本;
Object o=interpreter.eval(str);
bsh(BeanShell)动态执行java代码
public static void main(String[] args) throws EvalError {
Interpreter bsh = new Interpreter();
//循环打印变量
bsh.eval("for(int i=0; i<5; i++) { System.out.println(\"hello\"); }");
}
2、source
把刚刚的"beanshell脚本"修改一下输出,放入一个文件中
String bshPath="C:\\Users\\Administrator\\Desktop\\slenium\\demo.bsh";
Interpreter bsh = new Interpreter();
//导入并执行一个脚本文件
bsh.source(bshPath); // or bsh.eval("source(\"myscript.bsh\")");
1
3、松类型变量
Java是强类型的语言,必须声明类型,但是 BeanShell松散类型,可以不用定义变量类型。
元类型:省略掉变量类型
foo = "Foo"; num = (2 + 3) * 10 / 2; System.out.println(foo + " = " + num);
运行结果:
对象类型:省略掉变量类型。
button = new JButton( "My Button" ); frame = new JFrame( "My Frame" ); frame.getContentPane().add( button, "Center" ); frame.pack(); frame.setVisible(true);
运行结果
4、set()和get()
set()方法传递对象的变量参数给BeanShell
将10赋值给num
interpreter.set("num", 10);
通过get()方法去取得BeanShell中的变量
interpreter.get("num");
Interpreter interpreter = new Interpreter();
interpreter.set("num", 5);
interpreter.get("num");
5、脚本方法
可以声明和使用方法就像在Java中一样。
在main函数里执行脚本,并调用demo.bsh里的函数
public static void main(String[] args) throws EvalError, IOException {
String bshPath = "C:\\Users\\Administrator\\Desktop\\slenium\\demo.bsh";
Interpreter bsh = new Interpreter();
//导入并执行脚本
bsh.source(bshPath);
//调用脚本的函数
Object eval = bsh.eval("addTwoNumbers(2,8)");
System.out.println(eval);//10
}
扩展:获取脚本中定义了哪些方法
getMethods() 返回 bsh.BshMethod 对象的数组,这些对象是 BeanShell 脚本方法的内部解析表示形式的包装器:
int addTwoNumbers( int a, int b ) { return a + b; } //打印此命名空间中定义的方法 System.out.println(this.namespace.getMethods());
java 反射 API 使用特殊的类值来表示基本类型,例如 int、char、boolean。这些类型是各自原始包装类中的静态字段。例如 Integer.TYPE、Character.TYPE、Boolean.TYPE。
要调用脚本方法,请调用其 invoke() 方法,并传递参数数组、解释器引用和“callstack”引用。
int subTwoNumbers( int a, int b ) {
System.out.println("调用方法subTwoNumbers");
return a - b;
}
//查找定义的方法
name="subTwoNumbers";
signature = new Class [] { Integer.TYPE, Integer.TYPE };
bshMethod = this.namespace.getMethod( name, signature );
//调用方法
bshMethod.invoke( new Object [] { new Integer(10), new Integer(2) },
this.interpreter, this.callstack );
6、添加第三方jar
准备好我们的第三方jar,如:fastjson.jar
在demo.bsh脚本中使用fastjson.jar中的方法
添加第三方jar的方法如下: interpreter.getClassManager().addClassPath(jarFile.getAbsoluteFile().toURI().toURL());
调用bsh脚本并执行
String bshPath = "C:\\Users\\Administrator\\Desktop\\slenium\\demo.bsh"; Interpreter bsh = new Interpreter(); //最关键的一步,添加第三方jar File jarFile = new File("E:\\tmp\\fastjson-1.2.29.jar"); bsh.getClassManager().addClassPath(jarFile.getAbsoluteFile().toURI().toURL()); //接着就可以在脚本中引入并使用了 bsh.source(bshPath);
运行结果如下
JCEF
JCEF的官方网站(chromiumembedded / java-cef / wiki / Home — Bitbucket)
Java Chromium嵌入式框架(JCEF)。 一个简单的框架,用于使用Java编程语言在其他应用程序中嵌入基于Chromium的浏览器
进程模型
Browser被定义为主进程,负责窗口管理,界面绘制和网络交互。Blink的渲染和Js的执行被放在一个独立的Render 进程中;除此之外,Render进程还负责Js Binding和对Dom节点的访问。 默认的进程模型中,会为每个标签页创建一个新的Render进程。
Browser和Render进程可以通过发送异步消息进行双向通信。
一个从Browser进程发送到Render进程的消息将会在CefRenderProcessHandler::OnProcessMessageReceived()方法里被接收。一个从Render进程发送到Browser进程的消息将会在CefClient::OnProcessMessageReceived()方法里被接收。
JavaScript被集成在Render进程,但是需要频繁和Browser进程交互。 JavaScript API应该被设计成可使用闭包异步执行。
入门案例
初始化JCEF:在应用程序启动时,必须先初始化JCEF
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.OS;
import org.cef.browser.CefBrowser;
import org.cef.handler.CefAppHandlerAdapter;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class TestFrame extends JFrame {
private static final long serialVersionUID = -7410082787754606408L;
public static void main(String[] args) {
new TestFrame();
}
public TestFrame() {
//是否Linux系统
boolean useOSR = OS.isLinux();
//是否透明
boolean isTransparent = false;
//添加Handler,在CEFAPP状态为终止时退出程序
CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
@Override
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
// Shutdown the app if the native CEF part is terminated
if (state == CefApp.CefAppState.TERMINATED) System.exit(0);
}
});
//--------------------------初始化JCEF---------------------------
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = useOSR;
//获取CefApp实例
CefApp cefApp = CefApp.getInstance(settings);
//创建客户端实例
CefClient cefClient = cefApp.createClient();
//-----------------------------创建浏览器实例-------------------------------
//创建一个新的浏览器实例,并打开指定的URL
CefBrowser cefBrowser = cefClient.createBrowser("http://hao.fly63.com/", useOSR, isTransparent);
//将浏览器UI添加到窗口中
getContentPane().add(cefBrowser.getUIComponent(), BorderLayout.CENTER);
pack();
setTitle("JCEF打开极简导航");
setSize(800, 600);
setVisible(true);
//添加一个窗口关闭监听事件
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
CefApp.getInstance().dispose();
dispose();
}
});
}
}
CefSettings
CefSettings结构体允许定义全局的CEF配置,经常用到的配置项如下
single_process 设置为true时,Browser和Renderer使用一个进程。
locale 此设置项将传递给Blink。如果此项为空,将使用默认值“en-US”。
log_file 此项设置的文件夹和文件名将用于输出debug日志。如果此项为空,默认的日志文件名为debug.log,位于应用程序所在的目录
log_severity 此项设置日志级别。只有此等级、或者比此等级高的日志的才会被记录。此项可以通过命令行参数“log-severity”配置,可以设置的值为“verbose”,“info”,“warning”,“error”,“error-report”,“disable”。
emote_debugging_port 此项可以设置1024-65535之间的值,用于在指定端口开启远程调试。例如,如果设置的值为8080,远程调试的URL为http://localhost:8080。CEF或者Chrome浏览器能够调试CEF
执行JavaScript代码
一旦有了浏览器实例,就可以使用CefBrowser
类提供的方法与网页进行交互。例如,你可以执行JavaScript代码、发送消息给网页等
执行JavaScript代码
cefBrowser.executeJavaScript("document.getElementById('myElement').innerHTML = 'Hello, JCEF!';", "",0);
----
发送消息给网页
发送消息给网页:
CefProcessMessage message = CefProcessMessage.create("myMessage"); message.getArgumentList().setString(0, "Hello, JCEF!"); cefBrowser.sendProcessMessage(CefProcessId.BROWSER, message);
处理网页发送的消息
render进程和browser进程之间是通两端的消息路由传递消息的,
创建了一个CefMessageRouterHandlerAdapter的子类,重写了onQuery方法来处理来自JavaScript的查询。
import org.cef.browser.CefBrowser; import org.cef.browser.CefFrame; import org.cef.callback.CefQueryCallback; import org.cef.handler.CefMessageRouterHandlerAdapter; public class JCEFMessageRouterHandler extends CefMessageRouterHandlerAdapter { //重写了onQuery方法来处理来自JavaScript的查询 @Override public boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request, boolean persistent, CefQueryCallback callback) { if (request.equals("getJavaData")) { String javaData = "This data is from Java"; callback.success(javaData); return true; } return false; } }
JCEF使得在Java应用程序中嵌入Chromium引擎成为可能,并且通过使用CefMessageRouter,您可以在Java和JavaScript之间实现双向的交互。
将这个JCEFMessageRouterHandler注册到CefMessageRouter中。这样,您就可以在JavaScript中使用cefQuery来与Java进行交互了
//配置一个查询路由,html页面可使用 window.java({}) 和 window.javaCancel({}) 来调用此方法 CefMessageRouter.CefMessageRouterConfig cmrc = new CefMessageRouter.CefMessageRouterConfig("java", "javaCancel"); //创建查询路由 CefMessageRouter router = CefMessageRouter.create(cmrc); router.addHandler(new JCEFMessageRouterHandler(), true); cefClient.addMessageRouter(router);
当JavaScript代码中调用cefQuery并传递"getJavaData"时,Java代码会返回一段数据。
// 在JavaScript中调用Java方法并获取返回值 cefQuery({ request: "getJavaData", onSuccess: function (response) { console.log("Data from Java: " + response); }, onFailure: function (errorCode, errorMessage) { console.error("Java query failed: " + errorCode + " - " + errorMessage); }, });
举例: 用js去打开文件选择对话框
//打开文件选择器
public static void openFileChooser(CefBrowser browser, final CefQueryCallback callback) {
CefRunFileDialogCallback dialogCallBack = new CefRunFileDialogCallback() {
@Override
public void onFileDialogDismissed(int selectedAcceptFilter, Vector<String> filePaths) {
if (filePaths.size() == 0) {
return;
}
File selectedFile = new File(filePaths.get(0));
if (selectedFile != null) {
String selectedPath = selectedFile.getAbsolutePath();
System.out.println(selectedPath);
callback.success(selectedPath);
}
}
};
browser.runFileDialog(CefDialogHandler.FileDialogMode.FILE_DIALOG_OPEN, "文件选择器", null, null, 0, dialogCallBack);
}
弹出窗口处理
CefLifeSpanHandlerAdapter:弹出窗口创建一个处理的Handler
当我们点击target值为_blank的链接时,JCEF默认以弹出窗口的形式打开新页面。创建一个实现CefLifeSpanHandlerAdapter的类,重写onBeforePopup方法:根据url创建一个CefBrowser对象。
Browser生命周期(Browser Life Span)
public class LifeSpanHandler extends CefLifeSpanHandlerAdapter {
private TabbedPaneTestFrame frame;
public LifeSpanHandler(TabbedPaneTestFrame frame) {
this.frame=frame;
}
/* (non-Javadoc)
* @see org.cef.handler.CefLifeSpanHandlerAdapter#onBeforePopup(org.cef.browser.CefBrowser, org.cef.browser.CefFrame, java.lang.String, java.lang.String)
*/
@Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame, String target_url, String target_frame_name) {
this.frame.createBrowser(target_url);
//返回true表示取消弹出窗口
return true;
}
}
设置onBeforePopup方法的返回值为true,取消弹出窗口
Java SPI 机制
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件。Java的SPI机制可以为某个接口寻找服务实现