Dependency injection is core to the way components are instantiated in GoCD and to this end we use Spring framework (v3.1.3).
GoCD uses XML based configuration to define the dependencies. Being a web-application,
web.xml defines a ContextLoaderListener which would create an ApplicationContext for GoCD server, the location for which is defined as:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext-*.xml</param-value> </context-param>
The configuration metadata has been broken into multiple applicationContext xml files, each dedicated to define specific kind of beans. For instance, applicationContext-acegi-security.xml defines all security related filters whereas, applicationContext-dataLocalAccess.xml defines all database specific beans. These applicationContext files are placed under WEB-INF folder in server module.
server | +-- webapp | +-- WEB-INF | +-- applicationContext-global.xml +-- applicationContext-acegi-security.xml +-- applicationContext-dataLocalAccess.xml +-- applicationContext-remote-server.xml +-- applicationContext-shine-server.xml
This started off as a good approach, but as the codebase grew, some of the files grew much bigger containing components from many modules. We are now trying to keep each context xml file small and within the respective module. Module specific context file needs to be imported explicitly by applicationContext-global.xml. eg:
in go-plugin-infra module:
go-plugin-infra | +-- resources | +-- applicationContext-plugin-infra.xml
component-scan is the default way to register a bean with spring. You might find a few beans which are defined explicitly, but those are just exceptions and not the norm.
We use both constructor and setter injection, the vast majority being constructor-injection. For a minority, an 'init' method is defined which is set as the
default-init-method in spring configuration, these methods, however, do nothing more than registering a few listeners or doing something really simple.
We needed to perform initialization of a few components after initialization of certain other non-dependent components were complete. A sample usage being withholding all material updates until all enabled plugins are loaded. This can be achieved easily using Spring by either adding a constructor dependency of PluginManager (manages plugins in GoCD) on MaterialUpdateService (performs updated for materials), or by using a DependsOn annotation. However, MaterialUpdateService does not really require an instance of a PluginManager for literally any job that the class performs, and hence this way of adding a dependency was undesirable. Thus, we came up with ApplicationInitializer. ApplicationInitializer is invoked once dependency-resolution is completed by Spring. At this point, we go about initializing all the components in the desired order.
As a rule, the constructors of all beans just assign the injected beans to the respective field variables. If any action needs to be performed as a part of the initialization process of the class in question, the call for the same needs to be made from ApplicationInitializer in the right order.
applicationContext.xml (at agent/src) defines the beans for GoCD agent. This file has not been split based on modules or any other manner as the file is small enough. An additional benefit of creating application-context file at module level is that the main application-context file could just import the module level xml file instead of repeating the definitions once for server and then for agent.
For instance, Plugin framework is loaded up for server and agent, hence both would just have to import