diff --git a/src/org/redkale/boot/Application.java b/src/org/redkale/boot/Application.java index f9b8c4c62..45347d1a6 100644 --- a/src/org/redkale/boot/Application.java +++ b/src/org/redkale/boot/Application.java @@ -609,10 +609,19 @@ public final class Application { runServers(timecd, watchs); //必须在所有服务都启动后再启动WATCH服务 timecd.await(); if (!singletonrun) signalHandle(); + if (!singletonrun) clearPersistData(); logger.info(this.getClass().getSimpleName() + " started in " + (System.currentTimeMillis() - startTime) + " ms\r\n"); if (!singletonrun) this.serversLatch.await(); } + private void clearPersistData() { + File cachedir = new File(home, "cache"); + if (!cachedir.isDirectory()) return; + for (File file : cachedir.listFiles()) { + if (file.getName().startsWith("persist-")) file.delete(); + } + } + private void signalHandle() { //http://www.comptechdoc.org/os/linux/programming/linux_pgsignals.html String[] sigs = new String[]{"HUP", "TERM", "INT", "QUIT", "KILL", "TSTP", "USR1", "USR2", "STOP"}; diff --git a/src/org/redkale/boot/NodeServer.java b/src/org/redkale/boot/NodeServer.java index 984b21b33..87f7676f8 100644 --- a/src/org/redkale/boot/NodeServer.java +++ b/src/org/redkale/boot/NodeServer.java @@ -19,6 +19,7 @@ import javax.annotation.*; import javax.persistence.Transient; import static org.redkale.boot.Application.*; import org.redkale.boot.ClassFilter.FilterEntry; +import org.redkale.convert.bson.*; import org.redkale.net.Filter; import org.redkale.net.*; import org.redkale.net.http.WebSocketServlet; @@ -408,6 +409,7 @@ public abstract class NodeServer { }); localServices.clear(); localServices.addAll(swlist); + this.loadPersistData(); final List slist = sb == null ? null : new CopyOnWriteArrayList<>(); CountDownLatch clds = new CountDownLatch(localServices.size()); localServices.stream().forEach(y -> { @@ -436,6 +438,117 @@ public abstract class NodeServer { maxClassNameLength = Math.max(maxClassNameLength, Sncp.getResourceType(y).getName().length() + 1); } + @SuppressWarnings("unchecked") + protected void loadPersistData() throws Exception { + File home = application.getHome(); + if (home == null || !home.isDirectory()) return; + File cachedir = new File(home, "cache"); + if (!cachedir.isDirectory()) return; + int port = this.server.getSocketAddress().getPort(); + final String prefix = "persist-" + port + "-"; + final BsonConvert convert = BsonFactory.create().skipAllIgnore(true).getConvert(); + for (final File file : cachedir.listFiles((dir, name) -> name.startsWith(prefix))) { + if (!file.getName().endsWith(".bat")) continue; + String classAndResname = file.getName().substring(prefix.length(), file.getName().length() - 4); //去掉尾部的.bat + int pos = classAndResname.indexOf('-'); + String servtype = pos > 0 ? classAndResname.substring(0, pos) : classAndResname; + String resname = pos > 0 ? classAndResname.substring(pos + 1) : ""; + + FileInputStream in = new FileInputStream(file); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int b; + while ((b = in.read()) != '\n') out.write(b); + final String[] fieldNames = out.toString().split(","); + int timeout = (int) ((System.currentTimeMillis() - file.lastModified()) / 1000); + for (final Service service : this.localServices) { + if (!servtype.equals(Sncp.getResourceType(service).getName())) continue; + if (!resname.equals(Sncp.getResourceName(service))) continue; + for (final String fieldName : fieldNames) { + Field field = null; + Class clzz = service.getClass(); + do { + try { + field = clzz.getDeclaredField(fieldName); + break; + } catch (Exception e) { + } + } while ((clzz = clzz.getSuperclass()) != Object.class); + field.setAccessible(true); + Object val = convert.convertFrom(field.getGenericType(), in); + Persist persist = field.getAnnotation(Persist.class); + if (persist.timeout() == 0 || persist.timeout() >= timeout) { + if (Modifier.isFinal(field.getModifiers())) { + if (Map.class.isAssignableFrom(field.getType())) { + ((Map) field.get(service)).putAll((Map) val); + } else if (Collection.class.isAssignableFrom(field.getType())) { + ((Collection) field.get(service)).addAll((Collection) val); + } + } else { + field.set(service, val); + } + } + if (in.read() != '\n') logger.log(Level.SEVERE, servtype + "'s [" + resname + "] load value error"); + } + } + in.close(); + } + } + + @SuppressWarnings("unchecked") + protected void savePersistData() throws IOException { + File home = application.getHome(); + if (home == null || !home.isDirectory()) return; + File cachedir = new File(home, "cache"); + int port = this.server.getSocketAddress().getPort(); + final String prefix = "persist-" + port + "-"; + final BsonConvert convert = BsonFactory.create().skipAllIgnore(true).getConvert(); + for (final Service service : this.localServices) { + Class clzz = service.getClass(); + final Set fieldNameSet = new HashSet<>(); + final List fields = new ArrayList<>(); + final StringBuilder sb = new StringBuilder(); + do { + for (Field field : clzz.getDeclaredFields()) { + if (field.getAnnotation(Persist.class) == null) continue; + if (fieldNameSet.contains(field.getName())) continue; + if (Modifier.isStatic(field.getModifiers())) throw new RuntimeException(field + " cannot static on @" + Persist.class.getName() + " in " + clzz.getName()); + if (Modifier.isFinal(field.getModifiers()) && !Map.class.isAssignableFrom(field.getType()) && !Collection.class.isAssignableFrom(field.getType())) { + throw new RuntimeException(field + " cannot final on @" + Persist.class.getName() + " in " + clzz.getName()); + } + fieldNameSet.add(field.getName()); + field.setAccessible(true); + try { + if (field.get(service) == null) continue; + } catch (Exception e) { + logger.log(Level.SEVERE, field + " get value error", e); + continue; + } + fields.add(field); + if (sb.length() > 0) sb.append(','); + sb.append(field.getName()); + } + } while ((clzz = clzz.getSuperclass()) != Object.class); + + if (fields.isEmpty()) continue; //没有数据需要缓存 + if (!cachedir.isDirectory()) cachedir.mkdirs(); + String resname = Sncp.getResourceName(service); + FileOutputStream out = new FileOutputStream(new File(cachedir, prefix + Sncp.getResourceType(service).getName() + (resname.isEmpty() ? "" : ("-" + resname)) + ".bat")); + out.write(sb.toString().getBytes()); + out.write('\n'); + for (Field field : fields) { + Object val = null; + try { + val = field.get(service); + } catch (Exception e) { + logger.log(Level.SEVERE, field + " save value error", e); + } + out.write(convert.convertTo(field.getGenericType(), val)); + out.write('\n'); + } + out.close(); + } + } + protected abstract ClassFilter createFilterClassFilter(); protected abstract ClassFilter createServletClassFilter(); @@ -577,6 +690,7 @@ public abstract class NodeServer { } }); if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString()); + this.savePersistData(); server.shutdown(); } diff --git a/src/org/redkale/net/sncp/Sncp.java b/src/org/redkale/net/sncp/Sncp.java index 959260656..646c9fd64 100644 --- a/src/org/redkale/net/sncp/Sncp.java +++ b/src/org/redkale/net/sncp/Sncp.java @@ -266,7 +266,7 @@ public abstract class Sncp { protected static Class createLocalServiceClass(ClassLoader classLoader, final String name, final Class serviceImplClass) { if (serviceImplClass == null) return null; if (!Service.class.isAssignableFrom(serviceImplClass)) return serviceImplClass; - ResourceFactory.checkName(name); + ResourceFactory.checkName(name); int mod = serviceImplClass.getModifiers(); if (!java.lang.reflect.Modifier.isPublic(mod)) return serviceImplClass; if (java.lang.reflect.Modifier.isAbstract(mod)) return serviceImplClass; @@ -891,7 +891,7 @@ public abstract class Sncp { final AnyValue conf) { if (serviceTypeOrImplClass == null) return null; if (!Service.class.isAssignableFrom(serviceTypeOrImplClass)) return null; - ResourceFactory.checkName(name); + ResourceFactory.checkName(name); int mod = serviceTypeOrImplClass.getModifiers(); boolean realed = !(java.lang.reflect.Modifier.isAbstract(mod) || serviceTypeOrImplClass.isInterface()); if (!java.lang.reflect.Modifier.isPublic(mod)) return null; diff --git a/src/org/redkale/service/Persist.java b/src/org/redkale/service/Persist.java new file mode 100644 index 000000000..bd1e11d0b --- /dev/null +++ b/src/org/redkale/service/Persist.java @@ -0,0 +1,32 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Service类中临时缓存字段
+ * + * 注意: 被标记字段的数据必须是可序列化和反序列化的, 且字段不能是static的, 如果字段类型不是Map或Collection类型则不能修饰为final + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + */ +@Target({FIELD}) +@Retention(RUNTIME) +public @interface Persist { + + /** + * 临时缓存的超时秒数,超过指定秒数的缓存数据将会被废弃, 0表示不超时, 默认超时值为60秒 + * + * @return int + */ + int timeout() default 60; +} diff --git a/src/org/redkale/util/Creator.java b/src/org/redkale/util/Creator.java index 1b34dc1a3..b15fe93c9 100644 --- a/src/org/redkale/util/Creator.java +++ b/src/org/redkale/util/Creator.java @@ -13,6 +13,7 @@ import java.lang.reflect.*; import java.net.*; import java.util.*; import java.util.AbstractMap.SimpleEntry; +import java.util.concurrent.*; import jdk.internal.org.objectweb.asm.*; import jdk.internal.org.objectweb.asm.Type; import static jdk.internal.org.objectweb.asm.Opcodes.*; @@ -206,6 +207,8 @@ public interface Creator { clazz = (Class) HashMap.class; } else if (clazz.isAssignableFrom(HashSet.class)) { clazz = (Class) HashSet.class; + } else if (clazz == ConcurrentMap.class) { + clazz = (Class) ConcurrentHashMap.class; } if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) { throw new RuntimeException("[" + clazz + "] is a interface or abstract class, cannot create it's Creator.");