diff --git a/CHANGELOG.md b/CHANGELOG.md
index 394e1b4f2..19257f5e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
* 【core 】 CollUtil.newHashSet重载歧义,更换为set方法
* 【core 】 增加ListUtil,增加Hash32、Hash64、Hash128接口
* 【crypto 】 BCUtil增加readPemPrivateKey和readPemPublicKey方法
+* 【cache 】 替换读写锁为StampedLock,增加LockUtil
### Bug修复
* 【core 】 修复NumberWordFormatter拼写错误(issue#799@Github)
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java
index bb67d4e91..143afde03 100644
--- a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java
@@ -6,9 +6,7 @@ import cn.hutool.core.lang.func.Func0;
import java.util.Iterator;
import java.util.Map;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+import java.util.concurrent.locks.StampedLock;
/**
* 超时和限制大小的缓存的默认实现
@@ -17,32 +15,39 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
*
创建一个新的Map
* 实现 prune
策略
*
- *
- * @author Looly,jodd
*
* @param 键类型
* @param 值类型
+ * @author Looly, jodd
*/
public abstract class AbstractCache implements Cache {
private static final long serialVersionUID = 1L;
protected Map> cacheMap;
- private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
- private final ReadLock readLock = cacheLock.readLock();
- private final WriteLock writeLock = cacheLock.writeLock();
+ private final StampedLock lock = new StampedLock();
- /** 返回缓存容量,0
表示无大小限制 */
+ /**
+ * 返回缓存容量,0
表示无大小限制
+ */
protected int capacity;
- /** 缓存失效时长, 0
表示无限制,单位毫秒 */
+ /**
+ * 缓存失效时长, 0
表示无限制,单位毫秒
+ */
protected long timeout;
- /** 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 */
+ /**
+ * 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。
+ */
protected boolean existCustomTimeout;
- /** 命中数 */
+ /**
+ * 命中数
+ */
protected int hitCount;
- /** 丢失数 */
+ /**
+ * 丢失数
+ */
protected int missCount;
// ---------------------------------------------------------------- put start
@@ -53,20 +58,19 @@ public abstract class AbstractCache implements Cache {
@Override
public void put(K key, V object, long timeout) {
- writeLock.lock();
-
+ final long stamp = lock.writeLock();
try {
putWithoutLock(key, object, timeout);
} finally {
- writeLock.unlock();
+ lock.unlockWrite(stamp);
}
}
/**
* 加入元素,无锁
- *
- * @param key 键
- * @param object 值
+ *
+ * @param key 键
+ * @param object 值
* @param timeout 超时时长
* @since 4.5.16
*/
@@ -85,8 +89,7 @@ public abstract class AbstractCache implements Cache {
// ---------------------------------------------------------------- get start
@Override
public boolean containsKey(K key) {
- readLock.lock();
-
+ final long stamp = lock.readLock();
try {
// 不存在或已移除
final CacheObj co = cacheMap.get(key);
@@ -99,7 +102,7 @@ public abstract class AbstractCache implements Cache {
return true;
}
} finally {
- readLock.unlock();
+ lock.unlockRead(stamp);
}
// 过期
@@ -111,24 +114,14 @@ public abstract class AbstractCache implements Cache {
* @return 命中数
*/
public int getHitCount() {
- this.readLock.lock();
- try {
- return hitCount;
- } finally {
- this.readLock.unlock();
- }
+ return hitCount;
}
/**
* @return 丢失数
*/
public int getMissCount() {
- this.readLock.lock();
- try {
- return missCount;
- } finally {
- this.readLock.unlock();
- }
+ return missCount;
}
@Override
@@ -140,11 +133,11 @@ public abstract class AbstractCache implements Cache {
public V get(K key, Func0 supplier) {
V v = get(key);
if (null == v && null != supplier) {
- writeLock.lock();
+ final long stamp = lock.writeLock();
try {
// 双重检查锁
final CacheObj co = cacheMap.get(key);
- if(null == co || co.isExpired() || null == co.getValue()) {
+ if (null == co || co.isExpired()) {
try {
v = supplier.call();
} catch (Exception e) {
@@ -155,7 +148,7 @@ public abstract class AbstractCache implements Cache {
v = co.get(true);
}
} finally {
- writeLock.unlock();
+ lock.unlockWrite(stamp);
}
}
return v;
@@ -163,23 +156,25 @@ public abstract class AbstractCache implements Cache {
@Override
public V get(K key, boolean isUpdateLastAccess) {
- readLock.lock();
-
+ // 尝试读取缓存,使用乐观读锁
+ long stamp = lock.readLock();
try {
// 不存在或已移除
final CacheObj co = cacheMap.get(key);
- if (co == null) {
+ if (null == co) {
missCount++;
return null;
}
- if (false == co.isExpired()) {
+ if (co.isExpired()) {
+ missCount++;
+ } else{
// 命中
hitCount++;
return co.get(isUpdateLastAccess);
}
} finally {
- readLock.unlock();
+ lock.unlock(stamp);
}
// 过期
@@ -199,30 +194,32 @@ public abstract class AbstractCache implements Cache {
@Override
public Iterator> cacheObjIterator() {
CopiedIter> copiedIterator;
- readLock.lock();
+ final long stamp = lock.readLock();
try {
copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
} finally {
- readLock.unlock();
+ lock.unlockRead(stamp);
}
return new CacheObjIterator<>(copiedIterator);
}
// ---------------------------------------------------------------- prune start
+
/**
- * 清理实现
- *
+ * 清理实现
+ * 子类实现此方法时无需加锁
+ *
* @return 清理数
*/
protected abstract int pruneCache();
@Override
public final int prune() {
- writeLock.lock();
+ final long stamp = lock.writeLock();
try {
return pruneCache();
} finally {
- writeLock.unlock();
+ lock.unlockWrite(stamp);
}
}
// ---------------------------------------------------------------- prune end
@@ -235,7 +232,7 @@ public abstract class AbstractCache implements Cache {
/**
* @return 默认缓存失效时长。
- * 每个对象可以单独设置失效时长
+ * 每个对象可以单独设置失效时长
*/
@Override
public long timeout() {
@@ -244,26 +241,16 @@ public abstract class AbstractCache implements Cache {
/**
* 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用
- *
+ *
* @return 过期对象清理是否可用,内部使用
*/
protected boolean isPruneExpiredActive() {
- this.readLock.lock();
- try {
- return (timeout != 0) || existCustomTimeout;
- } finally {
- this.readLock.unlock();
- }
+ return (timeout != 0) || existCustomTimeout;
}
@Override
public boolean isFull() {
- this.readLock.lock();
- try {
- return (capacity > 0) && (cacheMap.size() >= capacity);
- } finally {
- this.readLock.unlock();
- }
+ return (capacity > 0) && (cacheMap.size() >= capacity);
}
@Override
@@ -273,49 +260,34 @@ public abstract class AbstractCache implements Cache {
@Override
public void clear() {
- writeLock.lock();
+ final long stamp = lock.writeLock();
try {
cacheMap.clear();
} finally {
- writeLock.unlock();
+ lock.unlockWrite(stamp);
}
}
@Override
public int size() {
- this.readLock.lock();
- try {
- return cacheMap.size();
- } finally {
- this.readLock.unlock();
- }
+ return cacheMap.size();
}
@Override
public boolean isEmpty() {
- this.readLock.lock();
- try {
- return cacheMap.isEmpty();
- } finally {
- this.readLock.unlock();
- }
+ return cacheMap.isEmpty();
}
@Override
public String toString() {
- this.readLock.lock();
- try {
- return this.cacheMap.toString();
- } finally {
- this.readLock.unlock();
- }
+ return this.cacheMap.toString();
}
// ---------------------------------------------------------------- common end
/**
* 对象移除回调。默认无动作
- *
- * @param key 键
+ *
+ * @param key 键
* @param cachedObject 被缓存的对象
*/
protected void onRemove(K key, V cachedObject) {
@@ -324,27 +296,27 @@ public abstract class AbstractCache implements Cache {
/**
* 移除key对应的对象
- *
- * @param key 键
+ *
+ * @param key 键
* @param withMissCount 是否计数丢失数
*/
private void remove(K key, boolean withMissCount) {
- writeLock.lock();
+ final long stamp = lock.writeLock();
CacheObj co;
try {
co = removeWithoutLock(key, withMissCount);
} finally {
- writeLock.unlock();
+ lock.unlockWrite(stamp);
}
if (null != co) {
onRemove(co.key, co.obj);
}
}
-
+
/**
* 移除key对应的对象,不加锁
- *
- * @param key 键
+ *
+ * @param key 键
* @param withMissCount 是否计数丢失数
* @return 移除的对象,无返回null
*/
diff --git a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java
index 166c57a00..0fd8b80d9 100644
--- a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java
+++ b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java
@@ -1,15 +1,12 @@
package cn.hutool.cache.test;
-import java.util.Iterator;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
import cn.hutool.cache.Cache;
import cn.hutool.cache.impl.FIFOCache;
import cn.hutool.cache.impl.LRUCache;
import cn.hutool.core.lang.Console;
import cn.hutool.core.thread.ThreadUtil;
+import org.junit.Ignore;
+import org.junit.Test;
/**
* 缓存单元测试
@@ -28,30 +25,22 @@ public class CacheConcurrentTest {
// 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
for (int i = 0; i < threadCount; i++) {
- ThreadUtil.execute(new Runnable() {
- @Override
- public void run() {
- cache.put("key1", "value1", System.currentTimeMillis() * 3);
- cache.put("key2", "value2", System.currentTimeMillis() * 3);
- cache.put("key3", "value3", System.currentTimeMillis() * 3);
- cache.put("key4", "value4", System.currentTimeMillis() * 3);
- ThreadUtil.sleep(1000);
- cache.put("key5", "value5", System.currentTimeMillis() * 3);
- cache.put("key6", "value6", System.currentTimeMillis() * 3);
- cache.put("key7", "value7", System.currentTimeMillis() * 3);
- cache.put("key8", "value8", System.currentTimeMillis() * 3);
- Console.log("put all");
- }
+ ThreadUtil.execute(() -> {
+ cache.put("key1", "value1", System.currentTimeMillis() * 3);
+ cache.put("key2", "value2", System.currentTimeMillis() * 3);
+ cache.put("key3", "value3", System.currentTimeMillis() * 3);
+ cache.put("key4", "value4", System.currentTimeMillis() * 3);
+ ThreadUtil.sleep(1000);
+ cache.put("key5", "value5", System.currentTimeMillis() * 3);
+ cache.put("key6", "value6", System.currentTimeMillis() * 3);
+ cache.put("key7", "value7", System.currentTimeMillis() * 3);
+ cache.put("key8", "value8", System.currentTimeMillis() * 3);
+ Console.log("put all");
});
}
for (int i = 0; i < threadCount; i++) {
- ThreadUtil.execute(new Runnable() {
- @Override
- public void run() {
- show(cache);
- }
- });
+ ThreadUtil.execute(() -> show(cache));
}
System.out.println("==============================");
@@ -66,23 +55,20 @@ public class CacheConcurrentTest {
for (int i = 0; i < threadCount; i++) {
final int index = i;
- ThreadUtil.execute(new Runnable() {
- @Override
- public void run() {
- cache.put("key1"+ index, "value1");
- cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
-
- int size = cache.size();
- int capacity = cache.capacity();
- if(size > capacity) {
- Console.log("{} {}", size, capacity);
- }
- ThreadUtil.sleep(1000);
- size = cache.size();
- capacity = cache.capacity();
- if(size > capacity) {
- Console.log("## {} {}", size, capacity);
- }
+ ThreadUtil.execute(() -> {
+ cache.put("key1"+ index, "value1");
+ cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
+
+ int size = cache.size();
+ int capacity = cache.capacity();
+ if(size > capacity) {
+ Console.log("{} {}", size, capacity);
+ }
+ ThreadUtil.sleep(1000);
+ size = cache.size();
+ capacity = cache.capacity();
+ if(size > capacity) {
+ Console.log("## {} {}", size, capacity);
}
});
}
@@ -91,10 +77,8 @@ public class CacheConcurrentTest {
}
private void show(Cache cache) {
- Iterator> its = cache.iterator();
- while (its.hasNext()) {
- Object tt = its.next();
+ for (Object tt : cache) {
Console.log(tt);
}
}
diff --git a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java
index 12ccbe74c..c1832ce28 100644
--- a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java
+++ b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java
@@ -58,7 +58,7 @@ public class CacheTest {
//使用时间推近
lruCache.get("key1");
lruCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
-
+
String value1 = lruCache.get("key1");
Assert.assertNotNull(value1);
//由于缓存容量只有3,当加入第四个元素的时候,根据LRU规则,最少使用的将被移除(2被移除)
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java
index cdcdc0efc..0c2cff152 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java
@@ -1,13 +1,11 @@
package cn.hutool.core.lang;
+import cn.hutool.core.lang.func.Func0;
+
import java.io.Serializable;
import java.util.Map;
import java.util.WeakHashMap;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
-
-import cn.hutool.core.lang.func.Func0;
+import java.util.concurrent.locks.StampedLock;
/**
* 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理
@@ -21,10 +19,9 @@ public class SimpleCache implements Serializable{
/** 池 */
private final Map cache = new WeakHashMap<>();
-
- private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
- private final ReadLock readLock = cacheLock.readLock();
- private final WriteLock writeLock = cacheLock.writeLock();
+
+ // 乐观读写锁
+ private final StampedLock lock = new StampedLock ();
/**
* 从缓存池中查找值
@@ -33,15 +30,12 @@ public class SimpleCache implements Serializable{
* @return 值
*/
public V get(K key) {
- // 尝试读取缓存
- readLock.lock();
- V value;
+ long stamp = lock.readLock();
try {
- value = cache.get(key);
+ return cache.get(key);
} finally {
- readLock.unlock();
+ lock.unlockRead(stamp);
}
- return value;
}
/**
@@ -52,12 +46,25 @@ public class SimpleCache implements Serializable{
* @return 值对象
*/
public V get(K key, Func0 supplier) {
- V v = get(key);
- if (null == v && null != supplier) {
- writeLock.lock();
- try {
- // 双重检查锁
+ if(null == supplier){
+ return get(key);
+ }
+
+ long stamp = lock.readLock();
+ V v;
+ try{
+ v = cache.get(key);
+ if (null == v) {
+ // 尝试转换独占写锁
+ long writeStamp = lock.tryConvertToWriteLock(stamp);
+ if(0 == writeStamp){
+ // 转换失败,手动更新为写锁
+ lock.unlockRead(stamp);
+ writeStamp = lock.writeLock();
+ }
+ stamp = writeStamp;
v = cache.get(key);
+ // 双重检查,防止在竞争锁的过程中已经有其它线程写入
if(null == v) {
try {
v = supplier.call();
@@ -66,9 +73,9 @@ public class SimpleCache implements Serializable{
}
cache.put(key, v);
}
- } finally {
- writeLock.unlock();
}
+ } finally {
+ lock.unlock(stamp);
}
return v;
}
@@ -80,11 +87,12 @@ public class SimpleCache implements Serializable{
* @return 值
*/
public V put(K key, V value){
- writeLock.lock();
+ // 独占写锁
+ final long stamp = lock.writeLock();
try {
cache.put(key, value);
} finally {
- writeLock.unlock();
+ lock.unlockWrite(stamp);
}
return value;
}
@@ -96,11 +104,12 @@ public class SimpleCache implements Serializable{
* @return 移除的值
*/
public V remove(K key) {
- writeLock.lock();
+ // 独占写锁
+ final long stamp = lock.writeLock();
try {
return cache.remove(key);
} finally {
- writeLock.unlock();
+ lock.unlockWrite(stamp);
}
}
@@ -108,11 +117,12 @@ public class SimpleCache implements Serializable{
* 清空缓存池
*/
public void clear() {
- writeLock.lock();
+ // 独占写锁
+ final long stamp = lock.writeLock();
try {
this.cache.clear();
} finally {
- writeLock.unlock();
+ lock.unlockWrite(stamp);
}
}
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java
new file mode 100644
index 000000000..051134330
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java
@@ -0,0 +1,43 @@
+package cn.hutool.core.thread.lock;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.StampedLock;
+
+/**
+ * 锁相关工具
+ *
+ * @author looly
+ * @since 5.2.5
+ */
+public class LockUtil {
+
+ private static NoLock NO_LOCK = new NoLock();
+
+ /**
+ * 创建{@link StampedLock}锁
+ *
+ * @return {@link StampedLock}锁
+ */
+ public static StampedLock createStampLock() {
+ return new StampedLock();
+ }
+
+ /**
+ * 创建{@link ReentrantReadWriteLock}锁
+ *
+ * @param fair 是否公平锁
+ * @return {@link ReentrantReadWriteLock}锁
+ */
+ public static ReentrantReadWriteLock createReadWriteLock(boolean fair) {
+ return new ReentrantReadWriteLock(fair);
+ }
+
+ /**
+ * 获取单例的无锁对象
+ *
+ * @return {@link NoLock}
+ */
+ public static NoLock getNoLock(){
+ return NO_LOCK;
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java
index 12032abb0..912d21359 100644
--- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java
+++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java
@@ -17,7 +17,7 @@ public class NoLock implements Lock{
}
@Override
- public void lockInterruptibly() throws InterruptedException {
+ public void lockInterruptibly() {
}
@Override
@@ -25,8 +25,9 @@ public class NoLock implements Lock{
return true;
}
+ @SuppressWarnings("NullableProblems")
@Override
- public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
+ public boolean tryLock(long time, TimeUnit unit) {
return true;
}
@@ -34,6 +35,7 @@ public class NoLock implements Lock{
public void unlock() {
}
+ @SuppressWarnings("NullableProblems")
@Override
public Condition newCondition() {
return null;
diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java
new file mode 100644
index 000000000..bac03887b
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java
@@ -0,0 +1,46 @@
+package cn.hutool.core.lang;
+
+import cn.hutool.core.thread.ThreadUtil;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SimpleCacheTest {
+
+ @Before
+ public void putTest(){
+ final SimpleCache cache = new SimpleCache<>();
+ ThreadUtil.execute(()->cache.put("key1", "value1"));
+ ThreadUtil.execute(()->cache.get("key1"));
+ ThreadUtil.execute(()->cache.put("key2", "value2"));
+ ThreadUtil.execute(()->cache.get("key2"));
+ ThreadUtil.execute(()->cache.put("key3", "value3"));
+ ThreadUtil.execute(()->cache.get("key3"));
+ ThreadUtil.execute(()->cache.put("key4", "value4"));
+ ThreadUtil.execute(()->cache.get("key4"));
+ ThreadUtil.execute(()->cache.get("key5", ()->"value5"));
+
+ cache.get("key5", ()->"value5");
+ }
+
+ @Test
+ public void getTest(){
+ final SimpleCache cache = new SimpleCache<>();
+ cache.put("key1", "value1");
+ cache.get("key1");
+ cache.put("key2", "value2");
+ cache.get("key2");
+ cache.put("key3", "value3");
+ cache.get("key3");
+ cache.put("key4", "value4");
+ cache.get("key4");
+ cache.get("key5", ()->"value5");
+
+ Assert.assertEquals("value1", cache.get("key1"));
+ Assert.assertEquals("value2", cache.get("key2"));
+ Assert.assertEquals("value3", cache.get("key3"));
+ Assert.assertEquals("value4", cache.get("key4"));
+ Assert.assertEquals("value5", cache.get("key5"));
+ Assert.assertEquals("value6", cache.get("key6", ()-> "value6"));
+ }
+}
diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml
index 8125484cc..f3424433c 100644
--- a/hutool-script/pom.xml
+++ b/hutool-script/pom.xml
@@ -1,9 +1,10 @@
-
+
4.0.0
jar
-
+
cn.hutool
hutool-parent
@@ -13,12 +14,37 @@
hutool-script
${project.artifactId}
Hutool 脚本执行封装
-
+
+
+ 2.7.0
+ 3.0.1
+ 3.0.2
+
+
cn.hutool
hutool-core
${project.parent.version}
+
+ org.python
+ jython
+ ${jython.version}
+ provided
+
+
+ org.luaj
+ luaj-jse
+ ${luaj.version}
+ provided
+
+
+ org.codehaus.groovy
+ groovy-all
+ ${groovy.version}
+ pom
+ provided
+
diff --git a/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java b/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java
index d6f0b9d46..c62283d0b 100644
--- a/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java
+++ b/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java
@@ -1,65 +1,64 @@
package cn.hutool.script;
-import java.io.Reader;
-
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngineFactory;
-import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
+import java.io.Reader;
/**
* Javascript引擎类
- * @author Looly
*
+ * @author Looly
*/
-public class JavaScriptEngine extends FullSupportScriptEngine{
-
+public class JavaScriptEngine extends FullSupportScriptEngine {
+
public JavaScriptEngine() {
- super(new ScriptEngineManager().getEngineByName("javascript"));
+ super(ScriptUtil.getJsEngine());
}
-
+
/**
* 引擎实例
+ *
* @return 引擎实例
*/
- public static JavaScriptEngine instance(){
+ public static JavaScriptEngine instance() {
return new JavaScriptEngine();
}
//----------------------------------------------------------------------------------------------- Invocable
@Override
public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException {
- return ((Invocable)engine).invokeMethod(thiz, name, args);
+ return ((Invocable) engine).invokeMethod(thiz, name, args);
}
@Override
public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
- return ((Invocable)engine).invokeFunction(name, args);
+ return ((Invocable) engine).invokeFunction(name, args);
}
@Override
public T getInterface(Class clasz) {
- return ((Invocable)engine).getInterface(clasz);
+ return ((Invocable) engine).getInterface(clasz);
}
@Override
public T getInterface(Object thiz, Class clasz) {
- return ((Invocable)engine).getInterface(thiz, clasz);
+ return ((Invocable) engine).getInterface(thiz, clasz);
}
//----------------------------------------------------------------------------------------------- Compilable
@Override
public CompiledScript compile(String script) throws ScriptException {
- return ((Compilable)engine).compile(script);
+ return ((Compilable) engine).compile(script);
}
@Override
public CompiledScript compile(Reader script) throws ScriptException {
- return ((Compilable)engine).compile(script);
+ return ((Compilable) engine).compile(script);
}
//----------------------------------------------------------------------------------------------- ScriptEngine
diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java
index fe530ee02..d252a9099 100644
--- a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java
+++ b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java
@@ -1,10 +1,10 @@
package cn.hutool.script;
-import javax.script.ScriptException;
-
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
+import javax.script.ScriptException;
+
/**
* 脚本运行时异常
*
@@ -50,7 +50,6 @@ public class ScriptRuntimeException extends RuntimeException {
super(message);
this.fileName = fileName;
this.lineNumber = lineNumber;
- this.columnNumber = -1;
}
/**
diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java
index 559a58ea9..991c425c6 100644
--- a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java
+++ b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java
@@ -1,5 +1,8 @@
package cn.hutool.script;
+import cn.hutool.core.lang.SimpleCache;
+import cn.hutool.core.util.StrUtil;
+
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
@@ -10,34 +13,92 @@ import javax.script.ScriptException;
/**
* 脚本工具类
- *
- * @author Looly
*
+ * @author Looly
*/
public class ScriptUtil {
+ private static final ScriptEngineManager manager = new ScriptEngineManager();
+ private static SimpleCache cache = new SimpleCache<>();
+
/**
* 获得 {@link ScriptEngine} 实例
- *
- * @param name 脚本名称
+ *
+ * @param nameOrExtOrMime 脚本名称
* @return {@link ScriptEngine} 实例
*/
- public static ScriptEngine getScript(String name) {
- return new ScriptEngineManager().getEngineByName(name);
+ public static ScriptEngine getScript(String nameOrExtOrMime) {
+ return cache.get(nameOrExtOrMime, ()->{
+ ScriptEngine engine = manager.getEngineByName(nameOrExtOrMime);
+ if (null == engine) {
+ engine = manager.getEngineByExtension(nameOrExtOrMime);
+ }
+ if (null == engine) {
+ engine = manager.getEngineByMimeType(nameOrExtOrMime);
+ }
+ if (null == engine) {
+ throw new NullPointerException(StrUtil.format("Script for [{}] not support !", nameOrExtOrMime));
+ }
+ return engine;
+ });
}
/**
* 获得 Javascript引擎 {@link JavaScriptEngine}
- *
+ *
* @return {@link JavaScriptEngine}
*/
public static JavaScriptEngine getJavaScriptEngine() {
return new JavaScriptEngine();
}
-
+
/**
- * 编译脚本
- *
+ * 获得 JavaScript引擎
+ *
+ * @return Python引擎
+ * @since 5.2.5
+ */
+ public static ScriptEngine getJsEngine() {
+ return getScript("js");
+ }
+
+ /**
+ * 获得 Python引擎
+ * 需要引入org.python:jython
+ *
+ * @return Python引擎
+ * @since 5.2.5
+ */
+ public static ScriptEngine getPythonEngine() {
+ System.setProperty("python.import.site", "false");
+ return getScript("python");
+ }
+
+ /**
+ * 获得Lua引擎
+ * 需要引入org.luaj:luaj-jse
+ *
+ * @return Lua引擎
+ * @since 5.2.5
+ */
+ public static ScriptEngine getLuaEngine() {
+ return getScript("lua");
+ }
+
+ /**
+ * 获得Groovy引擎
+ * 需要引入org.codehaus.groovy:groovy-all
+ *
+ * @return Groovy引擎
+ * @since 5.2.5
+ */
+ public static ScriptEngine getGroovyEngine() {
+ return getScript("groovy");
+ }
+
+ /**
+ * 执行脚本
+ *
* @param script 脚本内容
* @return {@link CompiledScript}
* @throws ScriptRuntimeException 脚本异常
@@ -45,16 +106,16 @@ public class ScriptUtil {
*/
public static Object eval(String script) throws ScriptRuntimeException {
try {
- return compile(script).eval();
+ return getJsEngine().eval(script);
} catch (ScriptException e) {
throw new ScriptRuntimeException(e);
}
}
-
+
/**
- * 编译脚本
- *
- * @param script 脚本内容
+ * 执行脚本
+ *
+ * @param script 脚本内容
* @param context 脚本上下文
* @return {@link CompiledScript}
* @throws ScriptRuntimeException 脚本异常
@@ -62,16 +123,16 @@ public class ScriptUtil {
*/
public static Object eval(String script, ScriptContext context) throws ScriptRuntimeException {
try {
- return compile(script).eval(context);
+ return getJsEngine().eval(script, context);
} catch (ScriptException e) {
throw new ScriptRuntimeException(e);
}
}
-
+
/**
- * 编译脚本
- *
- * @param script 脚本内容
+ * 执行脚本
+ *
+ * @param script 脚本内容
* @param bindings 绑定的参数
* @return {@link CompiledScript}
* @throws ScriptRuntimeException 脚本异常
@@ -79,7 +140,7 @@ public class ScriptUtil {
*/
public static Object eval(String script, Bindings bindings) throws ScriptRuntimeException {
try {
- return compile(script).eval(bindings);
+ return getJsEngine().eval(script, bindings);
} catch (ScriptException e) {
throw new ScriptRuntimeException(e);
}
@@ -87,7 +148,7 @@ public class ScriptUtil {
/**
* 编译脚本
- *
+ *
* @param script 脚本内容
* @return {@link CompiledScript}
* @throws ScriptRuntimeException 脚本异常
@@ -95,7 +156,7 @@ public class ScriptUtil {
*/
public static CompiledScript compile(String script) throws ScriptRuntimeException {
try {
- return compile(getJavaScriptEngine(), script);
+ return compile(getJsEngine(), script);
} catch (ScriptException e) {
throw new ScriptRuntimeException(e);
}
@@ -103,7 +164,7 @@ public class ScriptUtil {
/**
* 编译脚本
- *
+ *
* @param engine 引擎
* @param script 脚本内容
* @return {@link CompiledScript}
@@ -111,7 +172,7 @@ public class ScriptUtil {
*/
public static CompiledScript compile(ScriptEngine engine, String script) throws ScriptException {
if (engine instanceof Compilable) {
- Compilable compEngine = (Compilable) engine;
+ final Compilable compEngine = (Compilable) engine;
return compEngine.compile(script);
}
return null;
diff --git a/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java b/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java
index fda465b73..6fcbf57c3 100644
--- a/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java
+++ b/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java
@@ -1,12 +1,12 @@
package cn.hutool.script.test;
-import javax.script.CompiledScript;
-import javax.script.ScriptException;
-
-import org.junit.Test;
-
import cn.hutool.script.ScriptRuntimeException;
import cn.hutool.script.ScriptUtil;
+import org.junit.Test;
+
+import javax.script.CompiledScript;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
/**
* 脚本单元测试类
@@ -30,4 +30,22 @@ public class ScriptUtilTest {
public void evalTest() {
ScriptUtil.eval("print('Script test!');");
}
+
+ @Test
+ public void pythonTest() throws ScriptException {
+ final ScriptEngine pythonEngine = ScriptUtil.getPythonEngine();
+ pythonEngine.eval("print('Hello Python')");
+ }
+
+ @Test
+ public void luaTest() throws ScriptException {
+ final ScriptEngine engine = ScriptUtil.getLuaEngine();
+ engine.eval("print('Hello Lua')");
+ }
+
+ @Test
+ public void groovyTest() throws ScriptException {
+ final ScriptEngine engine = ScriptUtil.getGroovyEngine();
+ engine.eval("println 'Hello Groovy'");
+ }
}