Play源代码分析1—Server启动过程

http://www.cnblogs.com/Chaos/archive/2011/04/17/2018500.html

Play是个Rails风格的Java Web框架,需要了解背景请看:

  1. Play Framework介绍1--主要概念
  2. Play Framework介绍2—Helloworld

如何调试请看此处。以下进入正题^_^

Server启动过程主要涉及三个地方:

  1. play.Play类:代表Play本身业务模型。
  2. play.server.Server类:负责服务器启动。
  3. play.classloading包:负责.java文件读取、编译和加载。

总体流程:

Play代码分析-Server.Main

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.");
}
}

复制代码

做两件事:

  1. Play.init
  2. 然后创建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();
}


}

复制代码

主要做:

  1. 加载配置
  2. new ApplicationClasses();加载app、views和conf路径到VirtualFile中,VirtualFile是Play内部的统一文件访问接口,方便后续读取文件
  3. 加载route
  4. 加载Module,Play的应用扩展组件。
  5. 加载Plugin,Play框架自身的扩展组件。
  6. 工作在产品模式则启动Play.

关键步骤为new ApplicationClasses(),执行computeCodeHashe(),后者触发目录扫描,搜索.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流程

Play.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);
}
}

复制代码

关键步骤为执行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;
}

复制代码

主要步骤:

  1. plugin.compileAll,给所有plugin一次机会进行自定义编译。
  2. Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));编译所有.java文件。编译后的.class存储在ApplicationClass中。内部使用了eclipse的JDT编译器。
  3. loadApplicationClass,取出ApplicationClass中的.class加入List<Class>中返回。

到此完成.java的加载。相关对象关系如下图:

Play代码分析

接着new Server()启动HTTP服务,监听请求

简化代码如下:

复制代码

 

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);
}
...
}

复制代码

主要步骤:

  1. 设置端口,地址
  2. new ServerBootstrap,创建jboss netty服务器。Play1.1.1使用了netty作为底层通讯服务器。
  3. new HttpServerPipelineFactory(),设置netty所需的请求处理管道工厂。它负责当请求到达时提供处理者。
  4. bootstrap.bind(new InetSocketAddress(address, httpPort),绑定地址,端口。

  

发布了15 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/J_M_S_H_T/article/details/88311854