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'"); + } }