Статьи

Простая система плагинов для веб-приложений

Нам нужно сделать несколько веб-проектов с большим количеством общих функций. Для этого неплохо было бы использовать систему плагинов (в качестве альтернативы копированию). Некоторые фреймворки (например, Grails) имеют возможность создавать веб-плагины, но большинство этого не делают, поэтому необходимо реализовать что-то на заказ.

Во-первых, давайте определим, какова требуемая функциональность. «Плагин»:

  • должны быть включены просто путем импорта через Maven / Ivy
  • следует зарегистрировать все классы (либо автоматически, либо через однострочную конфигурацию) в контейнере внедрения зависимостей, если он используется
  • должен быть вертикальным — т.е. содержать все файлы, от javascript, css и шаблонов, через контроллеры, до классов сервисного уровня
  • не требует сложной конфигурации, которую нужно копировать из проекта в проект
  • должно позволять легкую разработку и отладку без перераспределения

Классы java помещаются в файл jar и добавляются в каталог lib, следовательно, в путь к классам, так что это простая часть. Но нам нужно извлечь веб-ресурсы в соответствующие места, где они могут быть использованы остальной частью кода. Существует три основных подхода к этому: извлечение во время сборки, извлечение во время выполнения и загрузка во время выполнения из пути к классам.

Последний подход потребует контроллера (или сервлета), который загружает ресурсы из пути к классам (соответствующий jar), кэширует их и обслуживает их. Это имеет пару существенных недостатков, один из которых заключается в том, что, находясь в банке, их нельзя легко заменить во время разработки. Работать с ресурсами classpath также сложно, так как вы заранее не знаете имен файлов.

Два других подхода очень похожи. Например, Grails использует извлечение во время сборки — плагин представляет собой zip-файл, содержащий все необходимые ресурсы, и они извлекаются в соответствующие местоположения во время сборки проекта. Это хорошо, но для этого потребуется немного больше настроек (в нашем случае maven), которые также, вероятно, придется копировать из проекта в проект.

Поэтому мы выбрали подход извлечения во время выполнения. Это происходит при запуске — при загрузке приложения какой-либо слушатель запуска (в нашем случае это пружинные компоненты с @PostConstruct) перебирает все файлы jar в папке lib и извлекает файлы из определенной папки (например, «web»). «). Итак, структура файла JAR выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
com
   company
      pkg
         Foo.class
         Bar.class
web
   plugin-name
       css
           main.css
       js
          foo.js
          bar.js
       images
          logo.png
       views
          foo.jsp
          bar.jsp

Конечным результатом является то, что после запуска приложения вы получаете все необходимые веб-ресурсы, доступные из приложения, поэтому вы можете включить их в страницы (представления) вашего основного приложения.

И код, который выполняет извлечение, довольно прост (используя zip4j для части zip). Это может быть слушатель контекста сервлета, а не пружинный компонент — это не имеет значения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * Component that locates modules (in the form of jar files) and extracts their web elements, if any, on startup
 *
 * @author Bozhidar
 */
@Component
public class ModuleExtractor {
 
    private static final Logger logger = LoggerFactory.getLogger(ModuleExtractor.class);
 
    @Inject
    private ServletContext ctx;
 
    @SuppressWarnings("unchecked")
    @PostConstruct
    public void init() {
        File lib = new File(ctx.getRealPath("/WEB-INF/lib"));
        File[] jars = lib.listFiles();
        String targetPath = ctx.getRealPath("/");
        String viewPath = "/WEB-INF/views"; //that can be made configurable
        for (File jar : jars) {
            try {
                ZipFile file = new ZipFile(jar);
                for (FileHeader header : (List<FileHeader>) file.getFileHeaders()) {
                    if (header.getFileName().startsWith("web/") && !fileExists(header)) {
                        // extract views in WEB-INF (inaccessible to the outside world)
                        // all other files are extracted in the root of the application
                        if (header.getFileName().contains("/views/")) {
                            file.extractFile(header, targetPath + viewPath);
                        } else {
                            file.extractFile(header, targetPath);
                        }
                    }
                }
            } catch (ZipException ex) {
                logger.warn("Error opening jar file and looking for a web-module in: " + jar, ex);
            }
        }
    }
 
    private boolean fileExists(FileHeader header) {
        return new File(ctx.getRealPath(header.getFileName())).exists();
    }
}

Итак, чтобы создать плагин, вы просто создаете проект maven с jar-упаковкой и добавляете его в качестве зависимости к основному проекту, обо всем остальном позаботитесь. Вам может потребоваться зарегистрировать ModuleExtractor если сканирование пути к классам для bean-компонентов не включено (или вы решили сделать его слушателем), но это все.

Примечание: это решение не стремится быть полнофункциональной системой плагинов, которая решает все проблемы. Он не поддерживает версионирование, подмодули и т. Д. Поэтому название «просто». Но с этим можно многое сделать, и это имеет очень низкую сложность.