
Один ответ на оба вопроса к 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 });
}
}
Собственно это решает обе задачи.
Нюансы:
- Groovy скрипт Boot.groovy должен располагаться в WEB-INF/
- Это должен быть простой класс с обязательным методом service(request, response, servlet), который принимает в параметрах то, что там перечислено. Этот класс будет загружен единожды, а метод service выполнится при каждом запросе. Соответственно в свойствах класса можно хранить долгоживущие объекты (коннекторы к БД, builders и проч.)
- Есть проверка на изменение этого класса. То есть, если что в нем редактировалось — класс будет перекомпилирован автоматически
- Java-библиотеки, размещенные в WEB-INF/lib/ доступны в Groovy без каких-либо дополнительных движений. Прочие Groovy классы, которые может импортировать Boot.groovy должны быть размещены в WEB-INF/code/ Впрочем, эту директорию можно изменить (там, где устанавливаются свойства CompilerConfiguration). Вообще, заморочки с CLASSPATH в Groovy при использовании его в сервлете — тема отдельного занятного разговора (там о всяких там rootLoader'ах и иерархии classLoaders)
- Я не силен в Java,поэтому код может быть несовершенным. Если имеютсязамечания и поправки — не стесняйтесь высказать, буду очень благодарен.
Энджой!
PS: да, все это счастье у меня работало под Tomcat 5.5.20...
А больше я все равно ничего пока не умею и не имею :-)
Комментарии (4)