Skip to content

Spring in Portofino

Alessio Stalla edited this page Dec 5, 2022 · 9 revisions

Portofino 5 includes the Spring dependency injection framework and, from version to version, has integrated Spring progressively more tightly. Starting from Portofino 5.3, we also integrate with Spring Boot in various ways and we anticipate that Boot will be the primary deployment option for Portofino applications in the future.

Here we document the integration of Spring into Portofino.

Spring Boot

Traditionally, Portofino has always been distributed as a web application archive (WAR file) to be deployed on a servlet container or application server. However, since Portofino 5.3, we also have the option to build a self-contained service JAR that we can launch from the command line without any external component (apart from the JDK, of course), in various configurations.

In the following, we'll see how we can use Portofino with Spring Boot, with increasing levels of integration.

Level 0 – Portofino as a Boot component library

In this scenario, we write a regular Spring Boot app, and we only use selected Portofino services, e.g., persistence.

TBD

Level 1 – Portofino as a Boot-based service

In this scenario, Portofino is launched as a Spring Boot application, using Spring Boot services and conventions, but it retains Portofino's filesystem-based dispatcher to expose REST resources. So, while Boot can expose additional endpoints, and we can define additional services, Portofino has control of the main entry points into the application.

ApplicationContext Setup

The following is extracted from PortofinoContextLoaderListener's documentation.

[In Portofino, the Spring application context] is made of 3 layers:

  • Parent context - defines Portofino's own beans and modules. It's created once at application startup and destroyed once at shutdown.
  • User context - defines beans according to the user-provided SpringConfiguration class, if any. Otherwise it's an empty context. This context is reloaded automatically whenever the source code of class annotated with Component, org.springframework.context.annotation.Configuration, @Repository or @Service changes, and it can also be refreshed programmatically, via the refresh() method. Note that such capability is meant to aid development, and NOT as a kind of hotswap for production. Requests will fail during a context reload. In fact, in production it can be deactivated by setting the init parameter reloadContextWhenSourcesChange to false in the deployment descriptor (web.xml).
  • Bridge context - a singleton application context meant to be exposed to outside consumers. It has the user context as a parent and it's created and destroyed only once.

From the above, we learn that:

  • Portofino defines some platform beans in its own context;
  • we, as users, can define additional beans in a user context;
  • both contexts are combined and exposed to the application.

In particular, the "user context" is an optional class (Java or Groovy) named SpringConfiguration in the default package. When written in Groovy, it supports hot reload during development.

Modules

Modules are components of a Portofino application. By themselves, they don't do anything – they only enable capabilities that user code can use. Built-in modules include database persistence, the email subsystem, and Quartz scheduling.

Since Portofino 5, modules are automatically discovered at startup and registered as Spring beans. They can contribute other beans to the context declaring them with the @Bean annotation.

Users can write their own modules if they want to build redistributable components that implement some capability – for example, support for a proprietary database system. The only limitation currently is that modules must be written in Java, or at least compiled ahead of time – they can't be Groovy classes loaded at runtime.

So, in a typical application, it's unnecessary to write a module, as the same result can be obtained more easily by registering beans in the SpringConfiguration class (the user context). We'll want to define a new module only when we desire to package some feature and use it in multiple applications.

Note that modules are activated in an order that Spring computes according to the dependencies among them.

Injection

In actions, modules, and Spring beans (contributed by modules or by the user-defined application context), we can have Spring inject dependencies using the standard @Autowired annotation. Also, these objects can have @PostConstruct and @PreDestroy lifecycle methods, and any other hooks supported by Spring.

Note that Portofino, not Spring, instantiates its own REST resources (known as ResourceActions), and then has Spring autowire them. This mechanism does not apply to Spring beans (including REST resources that aren't ResourceActions) – Spring will directly instantiate and manage them. This shouldn't make any practical difference apart from the fact that, comprehensibly, ResourceActions are not beans in the Spring context (they're not singletons anyway).

Using XML Instead

Spring in Portofino is configured using annotations. However, it's possible to use XML alongside the annotations, for example to reuse some legacy context, or to leverage some feature that is more easily expressed in XML. For that, we can add an @ImportResource annotation on the SpringConfiguration class. That's standard Spring practice, it's nothing specific to Portofino. Note, though, that in that case the context won't be reloaded when the XML changes – only the SpringConfiguration class is monitored.

The Application Directory

Portofino needs a directory on the file system to run an application. In case of a WAR, this is the WEB-INF directory in the expanded WAR. With Spring Boot, the application directory is one of the following:

  • The value of the --app-dir command-line flag, if provided;
  • src/main/resources/portofino or src/main/webapp/WEB-INF if it exists, this allows to run a Portofino-Boot app in development against a project's source code;
  • The current directory, if it contains a portofino.properties file;
  • Otherwise, the portofino-application directory under the current directory.

In the last case, if the portofino-application directory does not exist, it is created and pre-populated with an empty application.

Command-line Arguments

Besides common Spring Boot options, we can use the following command-line arguments with a Portofino-Boot application:

  • --app-dir=foo/bar specifies where the Portofino application is located
  • --rest-api-base-path=/my/api (Portofino 5.3.1+) specifies the path where Portofino should listen for REST API calls. By default, it registers under /. Note that, with the Jersey JAX-RS implementation (the default), this still allows Boot to register endpoints alongside Portofino resources.

WAR Deployment

This is the traditional deployment option for Portofino applications with a UI. There are no differences in features between a Spring Boot Portofino application and a WAR deployment scenario, except that:

  • the WAR version includes the Angular UI;
  • the WAR initializes Portofino using conventional Servlet API facilities, rather than using Spring Boot services and conventions;
  • the WAR application is typically deployed using an external application server (although it's possible to build a self-contained WAR file that includes an embedded Tomcat).

In particular, the context structure is the same, and dependency injection works the same way. Any undocumented deviation from the Level 1 Boot Application behavior is to be considered a bug.