倚樓聽風雨

          Chaos 的Java 點滴

          Play源代碼分析1—Server啟動過程

          Play是個Rails風格的Java Web框架,需要了解背景請看:

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

          如何調(diào)試請看此處。以下進入正題^_^

          Server啟動過程主要涉及三個地方:

          1. play.Play類:代表Play本身業(yè)務(wù)模型。
          2. play.server.Server類:負責服務(wù)器啟動。
          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. 然后創(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();
          }


          }

           

          主要做:

          1. 加載配置
          2. new ApplicationClasses();加載app、views和conf路徑到VirtualFile中,VirtualFile是Play內(nèi)部的統(tǒng)一文件訪問接口,方便后續(xù)讀取文件
          3. 加載route
          4. 加載Module,Play的應(yīng)用擴展組件。
          5. 加載Plugin,Play框架自身的擴展組件。
          6. 工作在產(chǎn)品模式則啟動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);
          }
          }
          }

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

          關(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;
          }

          主要步驟:

          1. plugin.compileAll,給所有plugin一次機會進行自定義編譯。
          2. Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));編譯所有.java文件。編譯后的.class存儲在ApplicationClass中。內(nèi)部使用了eclipse的JDT編譯器。
          3. loadApplicationClass,取出ApplicationClass中的.class加入List中返回。

          到此完成.java的加載。相關(guān)對象關(guān)系如下圖:

          Play代碼分析

          接著new Server()啟動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);
          }
          ...
          }

          主要步驟:

          1. 設(shè)置端口,地址
          2. new ServerBootstrap,創(chuàng)建jboss netty服務(wù)器。Play1.1.1使用了netty作為底層通訊服務(wù)器。
          3. new HttpServerPipelineFactory(),設(shè)置netty所需的請求處理管道工廠。它負責當請求到達時提供處理者。
          4. bootstrap.bind(new InetSocketAddress(address, httpPort),綁定地址,端口。

            

          到此萬事具備,只等東風了…

          posted on 2011-04-18 22:25 chaos 閱讀(2350) 評論(1)  編輯  收藏 所屬分類: Play Framework

          評論

          # re: Play源代碼分析1&mdash;Server啟動過程 2011-04-19 09:26 durex

          很有用的代碼,繼續(xù)關(guān)注  回復(fù)  更多評論   

          <2011年4月>
          272829303112
          3456789
          10111213141516
          17181920212223
          24252627282930
          1234567

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(2)

          隨筆分類

          隨筆檔案

          搜索

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 平遥县| 名山县| 抚顺市| 金川县| 景谷| 惠东县| 大宁县| 隆子县| 五莲县| 沧州市| 监利县| 阿克| 华宁县| 郓城县| 正安县| 卓尼县| 库尔勒市| 长白| 琼结县| 碌曲县| 应用必备| 周至县| 临颍县| 敖汉旗| 琼结县| 益阳市| 福清市| 大名县| 电白县| 井冈山市| 承德县| 鹤壁市| 麦盖提县| 定南县| 曲松县| 巍山| 梅河口市| 潮安县| 简阳市| 泸西县| 礼泉县|