diff --git a/src/org/redkale/boot/Application.java b/src/org/redkale/boot/Application.java
new file mode 100644
index 000000000..a9adaedce
--- /dev/null
+++ b/src/org/redkale/boot/Application.java
@@ -0,0 +1,572 @@
+/*
+ * 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.boot;
+
+import org.redkale.boot.ClassFilter.FilterEntry;
+import org.redkale.util.AnyValue.DefaultAnyValue;
+import java.io.*;
+import java.net.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+import java.util.function.*;
+import java.util.logging.*;
+import javax.xml.parsers.*;
+import org.redkale.convert.bson.*;
+import org.redkale.convert.json.*;
+import org.redkale.net.*;
+import org.redkale.net.http.*;
+import org.redkale.net.sncp.*;
+import org.redkale.service.*;
+import org.redkale.source.*;
+import org.redkale.util.*;
+import org.redkale.watch.*;
+import org.w3c.dom.*;
+
+/**
+ * 编译时需要加入: -XDignore.symbol.file=true
+ *
+ * 进程启动类,程序启动后读取application.xml,进行classpath扫描动态加载Service与Servlet
+ * 优先加载所有SNCP协议的服务, 再加载其他协议服务,
+ * 最后进行Service、Servlet与其他资源之间的依赖注入。
+ *
+ *
+ * @see http://www.redkale.org
+ * @author zhangjx
+ */
+public final class Application {
+
+ //当前进程启动的时间, 类型: long
+ public static final String RESNAME_APP_TIME = "APP_TIME";
+
+ //当前进程的根目录, 类型:String
+ public static final String RESNAME_APP_HOME = "APP_HOME";
+
+ //application.xml 文件中resources节点的内容, 类型: AnyValue
+ public static final String RESNAME_APP_GRES = "APP_GRES";
+
+ //当前进程节点的name, 类型:String
+ public static final String RESNAME_APP_NODE = "APP_NODE";
+
+ //当前进程节点的IP地址, 类型:InetAddress、String
+ public static final String RESNAME_APP_ADDR = "APP_ADDR";
+
+ //当前SNCP Server的IP地址+端口集合 类型: Map、HashMap
+ public static final String RESNAME_APP_NODES = "APP_NODES";
+
+ //当前Service的IP地址+端口 类型: SocketAddress、InetSocketAddress、String
+ public static final String RESNAME_SERVER_ADDR = "SERVER_ADDR"; // SERVER_ADDR
+
+ //当前SNCP Server所属的组 类型: String
+ public static final String RESNAME_SERVER_GROUP = "SERVER_GROUP";
+
+ //当前Service所属的组 类型: Set、String[]
+ public static final String RESNAME_SNCP_GROUPS = Sncp.RESNAME_SNCP_GROUPS; // SNCP_GROUPS
+
+ protected final Map globalNodes = new HashMap<>();
+
+ private final Map> globalGroups = new HashMap<>();
+
+ protected final List transports = new ArrayList<>();
+
+ protected final InetAddress localAddress;
+
+ protected final List sources = new CopyOnWriteArrayList<>();
+
+ protected final List servers = new CopyOnWriteArrayList<>();
+
+ protected CountDownLatch servicecdl; //会出现两次赋值
+
+ //--------------------------------------------------------------------------------------------
+ private final ResourceFactory factory = ResourceFactory.root();
+
+ private final WatchFactory watch = WatchFactory.root();
+
+ private File home;
+
+ private final Logger logger;
+
+ private final AnyValue config;
+
+ private final long startTime = System.currentTimeMillis();
+
+ private final CountDownLatch serversLatch;
+
+ private Application(final AnyValue config) {
+ this.config = config;
+
+ final File root = new File(System.getProperty(RESNAME_APP_HOME));
+ this.factory.register(RESNAME_APP_TIME, long.class, this.startTime);
+ this.factory.register(RESNAME_APP_HOME, Path.class, root.toPath());
+ this.factory.register(RESNAME_APP_HOME, File.class, root);
+ try {
+ this.factory.register(RESNAME_APP_HOME, root.getCanonicalPath());
+ this.home = root.getCanonicalFile();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ String localaddr = config.getValue("address", "").trim();
+ this.localAddress = localaddr.isEmpty() ? Utility.localInetAddress() : new InetSocketAddress(localaddr, 0).getAddress();
+ Application.this.factory.register(RESNAME_APP_ADDR, Application.this.localAddress.getHostAddress());
+ Application.this.factory.register(RESNAME_APP_ADDR, InetAddress.class, Application.this.localAddress);
+ {
+ String node = config.getValue("node", "").trim();
+ if (node.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ byte[] bs = this.localAddress.getAddress();
+ int v1 = bs[bs.length - 2] & 0xff;
+ int v2 = bs[bs.length - 1] & 0xff;
+ if (v1 <= 0xf) sb.append('0');
+ sb.append(Integer.toHexString(v1));
+ if (v2 <= 0xf) sb.append('0');
+ sb.append(Integer.toHexString(v2));
+ node = sb.toString();
+ }
+ Application.this.factory.register(RESNAME_APP_NODE, node);
+ System.setProperty(RESNAME_APP_NODE, node);
+ }
+ //以下是初始化日志配置
+ final File logconf = new File(root, "conf/logging.properties");
+ if (logconf.isFile() && logconf.canRead()) {
+ try {
+ final String rootpath = root.getCanonicalPath().replace('\\', '/');
+ FileInputStream fin = new FileInputStream(logconf);
+ Properties properties = new Properties();
+ properties.load(fin);
+ fin.close();
+ properties.entrySet().stream().forEach(x -> {
+ x.setValue(x.getValue().toString().replace("${APP_HOME}", rootpath));
+ });
+
+ if (properties.getProperty("java.util.logging.FileHandler.formatter") == null) {
+ properties.setProperty("java.util.logging.FileHandler.formatter", LogFileHandler.LoggingFormater.class.getName());
+ }
+ if (properties.getProperty("java.util.logging.ConsoleHandler.formatter") == null) {
+ properties.setProperty("java.util.logging.ConsoleHandler.formatter", LogFileHandler.LoggingFormater.class.getName());
+ }
+ String fileHandlerPattern = properties.getProperty("java.util.logging.FileHandler.pattern");
+ if (fileHandlerPattern != null && fileHandlerPattern.contains("%d")) {
+ final String fileHandlerClass = LogFileHandler.class.getName();
+ Properties prop = new Properties();
+ final String handlers = properties.getProperty("handlers");
+ if (handlers != null && handlers.contains("java.util.logging.FileHandler")) {
+ prop.setProperty("handlers", handlers.replace("java.util.logging.FileHandler", fileHandlerClass));
+ }
+ if (!prop.isEmpty()) {
+ String prefix = fileHandlerClass + ".";
+ properties.entrySet().stream().forEach(x -> {
+ if (x.getKey().toString().startsWith("java.util.logging.FileHandler.")) {
+ prop.put(x.getKey().toString().replace("java.util.logging.FileHandler.", prefix), x.getValue());
+ }
+ });
+ prop.entrySet().stream().forEach(x -> {
+ properties.put(x.getKey(), x.getValue());
+ });
+ }
+ properties.put(SncpClient.class.getSimpleName() + ".handlers", LogFileHandler.SncpLogFileHandler.class.getName());
+ }
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ final PrintStream ps = new PrintStream(out);
+ properties.forEach((x, y) -> ps.println(x + "=" + y));
+ LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(out.toByteArray()));
+ } catch (Exception e) {
+ Logger.getLogger(this.getClass().getSimpleName()).log(Level.WARNING, "init logger configuration error", e);
+ }
+ }
+ this.logger = Logger.getLogger(this.getClass().getSimpleName());
+ this.serversLatch = new CountDownLatch(config.getAnyValues("server").length + 1);
+ }
+
+ public ResourceFactory getResourceFactory() {
+ return factory;
+ }
+
+ public WatchFactory getWatchFactory() {
+ return watch;
+ }
+
+ public File getHome() {
+ return home;
+ }
+
+ public long getStartTime() {
+ return startTime;
+ }
+
+ private void initLogging() {
+
+ }
+
+ public void init() throws Exception {
+ System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "" + Runtime.getRuntime().availableProcessors() * 4);
+ System.setProperty("convert.bson.tiny", "true");
+ System.setProperty("convert.json.tiny", "true");
+ System.setProperty("convert.bson.pool.size", "128");
+ System.setProperty("convert.json.pool.size", "128");
+ System.setProperty("convert.bson.writer.buffer.defsize", "4096");
+ System.setProperty("convert.json.writer.buffer.defsize", "4096");
+
+ File persist = new File(this.home, "conf/persistence.xml");
+ final String homepath = this.home.getCanonicalPath();
+ if (persist.isFile()) System.setProperty(DataDefaultSource.DATASOURCE_CONFPATH, persist.getCanonicalPath());
+ logger.log(Level.INFO, RESNAME_APP_HOME + "=" + homepath + "\r\n" + RESNAME_APP_ADDR + "=" + this.localAddress.getHostAddress());
+ String lib = config.getValue("lib", "").trim().replace("${APP_HOME}", homepath);
+ lib = lib.isEmpty() ? (homepath + "/conf") : (lib + ";" + homepath + "/conf");
+ Server.loadLib(logger, lib);
+ initLogging();
+ if (this.localAddress != null) {
+ byte[] bs = this.localAddress.getAddress();
+ int v = (0xff & bs[bs.length - 2]) % 10 * 100 + (0xff & bs[bs.length - 1]);
+ this.factory.register("property.datasource.nodeid", "" + v);
+ }
+ //------------------------------------------------------------------------
+ final AnyValue resources = config.getAnyValue("resources");
+ if (resources != null) {
+ factory.register(RESNAME_APP_GRES, AnyValue.class, resources);
+ final AnyValue properties = resources.getAnyValue("properties");
+ if (properties != null) {
+ String dfloads = properties.getValue("load");
+ if (dfloads != null) {
+ for (String dfload : dfloads.split(";")) {
+ if (dfload.trim().isEmpty()) continue;
+ dfload = dfload.trim().replace("${APP_HOME}", home.getCanonicalPath()).replace('\\', '/');
+ final File df = (dfload.indexOf('/') < 0) ? new File(home, "conf/" + dfload) : new File(dfload);
+ if (df.isFile()) {
+ Properties ps = new Properties();
+ InputStream in = new FileInputStream(df);
+ ps.load(in);
+ in.close();
+ ps.forEach((x, y) -> factory.register("property." + x, y));
+ }
+ }
+ }
+ for (AnyValue prop : properties.getAnyValues("property")) {
+ String name = prop.getValue("name");
+ String value = prop.getValue("value");
+ if (name == null || value == null) continue;
+ if (name.startsWith("system.property.")) {
+ System.setProperty(name.substring("system.property.".length()), value);
+ } else if (name.startsWith("mimetype.property.")) {
+ MimeType.add(name.substring("mimetype.property.".length()), value);
+ } else {
+ factory.register("property." + name, value);
+ }
+ }
+ }
+ }
+ this.factory.register(BsonFactory.root());
+ this.factory.register(JsonFactory.root());
+ this.factory.register(BsonFactory.root().getConvert());
+ this.factory.register(JsonFactory.root().getConvert());
+ initResources();
+ }
+
+ private void initResources() throws Exception {
+ //-------------------------------------------------------------------------
+ final AnyValue resources = config.getAnyValue("resources");
+ if (resources != null) {
+ //------------------------------------------------------------------------
+
+ for (AnyValue conf : resources.getAnyValues("group")) {
+ final String group = conf.getValue("name", "");
+ String protocol = conf.getValue("protocol", Transport.DEFAULT_PROTOCOL).toUpperCase();
+ if (!"TCP".equalsIgnoreCase(protocol) && !"UDP".equalsIgnoreCase(protocol)) {
+ throw new RuntimeException("Not supported Transport Protocol " + conf.getValue("protocol"));
+ }
+ Set addrs = globalGroups.get(group);
+ if (addrs == null) {
+ addrs = new LinkedHashSet<>();
+ globalGroups.put(group, addrs);
+ }
+ for (AnyValue node : conf.getAnyValues("node")) {
+ final InetSocketAddress addr = new InetSocketAddress(node.getValue("addr"), node.getIntValue("port"));
+ addrs.add(addr);
+ String oldgroup = globalNodes.get(addr);
+ if (oldgroup != null) throw new RuntimeException(addr + " had one more group " + (globalNodes.get(addr)));
+ globalNodes.put(addr, group);
+ }
+ }
+ }
+ //------------------------------------------------------------------------
+ }
+
+ private void startSelfServer() throws Exception {
+ final Application application = this;
+ new Thread() {
+ {
+ setName("Application-Control-Thread");
+ }
+
+ @Override
+ public void run() {
+ try {
+ final DatagramChannel channel = DatagramChannel.open();
+ channel.configureBlocking(true);
+ channel.socket().setSoTimeout(3000);
+ channel.bind(new InetSocketAddress(config.getValue("host", "127.0.0.1"), config.getIntValue("port")));
+ boolean loop = true;
+ ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
+ while (loop) {
+ buffer.clear();
+ SocketAddress address = channel.receive(buffer);
+ buffer.flip();
+ byte[] bytes = new byte[buffer.remaining()];
+ buffer.get(bytes);
+ if ("SHUTDOWN".equalsIgnoreCase(new String(bytes))) {
+ try {
+ long s = System.currentTimeMillis();
+ logger.info(application.getClass().getSimpleName() + " shutdowning");
+ application.shutdown();
+ buffer.clear();
+ buffer.put("SHUTDOWN OK".getBytes());
+ buffer.flip();
+ channel.send(buffer, address);
+ long e = System.currentTimeMillis() - s;
+ logger.info(application.getClass().getSimpleName() + " shutdown in " + e + " ms");
+ application.serversLatch.countDown();
+ System.exit(0);
+ } catch (Exception ex) {
+ logger.log(Level.INFO, "SHUTDOWN FAIL", ex);
+ buffer.clear();
+ buffer.put("SHUTDOWN FAIL".getBytes());
+ buffer.flip();
+ channel.send(buffer, address);
+ }
+ }
+ }
+ } catch (Exception e) {
+ logger.log(Level.INFO, "Control fail", e);
+ System.exit(1);
+ }
+ }
+ }.start();
+ }
+
+ private void sendShutDown() throws Exception {
+ final DatagramChannel channel = DatagramChannel.open();
+ channel.configureBlocking(true);
+ channel.connect(new InetSocketAddress(config.getValue("host", "127.0.0.1"), config.getIntValue("port")));
+ ByteBuffer buffer = ByteBuffer.allocate(128);
+ buffer.put("SHUTDOWN".getBytes());
+ buffer.flip();
+ channel.write(buffer);
+ buffer.clear();
+ channel.configureBlocking(false);
+ channel.read(buffer);
+ buffer.flip();
+ byte[] bytes = new byte[buffer.remaining()];
+ buffer.get(bytes);
+ channel.close();
+ logger.info(new String(bytes));
+ Thread.sleep(500);
+ }
+
+ public void start() throws Exception {
+ final AnyValue[] entrys = config.getAnyValues("server");
+ CountDownLatch timecd = new CountDownLatch(entrys.length);
+ final List sncps = new ArrayList<>();
+ final List others = new ArrayList<>();
+ for (final AnyValue entry : entrys) {
+ if (entry.getValue("protocol", "").toUpperCase().startsWith("SNCP")) {
+ sncps.add(entry);
+ } else {
+ others.add(entry);
+ }
+ }
+ if (!sncps.isEmpty() && globalNodes.isEmpty()) throw new RuntimeException("found SNCP Server node but not found node info.");
+
+ factory.register(RESNAME_APP_NODES, new TypeToken