Play是个Rails风格的Java Web框架,需要了解背景请看:
如何调试请看此处。以下进入正题^_^
Server启动过程主要涉及三个地方:
总体流程:
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
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(),执行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流程
简化代码如下:
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;
}
主要步骤:
到此完成.java的加载。相关对象关系如下图:
接着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 );
}
...
}
主要步骤:
到此万事具备,只等东风了…