add LockUtil and fix Cache

This commit is contained in:
Looly
2020-03-26 23:46:36 +08:00
parent 54760ea0a2
commit 0d7ef8f092
13 changed files with 379 additions and 218 deletions

View File

@@ -16,6 +16,7 @@
* 【core 】 CollUtil.newHashSet重载歧义更换为set方法 * 【core 】 CollUtil.newHashSet重载歧义更换为set方法
* 【core 】 增加ListUtil增加Hash32、Hash64、Hash128接口 * 【core 】 增加ListUtil增加Hash32、Hash64、Hash128接口
* 【crypto 】 BCUtil增加readPemPrivateKey和readPemPublicKey方法 * 【crypto 】 BCUtil增加readPemPrivateKey和readPemPublicKey方法
* 【cache 】 替换读写锁为StampedLock增加LockUtil
### Bug修复 ### Bug修复
* 【core 】 修复NumberWordFormatter拼写错误issue#799@Github * 【core 】 修复NumberWordFormatter拼写错误issue#799@Github

View File

@@ -6,9 +6,7 @@ import cn.hutool.core.lang.func.Func0;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/** /**
* 超时和限制大小的缓存的默认实现<br> * 超时和限制大小的缓存的默认实现<br>
@@ -18,31 +16,38 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
* <li>实现 <code>prune</code> 策略</li> * <li>实现 <code>prune</code> 策略</li>
* </ul> * </ul>
* *
* @author Looly,jodd
*
* @param <K> 键类型 * @param <K> 键类型
* @param <V> 值类型 * @param <V> 值类型
* @author Looly, jodd
*/ */
public abstract class AbstractCache<K, V> implements Cache<K, V> { public abstract class AbstractCache<K, V> implements Cache<K, V> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
protected Map<K, CacheObj<K, V>> cacheMap; protected Map<K, CacheObj<K, V>> cacheMap;
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); private final StampedLock lock = new StampedLock();
private final ReadLock readLock = cacheLock.readLock();
private final WriteLock writeLock = cacheLock.writeLock();
/** 返回缓存容量,<code>0</code>表示无大小限制 */ /**
* 返回缓存容量,<code>0</code>表示无大小限制
*/
protected int capacity; protected int capacity;
/** 缓存失效时长, <code>0</code> 表示无限制,单位毫秒 */ /**
* 缓存失效时长, <code>0</code> 表示无限制,单位毫秒
*/
protected long timeout; protected long timeout;
/** 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 */ /**
* 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。
*/
protected boolean existCustomTimeout; protected boolean existCustomTimeout;
/** 命中数 */ /**
* 命中数
*/
protected int hitCount; protected int hitCount;
/** 丢失数 */ /**
* 丢失数
*/
protected int missCount; protected int missCount;
// ---------------------------------------------------------------- put start // ---------------------------------------------------------------- put start
@@ -53,12 +58,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
@Override @Override
public void put(K key, V object, long timeout) { public void put(K key, V object, long timeout) {
writeLock.lock(); final long stamp = lock.writeLock();
try { try {
putWithoutLock(key, object, timeout); putWithoutLock(key, object, timeout);
} finally { } finally {
writeLock.unlock(); lock.unlockWrite(stamp);
} }
} }
@@ -85,8 +89,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
// ---------------------------------------------------------------- get start // ---------------------------------------------------------------- get start
@Override @Override
public boolean containsKey(K key) { public boolean containsKey(K key) {
readLock.lock(); final long stamp = lock.readLock();
try { try {
// 不存在或已移除 // 不存在或已移除
final CacheObj<K, V> co = cacheMap.get(key); final CacheObj<K, V> co = cacheMap.get(key);
@@ -99,7 +102,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
return true; return true;
} }
} finally { } finally {
readLock.unlock(); lock.unlockRead(stamp);
} }
// 过期 // 过期
@@ -111,24 +114,14 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
* @return 命中数 * @return 命中数
*/ */
public int getHitCount() { public int getHitCount() {
this.readLock.lock();
try {
return hitCount; return hitCount;
} finally {
this.readLock.unlock();
}
} }
/** /**
* @return 丢失数 * @return 丢失数
*/ */
public int getMissCount() { public int getMissCount() {
this.readLock.lock();
try {
return missCount; return missCount;
} finally {
this.readLock.unlock();
}
} }
@Override @Override
@@ -140,11 +133,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
public V get(K key, Func0<V> supplier) { public V get(K key, Func0<V> supplier) {
V v = get(key); V v = get(key);
if (null == v && null != supplier) { if (null == v && null != supplier) {
writeLock.lock(); final long stamp = lock.writeLock();
try { try {
// 双重检查锁 // 双重检查锁
final CacheObj<K, V> co = cacheMap.get(key); final CacheObj<K, V> co = cacheMap.get(key);
if(null == co || co.isExpired() || null == co.getValue()) { if (null == co || co.isExpired()) {
try { try {
v = supplier.call(); v = supplier.call();
} catch (Exception e) { } catch (Exception e) {
@@ -155,7 +148,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
v = co.get(true); v = co.get(true);
} }
} finally { } finally {
writeLock.unlock(); lock.unlockWrite(stamp);
} }
} }
return v; return v;
@@ -163,23 +156,25 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
@Override @Override
public V get(K key, boolean isUpdateLastAccess) { public V get(K key, boolean isUpdateLastAccess) {
readLock.lock(); // 尝试读取缓存,使用乐观读锁
long stamp = lock.readLock();
try { try {
// 不存在或已移除 // 不存在或已移除
final CacheObj<K, V> co = cacheMap.get(key); final CacheObj<K, V> co = cacheMap.get(key);
if (co == null) { if (null == co) {
missCount++; missCount++;
return null; return null;
} }
if (false == co.isExpired()) { if (co.isExpired()) {
missCount++;
} else{
// 命中 // 命中
hitCount++; hitCount++;
return co.get(isUpdateLastAccess); return co.get(isUpdateLastAccess);
} }
} finally { } finally {
readLock.unlock(); lock.unlock(stamp);
} }
// 过期 // 过期
@@ -199,18 +194,20 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
@Override @Override
public Iterator<CacheObj<K, V>> cacheObjIterator() { public Iterator<CacheObj<K, V>> cacheObjIterator() {
CopiedIter<CacheObj<K, V>> copiedIterator; CopiedIter<CacheObj<K, V>> copiedIterator;
readLock.lock(); final long stamp = lock.readLock();
try { try {
copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator()); copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
} finally { } finally {
readLock.unlock(); lock.unlockRead(stamp);
} }
return new CacheObjIterator<>(copiedIterator); return new CacheObjIterator<>(copiedIterator);
} }
// ---------------------------------------------------------------- prune start // ---------------------------------------------------------------- prune start
/** /**
* 清理实现 * 清理实现<br>
* 子类实现此方法时无需加锁
* *
* @return 清理数 * @return 清理数
*/ */
@@ -218,11 +215,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
@Override @Override
public final int prune() { public final int prune() {
writeLock.lock(); final long stamp = lock.writeLock();
try { try {
return pruneCache(); return pruneCache();
} finally { } finally {
writeLock.unlock(); lock.unlockWrite(stamp);
} }
} }
// ---------------------------------------------------------------- prune end // ---------------------------------------------------------------- prune end
@@ -248,22 +245,12 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
* @return 过期对象清理是否可用,内部使用 * @return 过期对象清理是否可用,内部使用
*/ */
protected boolean isPruneExpiredActive() { protected boolean isPruneExpiredActive() {
this.readLock.lock();
try {
return (timeout != 0) || existCustomTimeout; return (timeout != 0) || existCustomTimeout;
} finally {
this.readLock.unlock();
}
} }
@Override @Override
public boolean isFull() { public boolean isFull() {
this.readLock.lock();
try {
return (capacity > 0) && (cacheMap.size() >= capacity); return (capacity > 0) && (cacheMap.size() >= capacity);
} finally {
this.readLock.unlock();
}
} }
@Override @Override
@@ -273,42 +260,27 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
@Override @Override
public void clear() { public void clear() {
writeLock.lock(); final long stamp = lock.writeLock();
try { try {
cacheMap.clear(); cacheMap.clear();
} finally { } finally {
writeLock.unlock(); lock.unlockWrite(stamp);
} }
} }
@Override @Override
public int size() { public int size() {
this.readLock.lock();
try {
return cacheMap.size(); return cacheMap.size();
} finally {
this.readLock.unlock();
}
} }
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
this.readLock.lock();
try {
return cacheMap.isEmpty(); return cacheMap.isEmpty();
} finally {
this.readLock.unlock();
}
} }
@Override @Override
public String toString() { public String toString() {
this.readLock.lock();
try {
return this.cacheMap.toString(); return this.cacheMap.toString();
} finally {
this.readLock.unlock();
}
} }
// ---------------------------------------------------------------- common end // ---------------------------------------------------------------- common end
@@ -329,12 +301,12 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
* @param withMissCount 是否计数丢失数 * @param withMissCount 是否计数丢失数
*/ */
private void remove(K key, boolean withMissCount) { private void remove(K key, boolean withMissCount) {
writeLock.lock(); final long stamp = lock.writeLock();
CacheObj<K, V> co; CacheObj<K, V> co;
try { try {
co = removeWithoutLock(key, withMissCount); co = removeWithoutLock(key, withMissCount);
} finally { } finally {
writeLock.unlock(); lock.unlockWrite(stamp);
} }
if (null != co) { if (null != co) {
onRemove(co.key, co.obj); onRemove(co.key, co.obj);

View File

@@ -1,15 +1,12 @@
package cn.hutool.cache.test; 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.Cache;
import cn.hutool.cache.impl.FIFOCache; import cn.hutool.cache.impl.FIFOCache;
import cn.hutool.cache.impl.LRUCache; import cn.hutool.cache.impl.LRUCache;
import cn.hutool.core.lang.Console; import cn.hutool.core.lang.Console;
import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.thread.ThreadUtil;
import org.junit.Ignore;
import org.junit.Test;
/** /**
* 缓存单元测试 * 缓存单元测试
@@ -28,9 +25,7 @@ public class CacheConcurrentTest {
// 由于缓存容量只有3当加入第四个元素的时候根据FIFO规则最先放入的对象将被移除 // 由于缓存容量只有3当加入第四个元素的时候根据FIFO规则最先放入的对象将被移除
for (int i = 0; i < threadCount; i++) { for (int i = 0; i < threadCount; i++) {
ThreadUtil.execute(new Runnable() { ThreadUtil.execute(() -> {
@Override
public void run() {
cache.put("key1", "value1", System.currentTimeMillis() * 3); cache.put("key1", "value1", System.currentTimeMillis() * 3);
cache.put("key2", "value2", System.currentTimeMillis() * 3); cache.put("key2", "value2", System.currentTimeMillis() * 3);
cache.put("key3", "value3", System.currentTimeMillis() * 3); cache.put("key3", "value3", System.currentTimeMillis() * 3);
@@ -41,17 +36,11 @@ public class CacheConcurrentTest {
cache.put("key7", "value7", System.currentTimeMillis() * 3); cache.put("key7", "value7", System.currentTimeMillis() * 3);
cache.put("key8", "value8", System.currentTimeMillis() * 3); cache.put("key8", "value8", System.currentTimeMillis() * 3);
Console.log("put all"); Console.log("put all");
}
}); });
} }
for (int i = 0; i < threadCount; i++) { for (int i = 0; i < threadCount; i++) {
ThreadUtil.execute(new Runnable() { ThreadUtil.execute(() -> show(cache));
@Override
public void run() {
show(cache);
}
});
} }
System.out.println("=============================="); System.out.println("==============================");
@@ -66,9 +55,7 @@ public class CacheConcurrentTest {
for (int i = 0; i < threadCount; i++) { for (int i = 0; i < threadCount; i++) {
final int index = i; final int index = i;
ThreadUtil.execute(new Runnable() { ThreadUtil.execute(() -> {
@Override
public void run() {
cache.put("key1"+ index, "value1"); cache.put("key1"+ index, "value1");
cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3); cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
@@ -83,7 +70,6 @@ public class CacheConcurrentTest {
if(size > capacity) { if(size > capacity) {
Console.log("## {} {}", size, capacity); Console.log("## {} {}", size, capacity);
} }
}
}); });
} }
@@ -91,10 +77,8 @@ public class CacheConcurrentTest {
} }
private void show(Cache<String, String> cache) { private void show(Cache<String, String> cache) {
Iterator<?> its = cache.iterator();
while (its.hasNext()) { for (Object tt : cache) {
Object tt = its.next();
Console.log(tt); Console.log(tt);
} }
} }

View File

@@ -1,13 +1,11 @@
package cn.hutool.core.lang; package cn.hutool.core.lang;
import cn.hutool.core.lang.func.Func0;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import cn.hutool.core.lang.func.Func0;
/** /**
* 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理 * 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理
@@ -22,9 +20,8 @@ public class SimpleCache<K, V> implements Serializable{
/** 池 */ /** 池 */
private final Map<K, V> cache = new WeakHashMap<>(); private final Map<K, V> cache = new WeakHashMap<>();
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); // 乐观读写锁
private final ReadLock readLock = cacheLock.readLock(); private final StampedLock lock = new StampedLock ();
private final WriteLock writeLock = cacheLock.writeLock();
/** /**
* 从缓存池中查找值 * 从缓存池中查找值
@@ -33,15 +30,12 @@ public class SimpleCache<K, V> implements Serializable{
* @return 值 * @return 值
*/ */
public V get(K key) { public V get(K key) {
// 尝试读取缓存 long stamp = lock.readLock();
readLock.lock();
V value;
try { try {
value = cache.get(key); return cache.get(key);
} finally { } finally {
readLock.unlock(); lock.unlockRead(stamp);
} }
return value;
} }
/** /**
@@ -52,12 +46,25 @@ public class SimpleCache<K, V> implements Serializable{
* @return 值对象 * @return 值对象
*/ */
public V get(K key, Func0<V> supplier) { public V get(K key, Func0<V> supplier) {
V v = get(key); if(null == supplier){
if (null == v && null != supplier) { return get(key);
writeLock.lock(); }
try {
// 双重检查锁 long stamp = lock.readLock();
V v;
try{
v = cache.get(key); 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) { if(null == v) {
try { try {
v = supplier.call(); v = supplier.call();
@@ -66,9 +73,9 @@ public class SimpleCache<K, V> implements Serializable{
} }
cache.put(key, v); cache.put(key, v);
} }
} finally {
writeLock.unlock();
} }
} finally {
lock.unlock(stamp);
} }
return v; return v;
} }
@@ -80,11 +87,12 @@ public class SimpleCache<K, V> implements Serializable{
* @return 值 * @return 值
*/ */
public V put(K key, V value){ public V put(K key, V value){
writeLock.lock(); // 独占写锁
final long stamp = lock.writeLock();
try { try {
cache.put(key, value); cache.put(key, value);
} finally { } finally {
writeLock.unlock(); lock.unlockWrite(stamp);
} }
return value; return value;
} }
@@ -96,11 +104,12 @@ public class SimpleCache<K, V> implements Serializable{
* @return 移除的值 * @return 移除的值
*/ */
public V remove(K key) { public V remove(K key) {
writeLock.lock(); // 独占写锁
final long stamp = lock.writeLock();
try { try {
return cache.remove(key); return cache.remove(key);
} finally { } finally {
writeLock.unlock(); lock.unlockWrite(stamp);
} }
} }
@@ -108,11 +117,12 @@ public class SimpleCache<K, V> implements Serializable{
* 清空缓存池 * 清空缓存池
*/ */
public void clear() { public void clear() {
writeLock.lock(); // 独占写锁
final long stamp = lock.writeLock();
try { try {
this.cache.clear(); this.cache.clear();
} finally { } finally {
writeLock.unlock(); lock.unlockWrite(stamp);
} }
} }
} }

View File

@@ -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;
}
}

View File

@@ -17,7 +17,7 @@ public class NoLock implements Lock{
} }
@Override @Override
public void lockInterruptibly() throws InterruptedException { public void lockInterruptibly() {
} }
@Override @Override
@@ -25,8 +25,9 @@ public class NoLock implements Lock{
return true; return true;
} }
@SuppressWarnings("NullableProblems")
@Override @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { public boolean tryLock(long time, TimeUnit unit) {
return true; return true;
} }
@@ -34,6 +35,7 @@ public class NoLock implements Lock{
public void unlock() { public void unlock() {
} }
@SuppressWarnings("NullableProblems")
@Override @Override
public Condition newCondition() { public Condition newCondition() {
return null; return null;

View File

@@ -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<String, String> 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<String, String> 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"));
}
}

View File

@@ -1,5 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>
@@ -14,11 +15,36 @@
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<description>Hutool 脚本执行封装</description> <description>Hutool 脚本执行封装</description>
<properties>
<jython.version>2.7.0</jython.version>
<luaj.version>3.0.1</luaj.version>
<groovy.version>3.0.2</groovy.version>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId> <artifactId>hutool-core</artifactId>
<version>${project.parent.version}</version> <version>${project.parent.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>${jython.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.luaj</groupId>
<artifactId>luaj-jse</artifactId>
<version>${luaj.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,65 +1,64 @@
package cn.hutool.script; package cn.hutool.script;
import java.io.Reader;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.Compilable; import javax.script.Compilable;
import javax.script.CompiledScript; import javax.script.CompiledScript;
import javax.script.Invocable; import javax.script.Invocable;
import javax.script.ScriptContext; import javax.script.ScriptContext;
import javax.script.ScriptEngineFactory; import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException; import javax.script.ScriptException;
import java.io.Reader;
/** /**
* Javascript引擎类 * Javascript引擎类
* @author Looly
* *
* @author Looly
*/ */
public class JavaScriptEngine extends FullSupportScriptEngine{ public class JavaScriptEngine extends FullSupportScriptEngine {
public JavaScriptEngine() { public JavaScriptEngine() {
super(new ScriptEngineManager().getEngineByName("javascript")); super(ScriptUtil.getJsEngine());
} }
/** /**
* 引擎实例 * 引擎实例
*
* @return 引擎实例 * @return 引擎实例
*/ */
public static JavaScriptEngine instance(){ public static JavaScriptEngine instance() {
return new JavaScriptEngine(); return new JavaScriptEngine();
} }
//----------------------------------------------------------------------------------------------- Invocable //----------------------------------------------------------------------------------------------- Invocable
@Override @Override
public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { 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 @Override
public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
return ((Invocable)engine).invokeFunction(name, args); return ((Invocable) engine).invokeFunction(name, args);
} }
@Override @Override
public <T> T getInterface(Class<T> clasz) { public <T> T getInterface(Class<T> clasz) {
return ((Invocable)engine).getInterface(clasz); return ((Invocable) engine).getInterface(clasz);
} }
@Override @Override
public <T> T getInterface(Object thiz, Class<T> clasz) { public <T> T getInterface(Object thiz, Class<T> clasz) {
return ((Invocable)engine).getInterface(thiz, clasz); return ((Invocable) engine).getInterface(thiz, clasz);
} }
//----------------------------------------------------------------------------------------------- Compilable //----------------------------------------------------------------------------------------------- Compilable
@Override @Override
public CompiledScript compile(String script) throws ScriptException { public CompiledScript compile(String script) throws ScriptException {
return ((Compilable)engine).compile(script); return ((Compilable) engine).compile(script);
} }
@Override @Override
public CompiledScript compile(Reader script) throws ScriptException { public CompiledScript compile(Reader script) throws ScriptException {
return ((Compilable)engine).compile(script); return ((Compilable) engine).compile(script);
} }
//----------------------------------------------------------------------------------------------- ScriptEngine //----------------------------------------------------------------------------------------------- ScriptEngine

View File

@@ -1,10 +1,10 @@
package cn.hutool.script; package cn.hutool.script;
import javax.script.ScriptException;
import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import javax.script.ScriptException;
/** /**
* 脚本运行时异常 * 脚本运行时异常
* *
@@ -50,7 +50,6 @@ public class ScriptRuntimeException extends RuntimeException {
super(message); super(message);
this.fileName = fileName; this.fileName = fileName;
this.lineNumber = lineNumber; this.lineNumber = lineNumber;
this.columnNumber = -1;
} }
/** /**

View File

@@ -1,5 +1,8 @@
package cn.hutool.script; package cn.hutool.script;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.util.StrUtil;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.Compilable; import javax.script.Compilable;
import javax.script.CompiledScript; import javax.script.CompiledScript;
@@ -12,18 +15,32 @@ import javax.script.ScriptException;
* 脚本工具类 * 脚本工具类
* *
* @author Looly * @author Looly
*
*/ */
public class ScriptUtil { public class ScriptUtil {
private static final ScriptEngineManager manager = new ScriptEngineManager();
private static SimpleCache<String, ScriptEngine> cache = new SimpleCache<>();
/** /**
* 获得 {@link ScriptEngine} 实例 * 获得 {@link ScriptEngine} 实例
* *
* @param name 脚本名称 * @param nameOrExtOrMime 脚本名称
* @return {@link ScriptEngine} 实例 * @return {@link ScriptEngine} 实例
*/ */
public static ScriptEngine getScript(String name) { public static ScriptEngine getScript(String nameOrExtOrMime) {
return new ScriptEngineManager().getEngineByName(name); 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;
});
} }
/** /**
@@ -36,7 +53,51 @@ public class ScriptUtil {
} }
/** /**
* 编译脚本 * 获得 JavaScript引擎
*
* @return Python引擎
* @since 5.2.5
*/
public static ScriptEngine getJsEngine() {
return getScript("js");
}
/**
* 获得 Python引擎<br>
* 需要引入org.python:jython
*
* @return Python引擎
* @since 5.2.5
*/
public static ScriptEngine getPythonEngine() {
System.setProperty("python.import.site", "false");
return getScript("python");
}
/**
* 获得Lua引擎<br>
* 需要引入org.luaj:luaj-jse
*
* @return Lua引擎
* @since 5.2.5
*/
public static ScriptEngine getLuaEngine() {
return getScript("lua");
}
/**
* 获得Groovy引擎<br>
* 需要引入org.codehaus.groovy:groovy-all
*
* @return Groovy引擎
* @since 5.2.5
*/
public static ScriptEngine getGroovyEngine() {
return getScript("groovy");
}
/**
* 执行脚本
* *
* @param script 脚本内容 * @param script 脚本内容
* @return {@link CompiledScript} * @return {@link CompiledScript}
@@ -45,14 +106,14 @@ public class ScriptUtil {
*/ */
public static Object eval(String script) throws ScriptRuntimeException { public static Object eval(String script) throws ScriptRuntimeException {
try { try {
return compile(script).eval(); return getJsEngine().eval(script);
} catch (ScriptException e) { } catch (ScriptException e) {
throw new ScriptRuntimeException(e); throw new ScriptRuntimeException(e);
} }
} }
/** /**
* 编译脚本 * 执行脚本
* *
* @param script 脚本内容 * @param script 脚本内容
* @param context 脚本上下文 * @param context 脚本上下文
@@ -62,14 +123,14 @@ public class ScriptUtil {
*/ */
public static Object eval(String script, ScriptContext context) throws ScriptRuntimeException { public static Object eval(String script, ScriptContext context) throws ScriptRuntimeException {
try { try {
return compile(script).eval(context); return getJsEngine().eval(script, context);
} catch (ScriptException e) { } catch (ScriptException e) {
throw new ScriptRuntimeException(e); throw new ScriptRuntimeException(e);
} }
} }
/** /**
* 编译脚本 * 执行脚本
* *
* @param script 脚本内容 * @param script 脚本内容
* @param bindings 绑定的参数 * @param bindings 绑定的参数
@@ -79,7 +140,7 @@ public class ScriptUtil {
*/ */
public static Object eval(String script, Bindings bindings) throws ScriptRuntimeException { public static Object eval(String script, Bindings bindings) throws ScriptRuntimeException {
try { try {
return compile(script).eval(bindings); return getJsEngine().eval(script, bindings);
} catch (ScriptException e) { } catch (ScriptException e) {
throw new ScriptRuntimeException(e); throw new ScriptRuntimeException(e);
} }
@@ -95,7 +156,7 @@ public class ScriptUtil {
*/ */
public static CompiledScript compile(String script) throws ScriptRuntimeException { public static CompiledScript compile(String script) throws ScriptRuntimeException {
try { try {
return compile(getJavaScriptEngine(), script); return compile(getJsEngine(), script);
} catch (ScriptException e) { } catch (ScriptException e) {
throw new ScriptRuntimeException(e); throw new ScriptRuntimeException(e);
} }
@@ -111,7 +172,7 @@ public class ScriptUtil {
*/ */
public static CompiledScript compile(ScriptEngine engine, String script) throws ScriptException { public static CompiledScript compile(ScriptEngine engine, String script) throws ScriptException {
if (engine instanceof Compilable) { if (engine instanceof Compilable) {
Compilable compEngine = (Compilable) engine; final Compilable compEngine = (Compilable) engine;
return compEngine.compile(script); return compEngine.compile(script);
} }
return null; return null;

View File

@@ -1,12 +1,12 @@
package cn.hutool.script.test; 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.ScriptRuntimeException;
import cn.hutool.script.ScriptUtil; 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() { public void evalTest() {
ScriptUtil.eval("print('Script test!');"); 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'");
}
} }