Play源代碼分析:Server啟動(dòng)過程
Play是個(gè)Rails風(fēng)格的Java Web框架,需要了解背景請看:
如何調(diào)試請看此處。以下進(jìn)入正題^_^
Server啟動(dòng)過程主要涉及三個(gè)地方:
- play.Play類:代表Play本身業(yè)務(wù)模型。
- play.server.Server類:負(fù)責(zé)服務(wù)器啟動(dòng)。
- play.classloading包:負(fù)責(zé).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內(nèi)部的統(tǒng)一文件訪問接口,方便后續(xù)讀取文件
- 加載route
- 加載Module,Play的應(yīng)用擴(kuò)展組件。
- 加載Plugin,Play框架自身的擴(kuò)展組件。
- 工作在產(chǎn)品模式則啟動(dòng)Play.
關(guān)鍵步驟為new ApplicationClasses(),執(zhí)行computeCodeHashe(),后者觸發(fā)目錄掃描,搜索.java文件。相關(guān)過程簡化代碼如下:
- 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);
- }
- }
- }
簡化代碼如下:
- 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);
- }
- }
關(guān)鍵步驟為執(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一次機(jī)會(huì)進(jìn)行自定義編譯。
- Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));編譯所有.java文件。編譯后的.class存儲(chǔ)在ApplicationClass中。內(nèi)部使用了eclipse的JDT編譯器。
- loadApplicationClass,取出ApplicationClass中的.class加入List<Class>中返回。
到此完成.java的加載。相關(guān)對象關(guān)系如下圖:
接著new Server()啟動(dòng)HTTP服務(wù),監(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);
- }
- ...
- }
主要步驟:
- 設(shè)置端口,地址
- new ServerBootstrap,創(chuàng)建jboss netty服務(wù)器。Play1.1.1使用了netty作為底層通訊服務(wù)器。
- new HttpServerPipelineFactory(),設(shè)置netty所需的請求處理管道工廠。它負(fù)責(zé)當(dāng)請求到達(dá)時(shí)提供處理者。
- bootstrap.bind(new InetSocketAddress(address, httpPort),綁定地址,端口。
到此萬事具備,只等東風(fēng)了…
原文鏈接:http://www.cnblogs.com/Chaos/archive/2011/04/17/2018500.html
【編輯推薦】