新增Persist注解, 用于Service成员变量数据的临时缓存
This commit is contained in:
@@ -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"};
|
||||
|
||||
@@ -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<String> 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<String> fieldNameSet = new HashSet<>();
|
||||
final List<Field> 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<Filter> createFilterClassFilter();
|
||||
|
||||
protected abstract ClassFilter<Servlet> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ public abstract class Sncp {
|
||||
protected static <T extends Service> Class<? extends T> createLocalServiceClass(ClassLoader classLoader, final String name, final Class<T> 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;
|
||||
|
||||
32
src/org/redkale/service/Persist.java
Normal file
32
src/org/redkale/service/Persist.java
Normal file
@@ -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类中临时缓存字段 <br>
|
||||
*
|
||||
* <b>注意: </b> 被标记字段的数据必须是可序列化和反序列化的, 且字段不能是static的, 如果字段类型不是Map或Collection类型则不能修饰为final
|
||||
*
|
||||
* <p>
|
||||
* 详情见: https://redkale.org
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@Target({FIELD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface Persist {
|
||||
|
||||
/**
|
||||
* 临时缓存的超时秒数,超过指定秒数的缓存数据将会被废弃, 0表示不超时, 默认超时值为60秒
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int timeout() default 60;
|
||||
}
|
||||
@@ -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<T> {
|
||||
clazz = (Class<T>) HashMap.class;
|
||||
} else if (clazz.isAssignableFrom(HashSet.class)) {
|
||||
clazz = (Class<T>) HashSet.class;
|
||||
} else if (clazz == ConcurrentMap.class) {
|
||||
clazz = (Class<T>) ConcurrentHashMap.class;
|
||||
}
|
||||
if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) {
|
||||
throw new RuntimeException("[" + clazz + "] is a interface or abstract class, cannot create it's Creator.");
|
||||
|
||||
Reference in New Issue
Block a user