14 августа 2011 г.

Скрестим ежа с ужом. Java + Ruby + Rubygems = Maven

Генетикам известно, что гибриды (помесь разных видов) обладают свойствами своих родителей и физиологически более сильные особи. Такого же эффекта можно добиться и в техники, используя сильные стороны разных технологий. В этой статье я покажу преимущество такого подхода, на примере гибрида Ruby и Java. Информации в инете об этом не так уж и много, я надеюсь прочитав этот пост вы сэкономите пару бессонных ночей в отличии от меня;)


Уж и ёж

И так, за ужа у нас будет Ruby:
  • Динамическая типизация;
  • Гибкость;
  • Высокая скорость разработки;
  • Много интересных и вкусных gems;
А Java значит еж:
  • Высокая производительность;
  • Огромное количество готового кода;
  • Полноценный GUI;
  • Переносимость;
Хочу отметить, что сильные стороны я перечислил для языков относительно друг друга, я знаю что Си быстрее, а Java тоже высокоуровневый;)

Продвинутый читатель наверно скажет: "JRuby!!". И будет прав! Но в статье я покажу, как организовать мультиязыковый проект, использовать Java классы в скриптах Ruby и скомпилировать один jar вместе с Ruby и его гемами!

Хорошим, тоном при разработки гибридных проектов будет использовать скритовый язык для организации логики приложения, а низкоуровневые задачи решать уже на быстром статическом языке. И придерживаясь этой идеологии мы напишем маленькое web приложение на Sinatra, которое использует Java класс при обработке запросов!

Реализация

Для начала нам понадобиться JDK (я использую 1.6) и Maven. Для тех, кто не знает это утилита для сборки проекта Java вместе с зависимостями. Похож на bundler в руби, но более развитый - он очень много может за счет большого числа плагинов и в тоже время более сложный в настройке. Для работы нам понадобиться Maven версии 3, со вторым работать не будет! Вот тут как его установить. Теперь нам нужно организовать проект под работу с мавеном:

-\Наш проект
 |--pom.xml - файл конфигурации проекта Maven
 |--\src
    |--\main
        |--\java - здесь будут Java часть проекта (дальше идет свободная
        |   |--\net         организация пакетов Java)
        |       |--\flipback
        |           |--\usejruby
        |               |--UseJRuby.java - наш главный класс
        |--\resources - тут лежат дополнительные файлы, в том числе и скрипты Ruby
           |--\scripts
              |--test.rb - Ruby скрипт вызывающий 

Рассмотрим наш Java класс UseJRuby.java
package net.flipback.usejruby;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

@SuppressWarnings({ "restriction", "unused" })

public class UseJRuby {

    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("jruby");
        
        try {
            engine.eval("require 'rubygems';require 'scripts/test'");
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
    
    public static String helloFromJava() {
        return "Hello from Java";
    }
} 
Суть очень проста, при запуске программы мы запускаем движок JRuby и выполняем код с подключением скрипта с Ruby кодом, который и выполняет логику приложения. Так же у нас есть статический метод helloFromJava, его и будет использовать скрипт для ответа на запрос. Естественно, мы могли написать его в другом классе, но не написали)

Теперь посмотрим Ruby скрипт test.rb:
require 'sinatra'
require 'java'

$: << File.join(File.dirname(__FILE__),'..')
java_import 'net.flipback.usejruby.UseJRuby'

class Server < Sinatra::Base
  get '/' do
    UseJRuby.helloFromJava
  end 
end
Server.run!
Обычное приложение Sinatra, один маленький нюанс, в обработке запроса к корню приложения, мы вызываем статический метод Java класса!! Отметим, что для подключения джава класса, на надо определить путь к директории с классами:
$: << File.join(File.dirname(__FILE__),'..')
При компиляции, все содержимое папки resources попадает в корень директории target/classes в месте с пакетами Java и окажется, что скрипт находиться на уровень выше чем классы Java, по этому мы ему и подсказали, где искать классы.

Конфигурация

Теперь, что бы проект собрать надо сконфигурировать pom.xml  файл. Код в студию!
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.flipback.useruby</groupId>
  <artifactId>usejruby</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Use JRUBY</name>
  <dependencies>
          <dependency>
            <groupId>rubygems</groupId>
            <artifactId>sinatra</artifactId>
            <version>1.2.6</version>
            <type>gem</type>
          </dependency>
          <dependency>
              <groupId>org.jruby</groupId>
              <artifactId>jruby-complete</artifactId>
              <version>1.6.3</version>
              <type>jar</type>
              <scope>compile</scope>
          </dependency>
  </dependencies>
  <build>
      <plugins>
        <plugin>
            <groupId>de.saumya.mojo</groupId>
            <artifactId>gem-maven-plugin</artifactId>
            <version>0.28.3</version>
            <extensions>false</extensions> 
            <configuration>
                <script>
                require 'rubygems'
                require 'scripts/test'
                </script>
            </configuration>
          </plugin>
          <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>net.flipback.usejruby.UseJRuby</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies-with-gems</descriptorRef>
                </descriptorRefs>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>de.saumya.mojo</groupId>
                    <artifactId>gem-assembly-descriptors</artifactId>
                <version>0.3</version>
            </dependency>
            </dependencies>
        </plugin>
     </plugins>
  </build>
  <repositories>
    <repository>
      <id>rubygems-releases</id>
      <url>http://gems.saumya.de/releases</url>
    </repository>
  </repositories>
</project>
Кода много, но не бойтесь я вами)) И так по частям. Описательная часть нашего проекта:
<groupId>net.flipback.useruby</groupId>
  <artifactId>usejruby</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Use JRUBY</name>
Тут все просто и понятно. Отметим, что все зависимости, библиотеки и плагины имеют свой идентификатор aritfactId, по нему maven найдет его в удаленном репозитории и скачает в локальный. Т.е. устанавливать кроме Mavenа нам ничего не нужно, он сам скачает все что требуется. Дальше плагины:

gem-maven-plugin

Плагин для работы с RubyGems - это гвоздь программы. Именно он позволит
использовать гемы и установит их в проект. Обратите внимание на секцию <script> в ней мы пишем тоже, что и в нашем Java классе. Дело в том, что когда мы соберем проект в jar пакет, он будет запускаться из UseJRuby.class, а при разработке мы можем запустить программу с помощью команды:
 

$mvn gem:exec

Котрая и выполнит код из секции  
<plugin>
     <groupId>de.saumya.mojo</groupId>
     <artifactId>gem-maven-plugin</artifactId>
     <version>0.28.3</version>
     <extensions>false</extensions> 
     <configuration>
        <script>
           require 'rubygems'
           require 'scripts/test'
        </script>
     </configuration>
</plugin>
 
maven-assembly-plugin
Плагин для сборки проекта в один jar вместе с зависимостями и гемами. С помощью него мы и соберем приложение и отправим моей маме, она его сможет запустить)) Здесь мы указали, класс для запуска net.flipback.usejruby.UseJRuby, а также указали, что собирать нужно вместе с гемами. Поддержку гемов обеспечивает расширение gem-assembly-descriptors, которое мы прописали в зависимостях.
<plugin>
   <artifactId>maven-assembly-plugin</artifactId>
   <configuration>
      <archive>
        <manifest>
           <mainClass>net.flipback.usejruby.UseJRuby</mainClass>
        </manifest>
      </archive>
      <descriptorRefs>
         <descriptorRef>jar-with-dependencies-with-gems</descriptorRef>
      </descriptorRefs>
      <dependencies>
         <dependency>
            <groupId>de.saumya.mojo</groupId>
            <artifactId>gem-assembly-descriptors</artifactId>
            <version>0.3</version>
        </dependency>
    </dependencies>
 </plugin>
 Так с плагинами все! Теперь остались зависимости:
<dependencies>
   <dependency>
      <groupId>rubygems</groupId>
      <artifactId>sinatra</artifactId>
      <version>1.2.6</version>
      <type>gem</type>
    </dependency> 
    <dependency>
       <groupId>org.jruby</groupId>
       <artifactId>jruby-complete</artifactId>
       <version>1.6.3</version>
       <type>jar</type>
       <scope>compile</scope>
    </dependency>
 </dependencies>
Тут мы прописали, что нам нужен гем под названием sinatra и сам JRuby.

Для гемов нам нужно указать репозиторий:
<repositories>
    <repository>
      <id>rubygems-releases</id>
      <url>http://gems.saumya.de/releases</url>
    </repository>
  </repositories>
С конфигурацией покончено. А чуть не забыл!! Для работы с плагином gem-maven-plugin пропишите в настройках mavena файл settings.xml:
<pluginGroups>
  <pluginGroup>de.saumya.mojo</pluginGroup>
</pluginGroups>

Сборка

Теперь попробуем запустить проект. Для этого вводи 2-е команды:

$mvn compile #компиляция проекта
$mvn gem:exec #запуск программы


Не пугайтесь, что maven начнет много качать из интернета. Он загружает дерево зависимостей только для первого запуска программы. Если все хорошо, то запустится приложение синатры и при переходе по ссылке http://127.0.0.1:4567/ увидите приветствие из Java класса. По моему это очень круто)
Теперь сборка в jar архив:

$mvn assembly:assembly

После успешного завершения в директории target вы найдете файл с длинным названием sejruby-0.0.1-SNAPSHOT-jar-with-dependencies-with-gems.jar , который включает в себя JRuby и Sinatra и может запускаться везде, где есть JavaRuntime.

П.С.

Если Вас заинтересовал такой подход к разработке, то посмотрите проект jruby-maven-plugins там много интересных плагинов, которые смогут вам помочь. Надеюсь было интересно) Всем пока!

Комментариев нет:

Отправить комментарий