The context holds the driver's internal components. It is exposed in the public API as DriverContext
, accessible via session.getContext()
. Internally, the child interface InternalDriverContext
adds access to more components; finally, DefaultDriverContext
is the implementing class.
Most components initialize lazily (see LazyReference
). They also reference each other, typically by taking the context as a constructor argument, and extracting the dependencies they need:
public DefaultTopologyMonitor(InternalDriverContext context) { ... this.controlConnection = context.getControlConnection(); }
This avoids having to handle the initialization order ourselves. It is also convenient for unit tests: you can run a component in isolation by mocking all of its dependencies.
Obviously, things won't go well if there are cyclic dependencies; if you make changes to the context, you can set a system property to check the dependency graph, it will throw if a cycle is detected (see CycleDetector
):
-Dcom.datastax.oss.driver.DETECT_CYCLES=true
This is disabled by default, because we don't expect it to be very useful outside of testing cycles.
As should be clear by now, the context is a poor man's Dependency Injection framework. We deliberately avoided third-party solutions:
The basic approach to plug in a custom internal component is to subclass the context.
For example, let's say you wrote a custom NettyOptions
implementation (maybe you have multiple sessions, and want to reuse the event loop groups instead of recreating them every time):
public class CustomNettyOptions implements NettyOptions { ... }
In the default context, here's how the component is managed:
public class DefaultDriverContext { // some content omitted for brevity private final LazyReference<NettyOptions> nettyOptionsRef = new LazyReference<>("nettyOptions", this::buildNettyOptions, cycleDetector); protected NettyOptions buildNettyOptions() { return new DefaultNettyOptions(this); } @NonNull @Override public NettyOptions getNettyOptions() { return nettyOptionsRef.get(); } }
To switch in your implementation, you only need to override the build method:
public class CustomContext extends DefaultDriverContext { public CustomContext(DriverConfigLoader configLoader, ProgrammaticArguments programmaticArguments) { super(configLoader, programmaticArguments); } @Override protected NettyOptions buildNettyOptions() { return new CustomNettyOptions(this); } }
Then you need a way to create a session that uses your custom context. The session builder is extensible as well:
public class CustomBuilder extends SessionBuilder<CustomBuilder, CqlSession> { @Override protected DriverContext buildContext( DriverConfigLoader configLoader, ProgrammaticArguments programmaticArguments) { return new CustomContext(configLoader, programmaticArguments); } @Override protected CqlSession wrap(@NonNull CqlSession defaultSession) { // Nothing to do here, nothing changes on the session type return defaultSession; } }
Finally, you can use your custom builder like the regular CqlSession.builder()
, it inherits all the methods:
CqlSession session = new CustomBuilder() .addContactPoint(new InetSocketAddress("1.2.3.4", 9042)) .withLocalDatacenter("datacenter1") .build();