倚樓聽風雨

          Chaos 的Java 點滴

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

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

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

          如何調試請看此處。以下進入正題^_^

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

          1. play.Play類:代表Play本身業(yè)務模型。
          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. 然后創(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內部的統(tǒng)一文件訪問接口,方便后續(xù)讀取文件
          3. 加載route
          4. 加載Module,Play的應用擴展組件。
          5. 加載Plugin,Play框架自身的擴展組件。
          6. 工作在產品模式則啟動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流程

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

          關鍵步驟為執(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中。內部使用了eclipse的JDT編譯器。
          3. loadApplicationClass,取出ApplicationClass中的.class加入List中返回。

          到此完成.java的加載。相關對象關系如下圖:

          Play代碼分析

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

          主要步驟:

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

            

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

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

          評論

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

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

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

          導航

          統(tǒng)計

          常用鏈接

          留言簿(2)

          隨筆分類

          隨筆檔案

          搜索

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 定兴县| 沈丘县| 宣武区| 武夷山市| 隆安县| 宝应县| 抚顺县| 广宁县| 黑山县| 太湖县| 永川市| 巩留县| 台中县| 连平县| 罗甸县| 武夷山市| 平度市| 阳西县| 遂溪县| 正阳县| 西充县| 花垣县| 平度市| 孙吴县| 中西区| 错那县| 封丘县| 大洼县| 佛教| 饶平县| 将乐县| 沈阳市| 旺苍县| 金溪县| 杂多县| 梅河口市| 滨州市| 合阳县| 尼勒克县| 樟树市| 娄底市|