Play源代碼分析1—Server啟動過程
Play是個Rails風格的Java Web框架,需要了解背景請看:
如何調試請看此處。以下進入正題^_^
Server啟動過程主要涉及三個地方:
- play.Play類:代表Play本身業(yè)務模型。
- play.server.Server類:負責服務器啟動。
- play.classloading包:負責.java文件讀取、編譯和加載。
總體流程:
Server.main為入口方法:
public static void main(String[] args) throws Exception {
…
Play.init(root, System.getProperty("play.id", ""));
if (System.getProperty("precompile") == null) {
new Server();
} else {
Logger.info("Done.");
}
}
做兩件事:
- Play.init
- 然后創(chuàng)建Server對象。
Play.init
public static void init(File root, String id) {
…
readConfiguration();
Play.classes = new ApplicationClasses();
…
// Build basic java source path
VirtualFile appRoot = VirtualFile.open(applicationPath);
roots.add(appRoot);
javaPath = new ArrayList<VirtualFile>(2);
javaPath.add(appRoot.child("app"));
javaPath.add(appRoot.child("conf"));
// Build basic templates path
templatesPath = new ArrayList<VirtualFile>(2);
templatesPath.add(appRoot.child("app/views"));
// Main route file
routes = appRoot.child("conf/routes");
…
// Load modules
loadModules();
…
// Enable a first classloader
classloader = new ApplicationClassloader();
// Plugins
loadPlugins();
// Done !
if (mode == Mode.PROD ||preCompile() ) {
start();
}
…
}
主要做:
- 加載配置
- new ApplicationClasses();加載app、views和conf路徑到VirtualFile中,VirtualFile是Play內部的統(tǒng)一文件訪問接口,方便后續(xù)讀取文件
- 加載route
- 加載Module,Play的應用擴展組件。
- 加載Plugin,Play框架自身的擴展組件。
- 工作在產品模式則啟動Play.
關鍵步驟為new ApplicationClasses(),執(zhí)行computeCodeHashe(),后者觸發(fā)目錄掃描,搜索.java文件。相關過程簡化代碼如下:
public ApplicationClassloader() {
super(ApplicationClassloader.class.getClassLoader());
// Clean the existing classes
for (ApplicationClass applicationClass : Play.classes.all()) {
applicationClass.uncompile();
}
pathHash = computePathHash();
…
}
int computePathHash() {
StringBuffer buf = new StringBuffer();
for (VirtualFile virtualFile : Play.javaPath) {
scan(buf, virtualFile);
}
return buf.toString().hashCode();
}
void scan(StringBuffer buf, VirtualFile current) {
if (!current.isDirectory()) {
if (current.getName().endsWith(".java")) {
Matcher matcher = Pattern.compile("\\s+class\\s([a-zA-Z0-9_]+)\\s+").matcher(current.contentAsString());
buf.append(current.getName());
buf.append("(");
while (matcher.find()) {
buf.append(matcher.group(1));
buf.append(",");
}
buf.append(")");
}
} else if (!current.getName().startsWith(".")) {
for (VirtualFile virtualFile : current.list()) {
scan(buf, virtualFile);
}
}
}
Start流程
簡化代碼如下:
public static synchronized void start() {
try {
...
// Reload configuration
readConfiguration();
...
// Try to load all classes
Play.classloader.getAllClasses();
// Routes
Router.detectChanges(ctxPath);
// Cache
Cache.init();
// Plugins
for (PlayPlugin plugin : plugins) {
try {
plugin.onApplicationStart();
} catch(Exception e) {
if(Play.mode.isProd()) {
Logger.error(e, "Can't start in PROD mode with errors");
}
if(e instanceof RuntimeException) {
throw (RuntimeException)e;
}
throw new UnexpectedException(e);
}
}
...
// Plugins
for (PlayPlugin plugin : plugins) {
plugin.afterApplicationStart();
}
} catch (PlayException e) {
started = false;
throw e;
} catch (Exception e) {
started = false;
throw new UnexpectedException(e);
}
}
關鍵步驟為執(zhí)行Play.classloader.getAllClasses()加載app目錄中的類型。簡化代碼如下:
public List<Class> getAllClasses() {
if (allClasses == null) {
allClasses = new ArrayList<Class>();
if (Play.usePrecompiled) {
...
} else {
List<ApplicationClass> all = new ArrayList<ApplicationClass>();
// Let's plugins play
for (PlayPlugin plugin : Play.plugins) {
plugin.compileAll(all);
}
for (VirtualFile virtualFile : Play.javaPath) {
all.addAll(getAllClasses(virtualFile));
}
List<String> classNames = new ArrayList<String>();
for (int i = 0; i < all.size(); i++) {
if (all.get(i) != null && !all.get(i).compiled) {
classNames.add(all.get(i).name);
}
}
Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));
for (ApplicationClass applicationClass : Play.classes.all()) {
Class clazz = loadApplicationClass(applicationClass.name);
if (clazz != null) {
allClasses.add(clazz);
}
}
...
}
}
return allClasses;
}
主要步驟:
- plugin.compileAll,給所有plugin一次機會進行自定義編譯。
- Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));編譯所有.java文件。編譯后的.class存儲在ApplicationClass中。內部使用了eclipse的JDT編譯器。
- loadApplicationClass,取出ApplicationClass中的.class加入List中返回。
到此完成.java的加載。相關對象關系如下圖:
接著new Server()啟動HTTP服務,監(jiān)聽請求
簡化代碼如下:
public Server() {
...
if (httpPort == -1 && httpsPort == -1) {
httpPort = 9000;
}
...
InetAddress address = null;
try {
if (p.getProperty("http.address") != null) {
address = InetAddress.getByName(p.getProperty("http.address"));
} else if (System.getProperties().containsKey("http.address")) {
address = InetAddress.getByName(System.getProperty("http.address"));
}
} catch (Exception e) {
Logger.error(e, "Could not understand http.address");
System.exit(-1);
}
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool())
);
try {
if (httpPort != -1) {
bootstrap.setPipelineFactory(new HttpServerPipelineFactory());
bootstrap.bind(new InetSocketAddress(address, httpPort));
bootstrap.setOption("child.tcpNoDelay", true);
if (Play.mode == Mode.DEV) {
if (address == null) {
Logger.info("Listening for HTTP on port %s (Waiting a first request to start) ...", httpPort);
} else {
Logger.info("Listening for HTTP at %2$s:%1$s (Waiting a first request to start) ...", httpPort, address);
}
} else {
if (address == null) {
Logger.info("Listening for HTTP on port %s ...", httpPort);
} else {
Logger.info("Listening for HTTP at %2$s:%1$s ...", httpPort, address);
}
}
}
} catch (ChannelException e) {
Logger.error("Could not bind on port " + httpPort, e);
System.exit(-1);
}
...
}
主要步驟:
- 設置端口,地址
- new ServerBootstrap,創(chuàng)建jboss netty服務器。Play1.1.1使用了netty作為底層通訊服務器。
- new HttpServerPipelineFactory(),設置netty所需的請求處理管道工廠。它負責當請求到達時提供處理者。
- bootstrap.bind(new InetSocketAddress(address, httpPort),綁定地址,端口。
到此萬事具備,只等東風了…
posted on 2011-04-18 22:25 chaos 閱讀(2348) 評論(1) 編輯 收藏 所屬分類: Play Framework