Один ответ на оба вопроса к GroovyServlet

Один ответ на оба вопроса к GroovyServlet

В одной из предыдущих заметок сетовал на то, что GroovyServlet не оправдывает всех моих чаяний. А именно, хотелось, чтобы 1) не было проблем с кодировкой файла скрипта, 2) один bootstrap-скрипт обрабатывал бы все запросы на сервлет.

Решение несложное, но состоящее в написании на Java сервлета, который бы инициализировал GroovyClassLoader, устанавливал бы требуемую кодировку, загружал groovy класс, инициализировал его, и в ответ на каждый запрос вызывал бы метод service() этого класса.

Таким образом, вся логика приложения была бы написана на Groovy, то есть, минимум чистого Java-кода

Такие параметры, как имя скрипта и его кодировка вынес в init parameters сервлета. Итого:

 

файл web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
 <servlet>
  <display-name>GServlet</display-name>
  <servlet-name>GServlet</servlet-name>
  <servlet-class>ru.schleicher.GServlet</servlet-class>
  <init-param>
   <param-name>handler</param-name>
   <param-value>/WEB-INF/Boot.groovy</param-value>
  </init-param>
  <init-param>
   <param-name>encoding</param-name>
   <param-value>UTF-8</param-value>
  </init-param>
 </servlet>
 <servlet-mapping>
  <servlet-name>GServlet</servlet-name>
  <url-pattern>/*</url-pattern>
 </servlet-mapping>
</web-app>

Что тут? в параметре handler — путь к тому самому скрипту. В параметре encoding — соответственно — кодировка файла. Маппинг на все запросы. ru.schleicher.GServlet — тот самый скрипт, что ниже

 

Файл ru/schleicher/GServlet.java:

package ru.schleicher;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;

import java.io.File;
import java.io.IOException;
import java.net.URL;

import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.tools.RootLoader;


public class GServlet extends GenericServlet {

    private static final long serialVersionUID = -7710181651739584146L;
    public static GroovyClassLoader gcl;
    public static GroovyObject boot;
    public static CompilerConfiguration cfg;
    public static Long last_mod = 0L;

    private Boolean needsToLoad() {
        ServletConfig config = getServletConfig();
        String handler = config.getInitParameter("handler");
        String file = getServletContext().getRealPath(handler);
        File scriptfile = new File(file);
        if (last_mod >= scriptfile.lastModified()) {
            return false;
        }
        last_mod = scriptfile.lastModified();
        return true;
    }

    public void init() throws ServletException {
        ServletConfig config = getServletConfig();
        String handler = config.getInitParameter("handler");
        String file = getServletContext().getRealPath(handler);
        File scriptfile = new File(file);
        cfg = new CompilerConfiguration();

        String enc = config.getInitParameter("encoding");
        if (enc == null) {
            enc = "UTF-8";
        }
        cfg.setSourceEncoding(enc);
        cfg.setClasspath(getServletContext().getRealPath("WEB-INF/code/"));
        cfg.setRecompileGroovySource(true);
        cfg.setMinimumRecompilationInterval(0);

        try {
            ClassLoader cl = getClass().getClassLoader();
            URL[] u = {new URL("file:///"+getServletContext().getRealPath("WEB-INF/code/"))};
            RootLoader rl = new RootLoader(u, cl);
            gcl = new GroovyClassLoader(rl, cfg);
            Class boot_class = gcl.parseClass(scriptfile);
            boot = (GroovyObject) boot_class.newInstance();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }

    public void service(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
        service((HttpServletRequest) request, (HttpServletResponse) response);
    }

    public void service(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        if (this.needsToLoad()) {
            this.init();
        }
        boot.invokeMethod("service", new Object[] { request, response, this });
    }
}

Собственно это решает обе задачи.

Нюансы:

  1. Groovy скрипт Boot.groovy должен располагаться в WEB-INF/
  2. Это должен быть простой класс с обязательным методом service(request, response, servlet), который принимает в параметрах то, что там перечислено. Этот класс будет загружен единожды, а метод service выполнится при каждом запросе. Соответственно в свойствах класса можно хранить долгоживущие объекты (коннекторы к БД, builders и проч.)
  3. Есть проверка на изменение этого класса. То есть, если что в нем редактировалось — класс будет перекомпилирован автоматически
  4. Java-библиотеки, размещенные в WEB-INF/lib/ доступны в Groovy без каких-либо дополнительных движений. Прочие Groovy классы, которые может импортировать Boot.groovy должны быть размещены в WEB-INF/code/ Впрочем, эту директорию можно изменить (там, где устанавливаются свойства CompilerConfiguration). Вообще, заморочки с CLASSPATH в Groovy при использовании его в сервлете — тема отдельного занятного разговора (там о всяких там rootLoader'ах и иерархии classLoaders)
  5. Я не силен в Java,поэтому код может быть несовершенным. Если имеютсязамечания и поправки — не стесняйтесь высказать, буду очень благодарен.

 

Энджой!

 

PS: да, все это счастье у меня работало под Tomcat 5.5.20...

А больше я все равно ничего пока не умею и не имею :-)

Комментарии (4)

mem: 1171 total: 22 module: 11 xsl: 7