12 KiB
Services
Registering and using on-demand services to encapsulate plugin functionality.
A service is a plugin component loaded on demand when your plugin calls the getService()
method of corresponding ComponentManager
instance (see Types).
The IntelliJ Platform ensures that only one instance of a service is loaded even though it is called several times.
Services are used to encapsulate logic operating on a set of related classes or to provide some reusable functionality that can be used across the plugin project, and conceptually don't differ from the service classes in other languages or frameworks.
A service must have an implementation class that is used for service instantiation. A service may also have an interface class used to obtain the service instance and provide the service's API.
A service needing a shutdown hook/cleanup routine can implement Disposable
and perform necessary work in dispose()
(see Automatically Disposed Objects).
Types
The IntelliJ Platform offers three types of services: application-level services (global singleton), project-level services, and module-level services. For the latter two, a separate instance of the service is created for each instance of its corresponding scope, see Project Model Introduction.
Avoid using module-level services as it can increase memory usage for projects with many modules.
{style="note"}
Constructor
Project/Module-level service constructors can have a Project
/Module
argument.
To improve startup performance, avoid any heavy initializations in the constructor.
Using constructor injection of dependency services is deprecated (and not supported in ) for performance reasons.
Other service dependencies must be acquired only when needed in all corresponding methods, e.g., if you need a service to get some data or execute a task, retrieve the service before calling its methods. Do not retrieve services in constructors to store them in class fields.
Use inspection Plugin DevKit | Code | Non-default constructors for service and extension class to verify code.
{style="warning"}
Light Services
A service not going to be overridden does not need to be registered in plugin.xml (see Declaring a Service).
Instead, annotate service class with @Service
.
Project-level services must specify @Service(Service.Level.PROJECT)
.
The service instance will be created in scope according to the caller (see Retrieving a Service).
Light Service Restrictions
- None of these attributes is required:
os
,client
,overrides
,id
,preload
. - Service class must be
final
. - Constructor injection of dependency services is not supported.
- If application-level service is a PersistentStateComponent, roaming must be disabled (
roamingType = RoamingType.DISABLED
).
Use these inspections to verify these and highlight services that can be converted (2023.3):
- Plugin DevKit | Code | Light service must be final
- Plugin DevKit | Code | Mismatch between light service level and its constructor
- Plugin DevKit | Code | A service can be converted to a light one and corresponding Plugin DevKit | Plugin descriptor | A service can be converted to a light one for plugin.xml
Examples
Application-level light service:
@Service
public final class MyAppService {
public void doSomething(String param) {
// ...
}
}
Project-level light service example:
@Service(Service.Level.PROJECT)
public final class MyProjectService {
private final Project myProject;
public MyProjectService(Project project) {
myProject = project;
}
public void doSomething(String param) {
String projectName = myProject.getName();
// ...
}
}
Application-level light service:
@Service
class MyAppService {
fun doSomething(param: String) {
// ...
}
}
Project-level light service example:
@Service(Service.Level.PROJECT)
class MyProjectService(private val project: Project) {
fun doSomething(param: String) {
val projectName = project.name
// ...
}
}
Declaring a Service
To register a non-Light Service, distinct extension points are provided for each type:
com.intellij.applicationService
- application-level servicecom.intellij.projectService
- project-level servicecom.intellij.moduleService
- module-level service (not recommended, see Note above)
To expose service API, create separate class for serviceInterface
and extend it in corresponding class registered in serviceImplementation
.
If serviceInterface
isn't specified, it's supposed to have the same value as serviceImplementation
.
Use inspection Plugin DevKit | Plugin descriptor | Plugin.xml extension registration to highlight redundant serviceInterface
declarations.
To provide custom implementation for test/headless environment, specify testServiceImplementation
/headlessImplementation
additionally.
Example
Application-level service:
-
Interface:
public interface MyAppService { void doSomething(String param); }
-
Implementation:
public final class MyAppServiceImpl implements MyAppService { @Override public void doSomething(String param) { // ... } }
Project-level service:
-
Interface:
public interface MyProjectService { void doSomething(String param); }
-
Implementation:
public final class MyProjectServiceImpl implements MyProjectService { private final Project myProject; public MyProjectServiceImpl(Project project) { myProject = project; } public void doSomething(String param) { String projectName = myProject.getName(); // ... } }
Application-level service:
-
Interface:
interface MyAppService { fun doSomething(param: String) }
-
Implementation:
class MyAppServiceImpl : MyAppService { override fun doSomething(param: String) { // ... } }
Project-level service:
-
Interface:
interface MyProjectService { fun doSomething(param: String) }
-
Implementation:
class MyProjectServiceImpl(private val project: Project) : MyProjectService { fun doSomething(param: String) { val projectName = project.name // ... } }
Registration in plugin.xml:
<extensions defaultExtensionNs="com.intellij">
<!-- Declare the application-level service -->
<applicationService
serviceInterface="com.example.MyAppService"
serviceImplementation="com.example.MyAppServiceImpl"/>
<!-- Declare the project-level service -->
<projectService
serviceInterface="com.example.MyProjectService"
serviceImplementation="com.example.MyProjectServiceImpl"/>
</extensions>
If declared services are intended to be used by other plugins depending on your plugin, consider bundling their sources in the plugin distribution.
{style="note"}
Retrieving a Service
Never acquire service instances prematurely or store them in fields for later use. Instead, always obtain service instances directly and only at the location where they're needed. Failing to do so will lead to unexpected exceptions and severe consequences for the plugin's functionality.
Such problems are highlighted via inspections (2023.3):
- Plugin DevKit | Code | Application service assigned to a static final field or immutable property
- Plugin DevKit | Code | Incorrect service retrieving
- Plugin DevKit | Code | Simplifiable service retrieving
{style="warning" title="Correct Service Retrieval"}
Getting a service doesn't need a read action and can be performed from any thread. If a service is requested from several threads, it will be initialized in the first thread, and other threads will be blocked until it is fully initialized.
MyAppService applicationService =
ApplicationManager.getApplication().getService(MyAppService.class);
MyProjectService projectService =
project.getService(MyProjectService.class);
Service implementations can wrap these calls with convenient static getInstance()
or getInstance(Project)
method:
MyAppService applicationService = MyAppService.getInstance();
MyProjectService projectService = MyProjectService.getInstance(project);
val applicationService = service<MyAppService>()
val projectService = project.service<MyProjectService>()
@startuml
skinparam monochrome true
skinparam DefaultFontName JetBrains Sans
skinparam DefaultFontSize 13
skinparam DefaultTextAlignment center
skinparam NoteTextAlignment left
' default 1.5
skinparam ActivityBorderThickness 1
' default 2
skinparam PartitionBorderThickness 1.5
:getService;
note right
Allowed in any thread.
Call on demand only.
Never cache the result.
Do not call in constructors
unless needed.
end note
if (Is Light Service) then (yes)
else (no)
if (Is Service Declaration Found) then (yes)
else (no)
:Return null;
detach
endif
endif
if (Is Created and Initialized?) then (yes)
else (no)
if (Is Container Active?) then (yes)
partition "synchronized\non service class" {
if (Is Created and Initialized?) then (yes)
else (no)
if (Is Initializing?) then (yes)
:Throw
PluginException
(Cyclic Service
Initialization);
detach
else (no)
partition "non-cancelable" {
:Create Instance]
note right
Avoid getting other
services to reduce
the initialization tree.
The fewer the
dependencies,
the faster and more
reliable initialization.
end note
:Register to be Disposed
on Container Dispose
(Disposable only)]
:Load Persistent State
(PersistentStateComponent
only)]
}
endif
endif
}
else (disposed or dispose in progress)
:Throw
ProcessCanceledException;
detach
endif
endif
:Return Instance;
@enduml
Sample Plugin
To clarify how to use services, consider the maxOpenProjects sample plugin available in the code samples.
This plugin has an application service counting the number of currently opened projects in the IDE. If this number exceeds the maximum number of simultaneously opened projects allowed by the plugin (3), it displays an information message.
See Code Samples on how to set up and run the plugin.